Context loader in Spring

on waitingforcode.com

Context loader in Spring

Like we already know, application context is the container where Spring-managed beans live. But the question that we didn't ask, is how this context is created ? We'll reply to this question in this article.

In the first part we'll see what is context loader in case of Spring's application context. At the second part we'll see the code of this loader. The last part will be destined to writing of our own loader. Before continue the reading, please note that the loader will be analyzed in terms of web application and dispatcher servlet.

All cases are explained for Spring 4.0.1 version.

What is Spring's context loader ?

The name of this component is very clear and as you can guess, context loader is in charge of constructing application context. It's represented as instance of org.springframework.web.context.ContextLoaderListener which extends ContextLoader class from the same package. Both implement javax.servlet.ServletContextListener interface. This interface is designed to receive the notifications about changes in servlet context. Its implementations can receive these notifications only when they are registered in deployment descriptor (WEB-INF/web.xml).

In the case of Spring web application, context loader is invoked at servlet context creation. After this event, it initializes the root web application context. Root is the very important world because at the launch time, two or more contexts can be created. The first one, and the most important, defines whole beans life space and is called application context. The other is servlet application context which contains the elements more web-oriented, as controllers or view resolvers. At this occasion, remember that context from servlet is the children of root application context. It means that servlet inherits all beans from root application context. It's the reason why you can define some common resources (for example: services) in root configuration file and share it through two different servlets. But in the other side, root application context can't reach servlet specific beans.

We can resume the role of context loader to two things:
- tie root web application context into dispatcher specific context
- automate context creation (programmer doesn't need to code anything to make context working)

Spring's context loader in detail

We've learned the role of context loader. Now, we can approach this aspect more in details. The web context loader classes are located inside org.springframework.web.context package. The main class is ContextLoaderListener that extends ContextLoader class. Both are implementing ServletContextListener interface.

The method invoked at context creation is public void contextInitialized(ServletContextEvent event). It calls ContextLoader's initWebApplicationContext method by passing to it received servlet context (got from event parameter). The first operation made by this second method is to check existence of another root context. If at least another one is present, an IllegalStateException is thrown and initialization fails. Otherwise, it continues by initializing org.springframework.web.context.WebApplicationContext instance. If initialized instance implements ConfigurableWebApplicationContext interface, the loader sets some services (parent context, application context, servlet context etc.) before setting current application context and preparing beans through context's refresh() method, already presented in the article about application context in Spring.

The second method present in ContextLoaderListener is public void contextDestroyed(ServletContextEvent event). It's invoked every time when loader's context is shutting down. This method makes two things:
- through closeWebApplicationContext() from ContextLoader, it closes the application context. The context closing is accomplished thanks to ConfigurableWebApplicationContext close() method. The context destroying is translated by beans and bean factory desctruction.
- invokes ContextCleanupListener.cleanupAttributes(event.getServletContext()) that will find all objects of the current servlet context implementing org.springframework.beans.factory.DisposableBean interface. After, theirs destroy() methods will be invoked to destroy no more used beans.

Implementing context loader in Spring web application

Imagine that you want to share one information between all users of your system. You can do that with a classical definition, but also with using of your own context loader. It'll be the first point presented in our sample code. The other one will concern multiple contexts. Our application will handle both, guest and connected users. But theirs pages haven't the same URL model. The connected users will be able to access the same pages as guests and all pages reserved to them. These reserved pages end with .chtml extension. As you can guess, they won't share the same information. More, for both we'll specify two separated servlet contextes. You'll see that because of it, connected users won't share the same beans as the guests.

We'll start by prepare a deployment web.xml file. It won't be described above. All explanations are included into file's content as the comments:



  
    guest
    org.springframework.web.servlet.DispatcherServlet
    1
  

  <-- guest is the default servlet -->
  
    guest
    /
  

  
    contextConfigLocation
    
    /WEB-INF/applicationContext.xml
    
  
  <-- Customized listener which will put some personnalized data into servlet's context -->
  
    com.mysite.servlet.CustomizedContextLoader
  

  
    connected
    org.springframework.web.servlet.DispatcherServlet
    2
  

  
    connected
    *.chtml
  

Specific servlet's bean configuration files are almost the same. The only difference is that connected-servlet.xml contains a definition of one bean not shared with guest servlet. This bean's name is secretData:


  
  


 

The content of mysterious bean is mainly composed by setters and overriden toString method:

public class SecretData {

  private String question;
  private String answer;

  public void setQuestion(String question) {
    this.question = question;
  }

  public void setAnswer(String answer) {
    this.answer = answer;
  }

  @Override
  public String toString() {
    return "SecretData {question: "+this.question+", answer: "+this.answer+"}";
  }
	
}

Other Java code is not complicated too. In CustomizedContextLoader we override the contextInitialized method to put there shared servlet's context attribute, called webappVersion. This attribute is a random number to prove that the loader for root application context is invoked only once:

public class CustomizedContextLoader extends ContextLoaderListener  {

  @Override
  public void contextInitialized(ServletContextEvent event) {
    System.out.println("[CustomizedContextLoader] Loading context");
    // this value could be read from data source, but for the simplicity reasons, we put it statically
    // number is random because we want to prove that the root context is loaded only once
    Random random = new Random();
    int version = random.nextInt(100001);
    System.out.println("Version set into servlet's context :"+version);
    event.getServletContext().setAttribute("webappVersion", version);
    super.contextInitialized(event);
  }
}

After that, we pass to controller which will handle both, guests and connected users, URLs:

@Controller
public class TestController    {

  @Autowired
  private ApplicationContext context;

  @RequestMapping(value = "/test.chtml", method = RequestMethod.GET)
  public String test(HttpServletRequest request) {
    LOGGER.debug("[TestController] Webapp version from servlet's context :"+request.getServletContext().getAttribute("webappVersion"));
    LOGGER.debug("[TestController] Found secretData bean :"+context.getBean("secretData"));
    return "test";
  }

  @RequestMapping(value = "/test.html", method = RequestMethod.GET)
  public String guestTest(HttpServletRequest request) {
    LOGGER.debug("[TestController] Webapp version from servlet's context :"+request.getServletContext().getAttribute("webappVersion"));
    LOGGER.debug("[TestController] Found secretData bean :"+context.getBean("secretData"));
    return "test";
  }
}

To test this controller, firstly enter to http://localhost:8080/test.chtml and after to http://localhost:8080/test.html. Now, pass directly into yours logs to check what happened:

[CustomizedContextLoader] Loading context
Version set into servlet's context :38023
// ... test.chtml
[TestController] Webapp version from servlet's context :38023
[TestController] Found secretData bean :SecretData {question: How old are you ?, answer: 33}
// ... test.html
[TestController] Webapp version from servlet's context :38023
3 avr. 2014 14:01:02 org.apache.catalina.core.StandardWrapperValve invoke
GRAVE: Servlet.service() for servlet [guestServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'secretData' is defined] with root cause
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'secretData' is defined
	  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:638)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1159)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:282)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:273)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
  at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:973)
  at com.mysite.controller.TestController.guestTest(TestController.java:114)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
  at java.lang.reflect.Method.invoke(Unknown Source)
  at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:214)
  at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
  at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:748)
  at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
  at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:931)
  at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:822)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:807)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
  at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:240)
  at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:164)
  at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:462)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
  at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:562)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:395)
  at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:250)
  at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:188)
  at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
  at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at java.lang.Thread.run(Unknown Source)

You can observe the points defined at the begin of this part. Firstly, one information (webapp version) is put inside servlet context and inherited by both servlet contexts. The second point is bean's visibility. Guest's servlet doesn't see secretData bean because it was defined only inside configuration for connected users (connectedServlet-servlet.xml).

This time, we learned about context loader in Spring web application. The first part explained two main roles of this loader: assemble root context with the servlet context and automate context creation. Next, we discovered code's site of context loader with all implemented interfaces and method. The last part was destined to extend native context loader and make some tests of beans and servlet's attributes inheritance.

Share on: