Friday, August 21, 2009

Spring and GWT Servlet Instantiation

I'm another person who wants to make a web application that uses Spring (dependency injection) and GWT (RPC and UI rendering).


GWT recommends setting up a servlet to do the RPC by declaring it in web.xml like this.

 <!-- Servlets -->
 <servlet>
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <servlet-class>com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>stockPriceServiceImpl</servlet-name>
    <url-pattern>/stockwatcher/stockPrices</url-pattern>
  </servlet-mapping>

That way, the web-app container (eg. Tomcat) will construct the servlet object.

Now I want my servlet object to have Spring context beans injected into it.

I could write special Spring-specific code to put in my servlet to do get the Spring application context and find all the beans it needs, but that's cumbersome, and can't take advantage of Spring's autowiring.

I happened to watch Ben Alex, of Spring Security, give a great Introduction to Spring Security talk, where he mentioned that it's hard to give up Dependency Injection just to fit in with the web-app container. So instead of letting the web-app container create the Spring Security filter that one could declare in web.xml, he declares (at around 9'10") a DelegatingFilterProxy in web.xml, and gives it a name "springSecurityFilterChain". The clever DelegatingFilterProxy, when it is started by the container, loads the spring context, finds the bean with that name (ie. the bean called springSecurityFilterChain), and uses it as the actual filter.

I want to do the same thing, but instead of filters, I want to do it for the servlet itself. Luckily, Spring provides a class to do that: org.springframework.web.context.support.HttpRequestHandlerServlet. All I have to do is replace the servlet-class line in the web.xml above with that HttpRequestHandlerServlet. Then, when the servlet is required, the HttpRequestHandlerServlet will look in the application context for a bean called "stockPriceServiceImpl". To make sure we have one of those, the applicationContext.xml needs to include something like:

<bean id="stockPriceServiceImpl" class="com.google.gwt.sample.stockwatcher.server.StockPriceServiceImpl"/>
There are two problems with this. The first one is that the HtpRequestHandlerServlet can only hand requests to HttpRequestHandler objects. Now we know that the stockPriceServiceImpl is a servlet, and it can handle these requests, but it doesn't use the right method names. Now I should really make a new version of the HttpRequestHandlerServlet that can hand off requests to a javax.servlet.HttpServlet or even to a javax.servlet.GenericServlet. The GWT RemoteServiceServlet extends these two standard java classes. But that's for another day. For today, I simply made my StockPriceServiceImpl class implement HttpRequestHandler and added the following method:

@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) 
                         throws ServletException, IOException {
  service(request, response);
}

Now, spring is able to construct the stockPriceServiceImpl, but it still doesn't work. There's a NullPointerException that happens. I delved around in the source, and discovered that the stockPriceServiceImpl needed to locate its servletContext, so that it could load various serialization files. When the web-app constructs a servlet, it initialises it with the appropriate context. When Spring initialises it, it doesn't - by default. But, we can tell spring that we want it to do so, if the object implements ServletContextAware. So, I added that as an interface that the stockPriceServiceImpl implements, and then had to implement the setServletContext method. Unfortunately, the ServletContext object to be passed doesn't have a convenient constructor (it's an interface). So I created an anonymous class to provide the context, like this:

public void setServletContext(ServletContext servletContext) {
  final ServletContext context = servletContext;
  try {
    init(new ServletConfig() {
      @Override
      public String getInitParameter(String name) {
        return null;
      }
      @Override
      public Enumeration getInitParameterNames() {
        return null;
      }
      @Override
      public ServletContext getServletContext() {
        return context;
      }
      @Override
      public String getServletName() {
        return null;
      }
    });
  } catch (ServletException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
}

And now it seems to work. (It took me long enough to get here that I thought I'd better jot down what I did!) Next step, as I said, is to see if I can adapt HttpRequestHandlerServlet so that it can just instantiate a javax.servlet.GenericServlet directly without having to add those dodgy bits.

No comments: