ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Migrating to Spring
Pages: 1, 2, 3

Changing the Web Tier: SpringMVC

Now that I have Spring-ified the infrastructure and data code, it's time to tackle the web tier. The central class here is Spring MVC's Controller interface:



package org.springframework ...

interface Controller {

  public ModelAndView handleRequest(
    HttpServletRequest request ,
    HttpServletResponse response
  ) throws Exception ;
        
}

The entry point, handleRequest(), has a signature similar to a Struts Action class or even a homegrown page controller: the framework provides the servlet request and response objects, and the controller implementation returns the result of some business logic (the model) and an indicator of how to display it (the view). The model of ModelAndView is a Map of business objects or other data that will be manipulated at the view tier. In the sample code, the view is a JSP, but Spring MVC also supports Velocity templates and XSLT.

The sample app's servlet page controllers are so simple that the power of Spring MVC doesn't shine through. Hence, the updated page controllers are just shy of most textbook examples.

The old page controllers used a similar strategy of passing a Map of objects from the servlets to the JSPs. As a result, I don't have to make any changes to the JSP for this to work. Nor do I have to use any Spring-specific tag libraries.

There's still work to do in the new, Spring-based controllers, though: they invoke the ObjectRegistry to look up the DAO. In a pure Spring world, the DAO would be assigned to the controllers in the web-specific XML config file, here WEB-INF/SpringMVC-servlet.xml.

That sounds easy enough, right? I'll just add the object registry's Spring config file--spring-objectregistry.xml--to web.xml like so:

<context-param>
  <param-name>
    contextConfigLocation
  </param-name>
  <param-value>
    classpath*:spring-objectregistry.xml
  </param-value>
<context-param>

Now the objects specified in spring-objectregistry.xml will be visible to those in WEB-INF/SpringMVC-servlet.xml.

Doing that creates another problem: the MVC layer loads the files spring-objectregistry.xml and SpringMVC-servlet.xml into one ApplicationContext. The object registry loads spring-objectregistry.xml into a different ApplicationContext. These two ApplicationContexts live in their own world and cannot, by default, see each other's bean definitions.

In turn, the objects defined in spring-objectregistry.xml will be loaded twice: once by the MVC layer, then by the object registry. In turn, then, my "singletons" in spring-objectregistry.xml are not really singletons any more. In the sample code these are all stateless objects, but in a larger app some singletons may indeed hold state. If I don't load those objects once and only once, I will have synchronization problems. That, and if such an object were to perform some one-time, resource-intensive operations, my app's performance would suffer.

My first reaction was to refactor the code such that ObjectRegistry wasn't needed. That's a simple undertaking, but this was a learning exercise. To simulate a larger project--one in which removing the registry would not be such a one-off task--I decided to stick with it and figure out how make the two worlds work.

In short, I needed some way to expose the objects of ObjectRegistry (spring-objectregistry.xml) to those used at the web tier (WEB-INF/SpringMVC-servlet.xml). Spring's solution is the BeanFactoryLocator, which is a registry of ApplicationContexts. I can tell both the object registry and the MVC layer to load the common objects from BeanFactoryLocator.

First, I have to change ObjectRegistry, such that it doesn't explicitly load spring-objectregistry.xml:

import org.springframework....BeanFactoryLocator ;
import org.springframework.
    ...ContextSingletonBeanFactoryLocator ;
import org.springframework.
    ...BeanFactoryReference ;

// this was an ApplicationContext before
private final BeanFactoryReference _singletons ;

private ObjectRegistry(){

  // ContextSingletonBeanFactoryLocator loads
  //   contents of beanRefContext.xml

  BeanFactoryLocator bfl =
    ContextSingletonBeanFactoryLocator
      .getInstance() ;

  BeanFactoryReference bf =
    bfl.useBeanFactory( "OBJ_REGISTRY_DEFS" );
                         
  _singletons = bf ;

}

public Object get( final String key ){
  return( _singletons.getFactory().getBean( key ) ) ;
}

This code replaces the ApplicationContext with a BeanFactoryReference, named OBJ_REGISTRY_DEFS, that it pulls from a BeanFactoryLocator. In turn, OBJ_REGISTRY_DEFS is defined in a file called beanRefContext.xml:

<beans>

  <bean
    id="OBJ_REGISTRY_DEFS"
    class="...ClassPathXmlApplicationContext"
  >

    <constructor-arg>
      <list>
        <value>spring-objectregistry.xml</value>
      </list>
    </constructor-arg>

  </bean>

</beans>

The bean named OBJ_REGISTRY_DEFS is really an ApplicationContext backed by the original object registry config file, spring-objectregistry.xml. The calls to getBean() on the BeanFactoryReference just pass through to the underlying ApplicationContext.

That takes care of the ObjectRegistry itself. To make the web tier use OBJ_REGISTRY_DEFS as well--that is, to make the objects defined therein visible to the web-tier Spring config, SpringMVC-config.xml--takes some extra entries in web.xml:

  <context-param>
     <param-name>
       parentContextKey
     </param-name>
     <param-value>
       OBJ_REGISTRY_DEFS
     </param-value>
  </context-param>

  <context-param>

     <param-name>
       locatorFactorySelector
     </param-name>

     <param-value>
       classpath*:beanRefContext.xml
     </param-value>
  </context-param>

The first entry tells the web-tier Spring configuration that it should call on the BeanFactoryReference named OBJ_REGISTRY_DEFS for any objects it cannot find. The second tells the framework to load any file named beanRefContext.xml from the classpath.

Now the objects defined in spring-objectregistry.xml are visible to the web-tier objects in SpringMVC-config.xml. This means I can slowly phase out ObjectRegistry, instead of trying to make such a far-reaching change in one step.

Ugly? Yes. Glue code? Yes. A means to migrate your app to Spring, when you already have your own singleton registry? Absolutely. Now the app is shielded from future refactorings: removal of the ObjectRegistry (and its explicitly-loaded ApplicationContext) should only affect client code of ObjectRegistry.

A point of warning, however: even the Spring docs note that BeanFactoryLocator isn't for everyday use. It should be used for migration projects such as this. If you intend to use Spring for a new app, by comparison, your design should account for proper IoC injection from the start.

The Round-up

Comparing the original app to its successor, I first notice the difference in size; there's less of my code, which should simplify debugging. Furthermore, letting Spring handle so much of the object instantiation and dependency tracking means I have one less worry as the system grows. Injection, via IoC, should also simplify my tests--I can swap out one DAO implementation for another by changing an XML file.

Taking a higher-level view, I pondered the specific Spring components I used. The object lookup and JDBC templates looked familiar. In a sizable project, it's common to have framework code to handle object lookups or database connectivity. That's what makes Spring so useful--it feels natural. We've seen it before, it's just that now we don't have to (re)write it as we move from project to project. As an added bonus, the Spring crew can focus on enhancing their product and we worry about our own.

Another benefit to adopting Spring is that it's not an all-or-nothing endeavor. I was able to apply Spring to each application tier without affecting the others, so I could have stopped at any point along the way. If I had only wanted to leverage Spring's JDBC templates, for example, I could have changed my DAOs and left the rest of my app untouched.

While I'm late to the Spring party, I'm certainly pleased to be in attendance.

Resources

Q Ethan McCallum grew from curious child to curious adult, turning his passion for technology into a career.


Return to ONJava.com.