Question

I need to execute a certain procedure once my spring web application starts all of its beans. For this I created a ApplicationListener<ContextRefreshedEvent>.

However, when I run the application it gets called several times (as we have contexts for different namespaces e.g. mvc-servlets, etc.) but I need this particular listener to be called only once and when all the context where properly initialized.

Is there a way to achieve what I'm trying to do?

I'm using spring 3.1.0.RELEASE.

Was it helpful?

Solution

Yes, there is a way but it can be a bit tricky. The child contexts you are talking about are probably contexts started for a DispatcherServlet. If you have more than one of those, you'll get one context per dispatcher servlet.

Spring delegates to the container for these so there is no single point of management with regards to initialization. First, the root application context is initialized and then the various servlets are initialized by the container. For each of those, another context might kick in.

Fortunately, the servlet spec comes to the rescue with the load-on-startup parameter

The load-on-startup element indicates that this servlet should be loaded (instantiated and have its init() called) on the startup of the web application. The optional contents of these element must be an integer indicating the order in which the servlet should be loaded. If the value is a negative integer, or the element is not present, the container is free to load the servlet whenever it chooses. If the value is a positive integer or 0, the container must load and initialize the servlet as the application is deployed. The container must guarantee that servlets marked with lower integers are loaded before servlets marked with higher integers. The container may choose the order of loading of servlets with the same load-on-start-up value.

So you should do two things basically:

  1. Specify a load-on-startup element on each servlet and make sure that one has a distinctive, higher number
  2. Make sure that your listener catch the right event

Example

Consider the following (simplified) web.xml definition

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

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

    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>  
    <servlet>
        <servlet-name>anotherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/first/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>anotherServlet</servlet-name>
        <url-pattern>/second/*</url-pattern>
    </servlet-mapping>

</web-app>

Detecting the right context

This setup will lead to 3 calls to the listener. In this case, anotherServlet is the last in your chain, so you can identify this as follows:

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (context instanceof ConfigurableWebApplicationContext) { // sanity check
        final ConfigurableWebApplicationContext ctx =
                (ConfigurableWebApplicationContext) event.getApplicationContext();
        if ("anotherServlet-servlet".equals(ctx.getNamespace())) {
            // Run your initialization business here
        }
    }
}

If you're interested to understand where this is coming from, have a look to FrameworkServlet#initServletBean.

Not that you can still throw an exception at this point and this will still prevents the application to deploy properly.

Ordering

Finally, you can also make sure that your event is processed last in case there are multiple listeners registered for that particular event. To do this, you need to implement the Ordered interface:

public class YourListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) { }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    } 
}

OTHER TIPS

You'll have to be a little more clear about your context hierarchy, but if you have the typical setup of root context and servlet context, declare the ApplicationListener bean in the servlet context.

The root context will be refreshed by the ContextLoaderListener. The servlet context will be refreshed by the DispatcherServlet using the root context as its parent. When this refresh is done, your ApplicationListener will receive the event.

Hmm, in such cases I always use javax.annotation.PostConstruct annotation on the public method of my Spring component.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top