2/25/09   Integrating Google Web Toolkit (GWT) with Spring

I've seen a lot of complicated ways of doing this on the intraweb, but I came up with a very simple approach. No doubt someone will tell me why it's wrong.

The Goal

The aim is to handle GWT remote (RPC) calls using Spring beans, so that other managed Spring beans can be injected into them. For example, if the remote service needs to make calls to an EJB, the EJB is injected into the remote service as a Spring managed proxy and the remote service does not need to faff around with EJB lookups (we're talking EJB2.1 here). SImilarly, data sources and other dependencies can be injected.

The Problem

GWT remote services extend a GWT base class and are configured directly as servlets in the web deployment descriptor. Spring doesn't get a look in.

The Solution

Make the GWT remote services behave as Spring web controllers and then use Spring MVC to forward requests to them, injecting dependencies.

First of all, create a new abstract subclass of GWT's RemoteServiceServlet. This will provide the implementation of Spring's Controller interface. Importantly, it also implements the marker interface ServletContextAware. When Spring sees this marker interface, it injects the servlet context into the instance. This servlet context is required by GWT and so we override GWT's normal implementation of it (which assumes the remote service is running as a servlet). That's all we need to do to make the remote service run without being called directly as a servlet request.

public abstract class BaseServiceImpl extends RemoteServiceServlet implements Controller, ServletContextAware {
  private ServletContext servletContext;

  public final ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    doPost(req, resp);
    return null;
  }

  public ServletContext getServletContext() {
    return servletContext;
  }

  public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
  }
}

Now simply extend this class with your remote services and implement your remote interfaces as normal, providing any injected dependencies you want:

public class MyServiceImpl extends BaseServiceImpl implements MyService {
   MyLovelyEjb delegate;

   public void doStuff() { delegate.doStuff(); }

   public void setDelegate(MyLovelyEjb delegate) { ... }
}

This bean is wired up in Spring as normal:

<bean id="myService" class="com.tapina.example.MyServiceImpl">
  <property id="delegate" ref="myLovelyEjb"/>
</bean>

Now, instead of configuring the service in the web.xml, you just set up Spring MVC there:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring-gwt.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
  <servlet-name>spring-gwt</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>spring-gwt</servlet-name>
  <url-pattern>*.rpc</url-pattern>
</servlet-mapping>

The above need only be set up once, thereafter services are configured in the spring XML file:

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  <property name="mappings">
    <value>
        **/myService.rpc=myService
    </value>
  </property>
</bean>

More services can be added easily, with all their Spring-managed dependencies injected. The remote service implementation remain subclasses of RemoteServiceServlet and so can be used in or out of Spring.