Deferred Binding: The Magic of GWT Ray Cromwell CTO, Timepedia, Inc.
-
Upload
helen-joseph -
Category
Documents
-
view
217 -
download
2
Transcript of Deferred Binding: The Magic of GWT Ray Cromwell CTO, Timepedia, Inc.
Deferred Binding:The Magic of GWT
Ray Cromwell CTO, Timepedia, Inc
“Any sufficiently advanced technology is indistinguishable from magic.” -
Arthur C. Clarke
Deferred Binding
• What is it?• Why it’s needed.• How does GWT use it?• The Nitty Gritty of How it Works• How to create your own Deferred
Binding• Discussion
What is it? Let’s start with Static Binding
• Connection c = new MySQLConnection()– c tied to specific implementation, happens at
compile time– No ability to defer choice until runtime, user
stuck with MySQL connection
• Want: Connection c = load(userDriver);– Where userDriver selectable at runtime
Fix: Dynamic Binding
• Java has Dynamic Binding– Dynamic Class Loading– Dynamic Method Invocation (Reflection)
• Used by many Java applications– Runtime loadable drivers (JDBC, JAXP, etc)– Dependency Injection/Method Interceptors– Persistence Frameworks (Hiberbate/JPA)– Java Service Provider Interfaces (SPIs)
Another Solution: Deferred Binding• “Compile Time Dynamic Binding”• Still allows ‘userDriver’ selection to
be made at runtime• Conceptually similar
– GWT “dynamically loads” classes at compile time
• Similar capabilities in terms of reflection and method interception
Deferred Binding differs in Actualization• GWT determines set of possible bindings for
each instantiation• For each permutation, generates Javascript with
specific bindings ‘baked in’• Deferred Bindings can be ‘intercepted’
– Can delegate to a Generator, generates code on-the-fly– Provides full featured reflection API called TypeOracle
• Bootstrap selection script loads Javascript ‘executable’ containing correct set of bindings for given situation
Another way to look at it
• Imagine a database app which loads a JDBC driver via Class.forName()
• What if javac discovered all your JDBC drivers and– Compiled a separate executable for
each driver (MyOracleApp, MySybaseApp, etc)
– Used a startup script to pick the right version depending on your preferences
To Summarize
• Static Binding– Foo f = new Foo();
• Dynamic Binding– Class c = Class.forName(fooImplName);– Foo f = (Foo)c.newInstance();
• Deferred Binding– Foo f = (Foo)GWT.create(Foo.class);
Why it’s needed
• Smaller code• Better optimizations• Fewer network roundtrips• Metaprogramming• GWT Mantra: Why do at runtime
what you can do at compile time?
Smaller Code
• Browser and Locale differences– One set of functionality, many different
implementations
• Why force Firefox user to download inactive Opera implementation?
• Why force Chinese user to download English?
• Dynamic loading would add unneeded extra roundtrips and startup time
Better Optimization
• Dynamic Binding makes optimization more difficult– Compiler can’t analyze code it can’t see
• GWT sees all source code at compile time
• It optimizes each permutation as if dynamic bindings are statically bound
Example
• Animal a = (Animal)GWT.create(Animal.class)
• Bindings exist for Cat and Dog as implementations of Animal
• a.speak()– GWT can inline Cat.speak() and Dog.speak()– Can also remove dead code not touched by
Dog when running Cat permutation– Javac can’t.
Real World Example
DOM.setInnerText(element, “text”);
Delegates to implementation returned by GWT.create(DOMImpl.class)
Real World Example (cont)
In DOMImplIE6.javapublic native void setInnerText(Element
elem, String text) /*-{ if (!text) text = ''; elem.innerText = text; }-*/;
Real World Example (cont)In DOMImpl (for Safari) public native void setInnerText(Element elem, String text)
/*-{ // Remove all children first. while (elem.firstChild) { elem.removeChild(elem.firstChild); } // Add a new text node. if (text != null) { elem.appendChild($doc.createTextNode(text)); } }-*/;
Resulting Javascript on IE
DOM.setInnerText(element, “test”);
Compiles to
element.innerText = “test”;
On Safari, the result is
$setInnerText(element, ‘Test’);function $setInnerText(elem, text) { while (elem.firstChild) { elem.removeChild(elem.firstChild); } if (text != null) {
elem.appendChild($doc.createTextNode(text)); }}
Fewer roundtrips
• Typical Web applications include external resources– External JS scripts– Cascading Style Sheets– Images– Locale resource translations
• Deferred Binding allows resources to be ‘baked’ into crunched-down, streamlined versions
• Reduce network trips, increase startup speed
Metaprogramming
• Extend Java language with annotations or by interface convention
• Intercept class creation and substitute on-the-fly implementations
• Example: Javascript Interop– Create Interface (e.g. GMap2)– Map interface to JS library (e.g. Google Maps)– Have GWT compiler generate glue code
automatically
Super Cool Example: Image Bundlesinterface MyIcons extends ImageBundle { Image mySubmitBtn(); Image myLoadBtn(); Image myCancelBtn();}MyIcons mIcons =
(MyIcons)GWT.create(MyIcons.class)
Super Cool Example: Explained
• ImageBundle is tied to a Generator• Generator looks for gif/jpg/png icons of the
same name as each method• Concatenates all icons into single mosaic
image• Browser loads only 1 icon file• Generates MyIcons implementation class,
which returns icon clipped to region of interest
Summary: What’s the Mantra?
• Why do at runtime what you can do at compile time?
How does GWT use it?
• ImageBundles• RPC
– Generates custom serialization/service invocation impl for your Remote Interfaces
• Browser ‘detection’– Swaps in different DOM, event, and UI class
implementations based on browser• Localization
– Generates implementations of ResourceBundle-like classes per locale
The Nitty Gritty Details
• Module File Declarations– Defining and Extending Properties– Replace-With and Generate-With
• Permutation Generation• Selection Script
Module Files and Deferred Binding• Properties
– Define an enumerated set of values for a given name
• Rules– Declare how to rebind a given type
• Replace-With or Generate-With
• Conditions– When should this rule be executed?
Properties
• Start with a declaration of the initial enumeration
<define-property name=“gender” values=“male,female”/>
• Set a default value statically<set-property name=“gender” value=“male”/>
Properties (cont)
• A property can be set at runtime via Javascript provider
<property-provider name=“gender"><![CDATA[ return isFemale(document.cookie) ? “female” :
“male”;]]></property-provider>
Properties (cont)
• Modules can inherit your property – And extend its enumerated set of
values<extend-property name=“gender”
values=“neuter”/>
Rules: Replace-With
• Instruct compiler to replace one type with another
• Can limit with Conditions– “Replace all Dogs with Cats when
property Cats rule the world is true” <replace-with class=“org.cats.Cat”> <when-type-is class=“org.dogs.Dog”/> </replace-with>
Rules: Generate-With
• Like Replace-With, only replacement class is generated on-the-fly
• Handled by your Generator subclass – Has full reflection access to all known
classes <generate-with
class=“org.cats.rebind.CatGenerator”> <when-type-is class=“org.cats.VirtualCat”/> </generate-with>
Conditions
• <when-type-is class=“…”/>– Triggers when class attribute matches
• <when-type-assignable class=“…”/>• <when-property-is name=“…” value=“…”/>
– Triggers when property matches value
• Boolean logic supported– Condition OR Condition (<any>)– Condition AND Condition (<all>)– NOT Condition (<none>)
Permutation Generation
• Each property defines a dimension in n-dimensional space
• GWT will compile a version of your application for each position in this space
• Thus, the rebind conditions in the module are evaluated for each possible permutation
A Picture of Permutations
Firefox
Opera
Safari
IE6
English French Chinese
FF_EN
OP_EN
SF_EN
IE_EN
FF_FR
OP_FR
SF_FR
IE_FR
FF_ZH
OP_ZH
SF_ZH
IE_ZH
Permutations
• Of course, with more than 2 properties, we get more dimensions
• That’s a lot of versions, BUT– Sometimes two or more permutations map to
the same compiled code– Trades cheap server disk space for reduced
bandwidth usage, server pressure, and faster user experience
– Your users will thank you for a small, fast app
Selection Script
• Small amount of bootstrap code• Determines property values and maps
them onto a compiled version of app• Allows Perfect Caching
– Users never download big script more than once (Expires: For-eve-ah!)
– When app changes, small Selection Script changes and loads differently named version
• (Selection Script expires Real Soon Now)
Create your own Deferred Binding
• Example 1: Debug vs Production – Pick between two implementations, one
when deploying as debug build, another when deploying to production
• Example 2: Bean Introspector with Generators– Create Interface Introspector which can
return list of JavaBean methods of a class
Example 1: Debug vs Production
• Create Logger interface– DebugLogger produces detailed errors– ProductionLogger no-op
• interface Logger { void log(String msg); }
• Usage: Logger log = (Logger)GWT.create(Logger.class)
Example 1 (cont)
• Step 2: Define new property in Logger.gwt.xml– <define-property name=“logger”
values=“debug,production”>
• Set default value– <set-property name=“logger”
value=“production”/>
Example 1: (cont)
Map property values to implementations<replace-with class=“DebugLogger”> <when-type-is class=“Logger”/> <when-property-is name=“logger” value=“debug”/></replace-with>– Repeat for ProductionLogger
Example 1: (cont) Implement Classesclass DebugLogger implements
Logger { public void log(String msg) { Window.alert(msg); }}
Example 1: (cont)
• Choose version in host page via– <gwt:property name=“logger”
value=“…”/>– Or ?logger=value in URL
• For extra credit– Return separate versions for Firefox,
IE, etc• E.g. Use Firebug console on Firefox
Example 2: Introspector
• Create tagging interface Introspector
interface Introspector {}• To use, derive interface with
annotations• Each method in derived interface
contains annotation declaring the class to be introspected
Example 2
interface MySpector extends Introspector {
/** * @gwt.introspect org.company.Foo */ String[] getFoo();}
Example 2: Usage
MySpector ms = (MySpector)GWT.create(MySpector.class);
// return list of bean properties of FooString beanProperties[] =
ms.getFoo();
Example 2: Generators to the Rescue• In Introspector.gwt.xml
<generate-with class=“MyGenerator”> <when-type-assignable class=“Introspector”/></generate-with>
• Note, Generators should not be in the .client package, by convention place them in a .rebind package
Example 2: Implement a Generator• Place in .rebind package• Extend com.google.gwt.core.ext.Generator• Override public String generate(TreeLogger logger,
GeneratorContext ctx, String requestedClass)• Call ctx.tryCreate(logger, package, className) to
create PrintWriter• Use PrintWriter for outputing new Java source• Inspect type information with TypeOracle from ctx• Return fully-qualified name of generated class
Example 2: Skeletonpublic class MyGenerator extends Generator {public String generate(TreeLogger l, GeneratorContext ctx,
String requestedClass) { PrintWriter pw = context.tryCreate( l, “test”, “TestImpl”); pw.println(“package test;”); pw.println(“public class TestImpl implements MySpector {“); pw.println(“public String[] getFoo() { return String[0]; }”); println(“}”); return “test.MySpectorImpl”; }}
Example 2: Skeleton, Problems
• Impl class always called “TestImpl”• Package fixed as “test”• getFoo() is stubbed
TypeOracle vs Java Reflection
GWT Reflection Java Reflection
TypeOracle.findType Class.forName
JClassType Class
JMethod Method
JParameter Parameter
JField Field
Example 2: Computing destination class/package TypeOracle oracle = ctx.getTypeOracle(); JClassType ctype = oracle.findType(requestedClass); String package = ctype.getPackage().getName(); String genClass = ctype.getSimpleSourceName() +
“Impl”; PrintWriter pw = context.tryCreate( l, package, genClass); pw.println(“package ”+package); pw.println(“public class “+genClass+” implements MySpector
{“); pw.println(“public String[] getFoo() { return String[0]; }”); println(“}”); return package+genClass;
Example 2: Implement getFoo()
TypeOracle oracle = ctx.getTypeOracle(); JClassType ctype = oracle.findType(requestedClass); String package = ctype.getPackage().getName(); String genClass = ctype.getSimpleSourceName() + “Impl”; PrintWriter pw = context.tryCreate( l, package, genClass); pw.println(“package ”+package); pw.println(“public class “+genClass+” implements
MySpector {“); genMethods(oracle, ctype, pw); println(“}”); return package+genClass;
Example 2: Loop over all methods
public void genMethods(TypeOracle oracle, JClassType ctype, PrintWriter pw) {
for(JMethod method : ctype.getMethods()) { String md[][] = method.getMetaData(“gwt.introspect”); if(md != null && md.length > 0) { String classToIntrospect = md[0][0]; genMethod(method, oracle, classToIntrospect,
pw); } }}
Example 2: Generate Method
public void genMethod(JMethod m, TypeOracle oracle, String target,
PrintWriter pw) { JClassType targetType = oracle.findType(target); ArrayList<String> beanProps = new ArrayList<String>(); for(JMethod targetMethod : targetType) if(isBeanMethod(targetMethod)) beanProps.add(t.getName()); pw.println(“public String[] “+m.getName()+”() {”; writeProps(beanProps, pw); pw.println(“}”);}
Example 2: Detecting a bean methodpublic boolean isBeanMethod(JMethod m) { String name = m.getName(); JParameter params[] = m.getParameters(); return name.startsWith(“get”) &&
Character.isUpperCase(name.charAt(3)) && params.length > 0;}
Example 2: Writing out array
void writeProps(ArrayList<String> props, PrintWriter pw) { pw.println(“return new String[] {“); for(String prop : props) { pw.print(“\””+prop.substring(3)+”\”, “); } pw.println(“};”);}
In Summary, Deferred Binding…
• Provides code generation and dynamic binding at compile time
• Allows the GWT compiler to perform an impressive number of optimizations not otherwise possible
• Can dramatically reduce network roundtrips• Permits Perfect Caching
• It’s simply the Magic of Google Web Toolkit
For a more complex tutorial
http://timepedia.blogspot.com/2007/06/gwt-demystified-generators-part-1.html
Discussion
Any questions?