In this post I will describe how we are using server side GWT RPC serialization at Ikayzo to both improve performance and simplify client code logic.
The cache file can be very large, but as its name implies, it should be cached by the client and is intended to be downloaded only once. (So it is not shown in the sequence above.) The tradeoff here is that by performing a quick check with every page load GWT achieves what Google terms "perfect caching".
We used this technique extensively in a recent project and it has provided a lot of benefit for both the end user experience and maintenance of the client code. There are, however, tradeoffs in complexity on the server side and a few things that are a real hassle to deal with. In the rest of this post I'll share the snippets of code that will save you some time and outline some of the issues that still need to be addressed to make this easier in the future.
In our code we have created a number of convenience methods and built some of this into our base application layer. But the effect is that on the client side we can simply make a call like the following in our widget initialization code:
// Client Side - What we're going for...
Customer customer = getSerializedObject("customer");
The first step to making the above happen is to capture the Customer object on the server side while we are building the page. This implies of course that you have a presentation layer or servlet feeding your pages and that it knows what the client needs. We use Struts 2 as a "glue" layer to help us build our GWT pages and so in our case this server side data capture happens in a Struts action class.
Let's assume that we get our Customer from a GWT RPC service with a method called getCustomerById(). First we'll simply invoke the method on the server side. If you haven't done anything egregious in your service implementation then instantiating an instance of it should be a lightweight operation. (We use Spring to hold all of our controllers and setup). Alternately you can register your instance somewhere and reuse it. So we'll get the Customer object like this:
// Server Side - Get the customer object
long customerId = ...;
Customer customer = new MyServiceImpl().getCustomerById( customerId );
The next step is to serialize the object.
// Server Side - Serialize customer to a String
String serializedCustomer =
RPC.encodeResponseForSuccess( serviceMethod, customer, serializationPolicy );
The snippet above references our customer object as well as two new items that require explanation: The service method and serialization policy. The public API for serializing the object is the static method RPC.encodeResponseForSuccess(). This method requires a reference to the java.lang.reflect Method object representing our getCustomerById() interface method. It's not going to invoke the method (we've already done that), but it seems to require it simply to determine the declared return type. We can get the method reference like this:
// Get the Method for encodeResponseForSuccess()
Method serviceMethod = MyService.class.getMethod( "getCustomerById", Long.TYPE )
Here we are asking the MyService class for a reference to our method. The first argument to getMethod() is the name of the method and what follows is a varargs list of parameter types. In this case the special field Long.TYPE represents the primitive long type. (Long.class would represent the Long wrapper type). If there were additional arguments we'd list their types here.
Next there is the serialization policy. This is a bit of a headache. If you wish to simply tag your domain classes with java.io.Serializable (standard Java) and use them as is recommended with GWT going forward then you have to load a serialization policy that tells GWT which classes it is allowed to serialize. The serialization policy lists all of the application classes that you reference from the interface. GWT generates one of these files for each of your service interface at build time. The file is named .gwt.rpc. The is the 32 character MD5 hash that is used to uniquely name files in the GWT build. You must identify this file and load it using the SerializationPolicyLoader. Here we have copied the file to the root of our classpath and renamed it simply "gwt.rpc". Please note that this file must be deployed to the server side classpath in this scenario:
The difficulty I alluded to can be seen above. I cannot tell you how to identify the correct .gwt.rpc file as it will have a unique name based on the contents. At the moment this is a manual step in our build when the interface changes. I can think of a number of hacky ways to automate this step, but as far as I know there are only two good solutions (neither of which I have tried yet). One would be to write a GWT linker that participates in the build and can easily export a copy of the file under the correct name. I'm sure someone will respond to this posting by telling me how easy that is to do and I hope they also send an example :) Another way would be to simply implement a custom SerializationPolicy that is aware our class structure in a broader way. But such as custom policy would have to be aware of which standard types are allowed to be serialized as well as which custom types. Getting this wrong would not be a security issue in this case, but would certainly cause failures at runtime.
// Server side - Get the SerializationPolicy for encodeResponseForSuccess()
// Note: You should cache the SerializationPolicyInputStream in =
GadgetAction.class.getResourceAsStream( "/gwt.rpc" );
SerializationPolicy serializationPolicy =
SerializationPolicyLoader.loadFromStream( in );
// Server Side String utils
s = escapeScriptTags( s ); //