Monday, July 6, 2009

Spring 3.0 Cheatsheet: Application Context

This is the first in a series of posts around patterns and recommendations for Spring 3.0.


Use a Facade to provide Application Context objects

We need to use a Spring ApplicationContext object to retrieve dependency-injected objects. But we want to encapsulate our use of Spring into a single point of change, so we use a facade to do this:

public class SpringFacade {

    private static FileSystemXmlApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext(String fileName) {
        if (applicationContext == null) {
            applicationContext = FileSystemXmlApplicationContext(fileName);
        }
        return SpringFacade.applicationContext;
    }
}
.....
Service svc = (MyService)SpringFacade.getApplicationContext().getBean("myService");

Use a Factory to support both Standalone and Web Application Context objects

While we want a plain vanilla standalone Application Context in a test environment, we need a web-aware flavor in a web deployment. This has the following advantages:

  1. web-centric scopes (i.e. request and session - see Spring reference doc section 4.4.4)
  2. multiple lifecycle events after initial program startup (section 4.8.3)
  3. web-centric resources like the ServletContext (5.4)
  4. view resolvers (17.2.1)
  5. Spring-centric dependency injection in a JSF environment (19.3)
  6. and etcetera

So we need a factory mechanism to decide at runtime which way to go - assuming of course we want to test our web classes, which of course we do. After all, one of Spring's advantages is loose coupling to facilitate testability.

Now, while it's true that I could probably figure out a way (aka a hack) to convince a test class to use a web-aware Spring context, and not have to bother with this factory nonsense, I'd rather spend my time figuring out a way to do things that have good design sensibilities.

First, use a ServletContextListener to cache a web-aware implementation:

public class SpringInitializer implements ServletContextListener {

    private static WebApplicationContext springContext;
    private Logger logger = Logger.getLogger(getClass().getSimpleName());

    public void contextInitialized(ServletContextEvent event) {
       logger.info("In a web environment: get a Spring web application context");
       springContext = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        springContext = null; // facilitate garbage collection to support hot redeployments
    }

    public static ApplicationContext getApplicationContext() {
        return springContext;
    }
}

Next, configure the web deployment descriptor to (1) specify the location of the config file(s), (2) load the context listener that will access those locations and (3) load the listener (Spring reference manual, section 4.8.5):
<?xml version="1.0" encoding="UTF-8"?>
 <web-app version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/myBeans.xml, /WEB-INF/yourBeans.xml, /WEB-INF/**/*Context.xml</param-value>
    </context-param>
.........
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.mybiz.SpringInitializer</listener-class>
    </listener>
.........
  </web-app>

Now the facade takes on factory-type responsibilities to decide dynamically which context to return:
public class SpringFacade {
    private static FileSystemXmlApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
       ApplicationContext context = SpringInitializer.getApplicationContext();
       if (context == null) {
           factory.logger.info("Returning class-path-XML app context");
           context = new ClassPathXmlApplicationContext("WEB-INF/beans.xml");
       } else {
          factory.logger.info("Returning web app context");
       }
    }
}



Next, I'll post an approach to using iBatis with Spring.

2 comments:

  1. Very useful post, thank you! h

    ReplyDelete
  2. Thanks very nice, exactly what we needed to know!

    ReplyDelete