Spring WebApplicationContextUtils

By on Feb 17, 2013 in eng | 3 comments

Share On GoogleShare On FacebookShare On Twitter

Last week I tried to find a way to access a spring bean from ServletContextListener. There are some interesting details that I want to log in here for later reference.

Let’s look at the web.xml configurations of an example web application.

<web-app version="2.5">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>app.AppContextListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>
</web-app>

There are 2 WebApplicationContext here.

The Servlet web context is a child of root web context. Child context can get bean defined in parent context but not vice versa. I have defined a bean in each context; a bean named globalService in root web context and a bean named appService in servlet web context. The problem happens when I want to access appService bean in ServletContextListener

public class AppContextListener implements ServletContextListener{

    private static final String DISPATCHER_SERVLET_CONTEXT_ATTR_NAME =
            "org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher";

    public void contextInitialized(ServletContextEvent sce) {
        ServletContext stx = sce.getServletContext();

        WebApplicationContext rootContext = getRootWebApplicationContext(stx);
        GlobalService globalService = rootContext.getBean(GlobalService.class);
        globalService.start();

       //The servletWebCtx is null.
        WebApplicationContext servletWebCtx = getDispatcherServletWebApplicationContext(stx);
    }

    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext stx = sce.getServletContext();

        WebApplicationContext rootWebCtx = getRootWebApplicationContext(stx);
        GlobalService globalService = rootWebCtx.getBean(GlobalService.class);
        globalService.stop();

        WebApplicationContext servletWebCtx = getDispatcherServletWebApplicationContext(stx);
       //This line will result in IllegalStateException: BeanFactory not initialized or already closed
        AppService appService = servletWebCtx.getBean(AppService.class);
    }

    public WebApplicationContext getRootWebApplicationContext(ServletContext stx){
        return WebApplicationContextUtils.getWebApplicationContext( stx );
    }

    public WebApplicationContext getDispatcherServletWebApplicationContext(ServletContext stx){
        return WebApplicationContextUtils.getWebApplicationContext(stx,
                                                DISPATCHER_SERVLET_CONTEXT_ATTR_NAME);
    }
}

In the web.xml, I have placed AppContextListener below ContextLoaderListener so the root web context has already been initialized (by ContextLoaderListener) by the time the contextInitialized() method get called. I can use WebApplicationContextUtils to get the root web context for accessing the globalService bean

Accessing bean in Serlvet web context

public WebApplicationContext getRootWebApplicationContext(ServletContext stx){
        return WebApplicationContextUtils.getWebApplicationContext( stx );
 }

The problem is that I can’t access appService bean from the root web context. I need to find a way get the Servlet web context. Actually, what WebApplicationContextUtils does is just retrieving a ServletConext attribute with the name “org.springframework.web.context.WebApplicationContext.ROOT”. The ContextLoaderListener has registered the root web context as a Servlet attribute using this name. I have looked at Spring source code and found that my Servlet web context should be in the name “org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher”.

The Spring source code related to the attribute name is shown below

public abstract class FrameworkServlet extends HttpServletBean {
    public static final String SERVLET_CONTEXT_PREFIX =
                         FrameworkServlet.class.getName() + ".CONTEXT.";

    public String getServletContextAttributeName() {
        return SERVLET_CONTEXT_PREFIX + getServletName();
    }
}
 

So I look it up with the overloaded method of WebApplicationContextUtils.

public WebApplicationContext getDispatcherServletWebApplicationContext(ServletContext stx){
        return WebApplicationContextUtils.getWebApplicationContext(stx,
                                                DISPATCHER_SERVLET_CONTEXT_ATTR_NAME);
 }

The above method return null when I call it in my contextInitialized() method. This is because the contextInitialized() method will be called before any Servlet initialization. At this point, the DispatcherServlet hasn’t been initialized yet so there is no Servlet attribute associated with the name.

The above method will return our Servlet web context as expected when I call it in contextDestroyed() method. But I will still unable to access my appService bean here. The getBean() method will throw error saying “IllegalStateException: BeanFactory not initialized or already closed”. This is because the contextDestroyed() method will be called after all Servlet instances have been destroyed. At this point, the DispatcherServlet has already closed the web context so it can’t be used in the contextDestroyed() method

I may need to move the appService bean to root web context to be able to access it in ServletContextListener

3 Comments

  1. Taylor York

    September 19, 2014

    You saved me!
    I could not find a way to get the dispatch servlet context from the root context. You correctly found that it has the name org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher (or whatever you servlet name is).

    Thank you very much for posting this!

  2. zhannk

    April 28, 2015

    absolutely, the lifecycle of webapplicationcontext, dispatcherservletwebapplicationcontext have work as expected in web container.

  3. chatchai

    May 5, 2015

    Thanks very much zhannk and Taylor. It is a shame that I haven’t been logged into this blog for a years so I miss your comments. I am starting to blog again.

Trackbacks/Pingbacks

  1. Get Spring Dispatcher servlet Spring context from ServletContext | M.V.M-n. - […] P.S. More info on the subject can be found here. […]

Submit a Comment

Your email address will not be published. Required fields are marked *