How to Integrate Spring 2.x with the Google Web Toolkit (GWT)

Level: Intermediate
Date: Originally published 10/07, updated 4/08.
Author: Richard Bondi,

This post explains how to manage your GWT server-side services with Spring and Spring MVC, and to inject Spring beans into them. It is based on the 11th entry in this GWT forum post.

Prerequisites

Jump Start

  If you want to set up Spring and GWT without reading the background, just follow these steps.

  1. Add the Spring MVC DispatcherServlet to your web.xml, as shown in Figure 1.
  2. To handle the case of a GWT client calling a GWT service, modify your [name of DispatcherServlet]-servlet.xml file as follows (see Figure 4 for sample code):
    1. Use a HandlerMapping of your choice to map a URL to the GWTController, and
    2. inject the GWT service into GWTController, and at the same time inject any non-GWT services into the GWT service.
  3. For each additional GWT client and service, repeat the previous step.
  4. Create the Spring controller GWTController (see Figure 5 for the full source code).

The rest of this article explains these steps in detail, but not quite in this order. Note that when terms are mentioned for the first time, they are bolded.

How to configure Spring MVC to call GWTController

Spring's MVC works first of all by redirecting browser request urls to Spring's DispatcherServlet. We do this in Figure 1 by configuring our web server's web.xml file to redirect all urls ending in ".whatever" to DispatcherServlet.

[Figure 1: The web.xml entry for the DispatcherServlet redirect.]
<web-app>
...
<servlet>
   <servlet-name>mywebapp</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>
mywebapp</servlet-name>
  <url-pattern>*.whatever</url-pattern>
</servlet-mapping>

...
</web-app>

Next, the DispatcherServlet must choose a org.springframework.web.portlet.mvc.Controller to process the request. It does this by using one of many so-called "handlers" or "handler mappers," which you can specify in the [servlet-name]-servlet.xml file. (In our case (see Figure 1), this file is mywebapp-servlet.xml.) So a DispatcherServlet routes URLs to a handler mapper, and a handler mapper routes URLs to Spring controllers. Figure 2 illustrates how to use the SimpleUrlHandlerMapping handler to route a URL to a controller class GWTController, which we will be writing later on.

[Figure 2: The mywebapp-servlet.xml using SimpleUrlHandlerMapping to route URLs to a Spring MVC controller named GWTController]
<beans>

  <!-- == SPRING DISPATCH HANDLER == -->

  <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <value>
        /**/login.whatever=loginController
        /**/userProfile.whatever=fooController
      </value>
    </property>
  </bean>

  ...

  <bean name="loginController" class="com.foo.servlet.GWTController">
  ...
  </bean>


  <bean name="userProfileController" class="com.foo.servlet.GWTController">
  ...
  </bean>

 
The code in Figures 1 and 2 says that the DispatcherServlet will take any request whose URL ends in "login.whatever" and forward it to a class GWTController whose application context id is loginController. (Figure 2 does not show the body of loginController, where other beans may be injected into it; we will get to that later.) Any URLs that end in "foo.whatever" will also be routed to an instance of GWTController, but it will have different beans injected to it from the ones injected into loginController. (The injections are not shown in Figure 2.)
 
The code of GWTController is shown in Figure 5, but how it works is going to take some explaining.

How GWTController works

Here we need to pause for a brief refresher on GWT RPC. As the GWT documentation states, a server-side RPC class is called a "service" in GWT:
The server-side code that gets invoked from the client is often referred to as a service, so the act of making a remote procedure call is sometimes referred to as invoking a service. To be clear, though, the term service in this context isn't the same as the more general "web service" concept. In particular, GWT services are not related to the Simple Object Access Protocol (SOAP).
Furthermore, every service must implement RemoteService and extend RemoteServiceServlet. RemoteService ties the service to the client-side code that will call it, because they both implement this interface. RemoteServiceServlet extends javax.servlet.http.HttpServlet, and contains all the RPC encoding and decoding code, which every GWT service obviously needs. When you use a GWT service without Spring, the client's call to the service calls the RemoteServiceServlet's doPost() method. (If you look at the GWT source code, you'll see this happens via reflection when the Ajax client call is made.)

With this in mind, let's look at the chain of method calls that occurs when login.whatever is requested.

The DispatcherServlet routes the login.whatever URL to SimpleUrlHandlerMapping, which routes it to a GWTController named loginController. The class GWTController extends GWT's RemoteServiceServlet, but it also implements Spring's MVC org.springframework.web.servlet.mvc.Controller interface, making it a Spring MVC controller. SimpleUrlHandlerMapping calls the Controller interface's handleRequest() method. As you'll see soon, we've written GWTController's handleRequest() to simply call RemoteServiceServlet's doPost(), and return null. One of the things this method does (and it does a lot) is to call the RemoteServiceServlet processCall() method.

Before we turn to processCall(), you may want to look at Figure 3. It summarizes the call chain we've just described. The "::" symbol has a class to its left and an interface to its right, and ":" is followed by a method belonging to the interface that precedes it. "->" means "calls."

[Figure 3: The Spring MVC - GWT call chain for the configuration in Figures 1 and 2]
Browser: http://xyz/login.whatever
-> DispatcherServlet
-> SimpleUrlHandlerMapping
-> loginController::Controller:handleRequest()
-> loginController::RemoteServletService:doPost()
-> loginController::RemoteServletService:processCall()
Now let's look at processCall(). Without Spring, whenever a GWT client makes an Ajax RPC call to a GWT (RemoteServiceServlet) service, the client is sending an RPC-encoded payload, and the calls to doPost() and processCall() are hidden from you the GWT programmer (they are called by reflection). The service's processCall() will RPC decode the payload with the code shown in Figure 4.

[Figure 4: Code fragment from com.google.gwt.user.server.rpc.RemoteServletService]
  ...
  public void processCall() {
    ...
    RPCRequest rpcRequest = RPC.decode(payload, this);
    ...
  }

Typically in GWT you write a different service for different client calls. If your page makes an RPC call to login, it will make an Ajax/RPC call to a login service named perhaps LoginService. If the same page makes an RPC call to get user profile information, it will call a service named perhaps UserProfileService. The this in Figure 4 will refer to the LoginService in one case, and to the UserProfileService in the other.
 
In Figure 2, URLs are all routed to an instance of GWTController. If one URL is for logging in, and another is for getting a user profile, we have a problem: it's not clear how GWTController can do both tasks. Specifically, each time GWTController's processCall() is called, the "this" in Figure 4 will always be the same GWTController. It can't be a LoginService for one URL, and UserProfileService for another. 

The solution is to inject services like LoginService and UserProfileService into GWTController, and to override processCall() to change this to refer to the injected service. Figure 5 contains the source code of GWTController with these features. There is a setter for injecting anything that implements GWT's RemoteService, for injecting a GWT service. The processCall() method has been changed to use the injected GWT service instead of "this." Finally, the Spring Controller method handleRequest() calls the underlying RemoteServiceServlet's doPost(), which (see the RemoteServiceServlet source code) calls our now overridden processCall(). (You can review the entire call chain in Figure 3.)

[Figure 5: GWTController.java]
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.ModelAndView;
import com.google.gwt.user.client.rpc.RemoteService;
public class GWTController extends RemoteServiceServlet implements Controller {
  // Instance fields
  private RemoteService remoteService;
  private Class remoteServiceClass;

  // Public methods
  /**
   * Call GWT's RemoteService doPost() method and return null.
   *
   * @param request The current HTTP request
   * @param response The current HTTP response
   * @return A ModelAndView to render, or null if handled directly
   * @throws Exception In case of errors
   */

  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    doPost(request, response);
    return null; // response handled by GWT RPC over XmlHttpRequest
  }

  /**
   * Process the RPC request encoded into the payload string and return a
   * string that encodes either the method return or an exception thrown by
   * it.
   * @param payload The RPC payload
   */

  public String processCall(String payload) throws SerializationException {
    try {
      RPCRequest rpcRequest = RPC.decodeRequest(payload, this.remoteServiceClass);

      // delegate work to the spring injected service
      return RPC.invokeAndEncodeResponse(this.remoteService, rpcRequest
          .getMethod(), rpcRequest.getParameters());
    } catch (IncompatibleRemoteServiceException e) {
      return RPC.encodeResponseForFailure(null, e);
    }
  }

  /**
   * Setter for Spring injection of the GWT RemoteService object.
   *
   * @param RemoteService
   *            The GWT RemoteService implementation that will be delegated to
   *            by the {@code GWTController}.
   */

  public void setRemoteService(RemoteService remoteService) {
    this.remoteService = remoteService;
    this.remoteServiceClass = this.remoteService.getClass();
  }
}

In addition to injecting a single GWT service into GWTController, you can first inject as many non-GWT classes as you wish into the GWT service. These will typically come from your business layer. And that, after all, is probably why you wanted to use Spring in the first place! Figure 6 contains sample code showing how this might be done.

<beans>

...

<!-- == SPRING DISPATCH HANDLER == -->

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/**/login.whatever=loginController
/**/userProfile.whatever=userProfileController
</value>
</property>
</bean>
...


<!-- == LOGIN GWT SERVICE == -->

<bean name="loginController" class="com.aspentech.imos.servlet.GWTController">
<property name="remoteService">
<bean class="com.foo.gwt.login.server.LoginServiceImpl">
<constructor-arg index="0" ref="someNonGWTService1"/>
<constructor-arg index="0" ref="someNonGWTService2"/>
<constructor-arg index="0" ref="someNonGWTService3"/>
</bean>
</property>
</bean>

<!-- == USER PROFILE GWT SERVICE == -->

 <bean name="userProfileController" class="com.aspentech.imos.servlet.GWTController">

<property name="remoteService">
<bean class="com.foo.gwt.login.server.UserProfileService">
<constructor-arg index="0" ref="someNonGWTService10"/>
<constructor-arg index="0" ref="someNonGWTService11"/>
<constructor-arg index="0" ref="someNonGWTService12"/>
</bean>
</property>
</bean>

</beans>
That's it! Enjoy using GWT with Spring.



Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License.