Friday, November 20, 2009

More about GWT and Spring

The following was contributed by Yaakov Chaikin (one of the authors of Core Servlets and JavaServer Pages) following some conversations we had regarding an earlier post of mine.
I am only too happy to post it here, with many thanks to Yaakov for taking the time for drafting it.




Let me share one more thing with the readers of Marco's blog. Marco mentioned that he was looking for a more general approach to wiring Spring beans into GWT RPC servlets, so I'd like to offer one.

First, let's explain the basic problem as I understand it. Servlets and Spring beans live in different worlds.

Servlets' lifecycle (when they are created, loaded into memory, initialized, invoked, etc) is controlled by the container (your server). Spring bean's lifecycle is controlled by Spring. So, what does it mean? Well, let me give you an example of what can't be done as a result of this, no matter how you decide to load the Spring's config file and initialize Spring application context:

public class SomeServlet extends HttpServlet {
  @Autowired
  private SomeServiceInterface someService;

  public void doGet(.....)
}

Why won't @Autowired work here? Because for it to work you need Spring to instantiate SomeServlet. Unfortunately, this is not (at least as far as I know, not yet) possible. It's the server's job (according to the spec) to control the lifecycle of SomeServlet and therefore it
gets to decide when to create this class and make it available for requests.

So, you are left with few choices here. Fortunately, Spring helps you out quite a bit here. First, let's look at a way to load Spring configuration without having to write any code. For that, use the
following configuration in your web.xml:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:/config.xml<</param-value>
</context-param>

<listener>
  <listener-class>
     org.springframework.web.context.ContextLoaderListener
  </listener-class>
</listener>

Now, you can use the following code in just about any web component that has access to the ServletContext (like a servlet) and pull out the Spring context:

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
...
...
ServletContext servletContext = getServletContext();
WebApplicationContext springContext =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
...

Back to GWT... So, if you want to stop there, you can use the code above in your GWT RPC servlet's init() method (remember, init() will be called only once per servlet and there is only one instance of the servlet ever created by the container, handling all requests in a different thread):

public class MyGWTServlet extends RemoteServiceServlet implements MyService {
  private SomeServiceInterface someService;

  public void init() {
    ServletContext servletContext = getServletContext();
    WebApplicationContext springContext =
        WebApplicationContextUtils.getWebApplicationContext(servletContext);

    someService = (SomeServiceInterface)
        springContext.getBean("someServiceBeanId");
  }
  ...
  ...
}

In my example, "someServiceBeanId" better be configured as a singleton and be thread safe or we would have a serious multi-threading problem on our hands.

However, personally, I didn't want to stop here since I really like the idea of @Autowired together with @Service, @Component, etc.
One huge reason for this is that I use these in such a way that when I stand up my application I will know right away whether or not I messed something up in my code as far as using the Spring configured beans.

For an example of how things can go wrong, consider the servlet code above. Say, I mistyped "someServiceBeanId" and spelled it 'id' (with the lower case i). What would happen when I deploy my web application? Nothing!

Everything would be just fine: no warnings, no errors. I start using my app and everything is still fine. That is until I happen to use some functionality that happens to use that particular GWT RPC servlet. Only then will I be surprised to see a NoSuchBeanDefinitionException. Personally, I'd rather know at deployment time that something is messed up. And the fact that I deploy my web app regularly during development will point out the problem rather quickly.

So, here is the solution that I use:

First, create a SpringRegistry class which looks roughly like this:

@Component
public class SpringRegistry {
public static final String KEY = "springRegistry";

@Autowired
private SomeServiceInterface someService;

@Autowired
private SomeOtherServiceInterface someOtherService;

@Autowired
...

public SomeServiceInterface getSomeServiceInterface() {
return someService;
}

public SomeOtherServiceInterface getSomeOtherServiceInterface() {
return someOtherService;
}

...
}


Now that we have a central bean that can autowire our services, we can define another ServletContextListener to populate that bean in one place. The code for that would look roughly like this:

public class SpringRegistryInitializer implements ServletContextListener {
  /**
   * Spring framework's autogenerated name for the SpringRegistry

      *   component define above.
   */

  private static final String SPRING_REGISTRY_CONFIG_NAME = "springRegistry";

  @Override
  public void contextInitialized(ServletContextEvent event) {
    ServletContext servletContext = event.getServletContext();
    WebApplicationContext springContext =
        WebApplicationContextUtils.getWebApplicationContext(servletContext);

    // Manually retrieve spring registry from spring context
    SpringRegistry springRegistry = (SpringRegistry)
        springContext.getBean(SPRING_REGISTRY_CONFIG_NAME);


    // Store SpringRegistry in servlet context for all web components
    // to be able to retrieve it
    servletContext.setAttribute(SpringRegistry.KEY, springRegistry);
  }

  @Override
  public void contextDestroyed(...) {}
}

ServletContextListener will now make sure that we are autowiring all of my services right from web application initialization (which is before any requests can be accepted by the spec.)

However, we must now remember to configure our new listener in web.xml and we must place our configuration somewhere after our configuration of Spring's ContextLoaderListener.
This is because our listener depends on the Spring context to be already initialized and according to the servlet spec the listeners will be executed in the order they are defined in web.xml.

If you don't want to worry about this condition, you can always do the Spring context loading yourself inside that same SpringRegistryInitializer, but it seems cleaner to me to use the already Spring provided SpringContextLoaderListener.
So, here is how our web.xml will look now:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:/config.xml<</param-value>
</context-param>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener>
  <listener-class>yourpackage.SpringRegistryInitializer</listener-class>
</listener>

Finally, the code in our GWT RPC servlet can look like this:
public class MyGWTServlet extends RemoteServiceServlet implements MyService {
private SomeServiceInterface someService;

public void init() {
SpringRegistry registry = (SpringRegistry)
getServletContext().getAttribute(SpringRegistry.KEY);

someService = registry.getSomeService();
}
...
...
public void doSomeOperation(....) { //use someService here }
}
Note, once again, that someService better be configured as a singleton and be thread safe or we would have a serious multi-threading problem on our hands.
Also, note that this approach gives you a nice clean separation between Spring and your web application code.

In your servlet or other web component's code you are no longer even retrieving the Spring configured bean through using using the bean's configured ID. It's all hidden from your servlet code inside the SpringRegistry class.

If, for whatever reason, you don't like or are not able to use @Autowired, etc., you can still use this approach and get the benefits of making sure you that if you make a mistake, it will be clear at the first time you deploy to a web container (server).
Just remove all that autowired stuff and inside your SpringRegistry provide setters for the services and use Spring SETTER injection in your Spring config file.

You'll get the same effect.

1 comment:

  1. Hi Marco,

    Here is an EVEN easier way of accomplishing the whole thing:

    In your servlet, create a setter for whatever you want to inject like this:

    @Required
    @Autowired
    public void setFoo(Foo foo) {
    // Assumes you declared foo as a private instance variable
    this.foo = foo;
    }

    Now, in your servlet's init method, do this:
    public void init(ServletConfig config) throws ServletException {
    WebApplicationContext context = WebApplicationContextUtil.getRequiredWebApplicationContext(config.getServletContext());
    AutowireCapableBeanFactory beanFactory = context.getAutowireCapableBeanFactory();
    beanFactory.autowireBean(this);
    }

    and you are done!
    I didn't know about the AutowireCapableBeanFactory at the time I wrote the original post to you.
    :-)

    Yaakov.

    ReplyDelete