Deferred Binding: The Magic of GWT Ray Cromwell CTO, Timepedia, Inc.

Post on 29-Dec-2015

218 views 2 download

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

Discussion

Any questions?