Skip navigation.

Spring Integration

Bayeux Services Integration with Spring

Integration of CometD services with Spring is particularly interesting, since most of the times your Bayeux services will require other beans to perform their service.
Not all Bayeux services are as simple as the EchoService, and having Spring's dependency injection (as well as other facilities) integrated greatly simplifies development.

Below you can find 3 proposed strategies to integrate with Spring.

Late Spring Initialization

This strategy delays Spring initialization until the CometD servlet is initialized.
This strategy is suited for those cases where Spring beans can be initialized late because no other services or frameworks need Spring to be initialized upfront.

This strategy requires a bit of coding to glue Spring and Bayeux services together.
The reason to require glue code is twofold:

  • the Bayeux object is not created by Spring (but by the CometD servlet), so any bean that depends on the Bayeux object must be initialized only after the CometD servlet
  • the order of initialization of servlets and listeners in web.xml is not defined by the Servlet Specification (the order will only be fully specified in servlet 3.0)

So, in order to guarantee a portable initialization, a bit of code is required.

Below you can find the configuration files and the code needed to integrate with Spring.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.spring.LateSpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use a listener to initialize Spring. This listener completely replaces Spring's org.springframework.web.context.ContextLoaderListener, but it is based on the same classes used by it to perform context initialization at web application startup.
This means that the LateSpringBayeuxInitializer looks up by default Spring beans located in /WEB-INF/applicationContext.xml, or in locations specified by the servlet context init param contextConfigLocation, as per usual Spring configuration.

Second, Spring's applicationContext.xml file, defining the EchoService we defined earlier:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="echoService" class="com.acme.cometd.EchoService">
        <constructor-arg><ref bean="bayeux" /></constructor-arg>
    </bean>

</beans>

Note how there is a reference to the "bayeux" bean, which is not defined in any Spring XML file, but setup by LateSpringBayeuxInitializer, see below.

Lastly, the glue code to integrate Spring, the LateSpringBayeuxInitializer class referenced in web.xml above:

public class LateSpringBayeuxInitializer implements ServletContextListener, ServletContextAttributeListener
{
    private volatile ContextLoader loader;

    public void contextInitialized(ServletContextEvent event)
    {
    }

    public void contextDestroyed(ServletContextEvent event)
    {
        ContextLoader loader = this.loader;
        if (loader != null)
            loader.closeWebApplicationContext(event.getServletContext());
    }

    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();

            StaticListableBeanFactory factory = new StaticListableBeanFactory();
            factory.addBean("bayeux", bayeux);
            GenericApplicationContext bayeuxApplicationContext = new GenericApplicationContext(new DefaultListableBeanFactory(factory));
            bayeuxApplicationContext.refresh();

            loader = new BayeuxContextLoader(bayeuxApplicationContext);
            ApplicationContext applicationContext = loader.initWebApplicationContext(event.getServletContext());

            customizeBayeux(bayeux, applicationContext);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }

    protected void customizeBayeux(Bayeux bayeux, ApplicationContext applicationContext)
    {
    }

    private static class BayeuxContextLoader extends ContextLoader
    {
        private final ApplicationContext parentApplicationContext;

        public BayeuxContextLoader(ApplicationContext parentApplicationContext)
        {
            this.parentApplicationContext = parentApplicationContext;
        }

        @Override
        protected ApplicationContext loadParentContext(ServletContext servletContext) throws BeansException
        {
            return parentApplicationContext;
        }
    }
}

Note how you can override the customizeBayeux() method and further customize the Bayeux object by, for example, looking up a org.cometd.SecurityPolicy object from Spring's application context and set it on the Bayeux object. Or, in the same way, add your own Spring-configured Bayeux extensions to the Bayeux object.

Lazy Spring Initialization

This strategy allows for normal Spring initialization, but requires that Bayeux services are marked as lazy (and recursively all other beans/services that depend on Bayeux services), again to allow the CometD servlet to initialize properly.
This strategy is suited for those cases where other frameworks (for example, Struts2) require Spring to be initialized upfront.

This strategy requires a bit of coding to be able to access the Bayeux object from Spring configuration files.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.acme.cometd.spring.LazySpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use the normal Spring listener (ContextLoaderListener) to initialize Spring, but we also add another listener, LazySpringBayeuxInitializer.
The LazySpringBayeuxInitializer is the glue code that we need to make the Bayeux object accessible from Spring configuration files:

public class LazySpringBayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();
            BayeuxHolder.setBayeux(bayeux);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

The LazySpringBayeuxInitializer class makes use of another class, BayeuxHolder, that is very simple:

public class BayeuxHolder
{
    private static volatile Bayeux bayeux;

    public static void setBayeux(Bayeux bayeux)
    {
        BayeuxHolder.bayeux = bayeux;
    }

    public static Bayeux getBayeux()
    {
        return bayeux;
    }
}

At this point, we just need to write Spring's applicationContext.xml file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="com.acme.cometd.spring.BayeuxHolder" factory-method="getBayeux" lazy-init="true" />

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the Spring configuration file can have normal beans (such as nonLazyService), and how those beans can be injected in lazy beans such as our EchoService.
Also, note how we make use of the BayeuxHolder, which is lazy-initialized, to retrieve the Bayeux object stored by LazySpringBayeuxInitializer when the CometD servlet is initialized.

It is very important to note that the EchoService, which also is lazy-initialized, can be injected only in other lazy-initialized beans or in non-singleton beans (such as beans in scope prototype); if it is injected in singleton non-lazy beans, it will not be ready (because the CometD servlet is not yet initialized), and you will get an error.

Full Spring Initialization

This strategy initializes the Bayeux object directly in the Spring configuration file, and injects it in the servlet context, where it is picked up by the CometD servlet.
This strategy relies a bit more on CometD implementation details and may require changes if the implementation details change.
It may or may not require glue code, depending on other details of the application being developed (see below).
It requires also a bit more of discipline because the Bayeux object can now be configured in 2 places, the Spring configuration file and the web.xml file, and you don't want to split the Bayeux configuration in different places.

The web.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

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

</web-app>

Spring's applicationContext.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="org.cometd.server.continuation.ContinuationBayeux">
        <property name="timeout" value="15000" />
    </bean>

    <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
        <property name="attributes">
            <map>
                <entry key="org.cometd.bayeux">
                    <ref local="bayeux" />
                </entry>
            </map>
        </property> 
    </bean>

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the implementation class of the Bayeux object is now referenced in applicationContext.xml and how the Bayeux object is exported into the servlet context via Spring's ServletContextAttributeExporter.
This only creates an un-initialized Bayeux object; only after the CometD servlet has been initialized the Bayeux object is ready to be used.
Because of this, it is important that the echoService (and any other Bayeux service and, recursively, other dependent services) is lazy-initialized.

Since the echoService is lazy-initialized, there must be a way to tell Spring to actually instantiate and initialize it when the application first needs it.

If you are using another framework that rely on Spring for dependency injection, then this step is performed by the other framework. For example, if you use Struts2 with Spring support, you don't need to tell Spring to instantiate and initialize you CometD services, because Struts2 will recognize that there is the need to inject your CometD service (for example in a Struts2 action) and Struts2 will ask Spring to provide it.
Therefore, the configuration above is perfectly sufficient.

However, if you are not using other frameworks, you need to manually tell Spring to instantiate and initialize your CometD services.
The technique to do so is similar to what described for the non-Spring service integration: writing a configuration servlet or listener that will reference your lazy-initialized CometD services. Below an example:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab Spring's ApplicationContent
        ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

        // Trigger CometD service initialization
        context.getBean("echoService");
    }
    
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

And of course this configuration servlet must be mapped with a higher value for the load-on-startup element in web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

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

</web-app>