CDI injection is not working in Servlets

Sebastian S. picture Sebastian S. · Jul 31, 2012 · Viewed 40.1k times · Source

I really need your help. I'm working on this problem for weeks or months now. I apologize for the long post, but I want to explain the problem and my settings as precisely as possible.

My JAVA EE 6 web application mainly contains 3 Servlets: a javax.faces.webapp.FacesServlet, a StreamingServlet extending javax.servlet.http.HttpServlet and a ClientServlet extending org.eclipse.jetty.websocket.WebSocketServlet. I want to use CDI with these Servlets but it only works for the FacesServlet, to be more precisely, for the Beans the JSF actions are routed to. CDI injection doesn't work with the other 2 Servlets.

The setup for my project is as follows: Maven 3.0.4, Jetty 8.1.4 (jetty-maven-plugin), MyFaces 2.1.6, Weld 1.1.8.

The file pom.xml contains the dependencies for CDI:

...
<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>1.1.EDR1.2</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>jsr250-api</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>org.jboss.weld.servlet</groupId>
    <artifactId>weld-servlet</artifactId>
    <version>${weld.version}</version>
    <scope>runtime</scope>
</dependency>
...

The file src/main/webapp/WEB-INF/beans.xml only contains an empty beans element for activating CDI:

<?xml version="1.0" encoding="UTF-8"?>
<beans 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/beans_1_0.xsd">
</beans>

The file scr/main/webapp/WEB-INF/jetty-env.xml contains the Jetty specific configuration for getting the bean manager via JNDI:

<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">

<Configure id="webAppCtx" class="org.eclipse.jetty.webapp.WebAppContext">
    <New id="BeanManager" class="org.eclipse.jetty.plus.jndi.Resource">
        <Arg>
            <Ref id="webAppCtx" />
        </Arg>
        <Arg>BeanManager</Arg>
        <Arg>
            <New class="javax.naming.Reference">
                <Arg>javax.enterprise.inject.spi.BeanManager</Arg>
                <Arg>org.jboss.weld.resources.ManagerObjectFactory</Arg>
                <Arg />
            </New>
        </Arg>
    </New>
</Configure>

The file src/main/webapp/WEB-INF/web.xml contains the servlet listener and the BeanManager reference for bootstrapping CDI:

...
<listener>
    <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>

<resource-env-ref>
    <description>Object factory for the CDI Bean Manager</description>
    <resource-env-ref-name>BeanManager</resource-env-ref-name>
    <resource-env-ref-type>javax.enterprise.inject.spi.BeanManager</resource-env-ref-type>
</resource-env-ref>
...

With these settings I always got the following error when running the application using mvn jetty:run (first line is most important):

2012-07-31 11:09:33,546 [main] ERROR org.jboss.weld.environment.jetty.JettyPost72Container - Unable to create JettyWeldInjector. CDI injection will not be available in Servlets, Filters or Listeners
java.lang.IllegalArgumentException: Cannot load class for org.jboss.weld.environment.jetty.WeldDecorator
    at org.jboss.weld.environment.servlet.util.Reflections.classForName(Reflections.java:58)
    at org.jboss.weld.environment.jetty.JettyPost72Container.initialize(JettyPost72Container.java:66)
    at org.jboss.weld.environment.servlet.Listener.contextInitialized(Listener.java:162)
    at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:764)
    at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:406)
    at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:756)
    at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:242)
    at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1234)
    at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:699)
    at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
    at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:256)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
    at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:167)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
    at org.eclipse.jetty.server.handler.HandlerCollection.doStart(HandlerCollection.java:224)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
    at org.eclipse.jetty.server.handler.HandlerWrapper.doStart(HandlerWrapper.java:90)
    at org.eclipse.jetty.server.Server.doStart(Server.java:262)
    at org.mortbay.jetty.plugin.JettyServer.doStart(JettyServer.java:65)
    at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
    at org.mortbay.jetty.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:511)
    at org.mortbay.jetty.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:364)
    at org.mortbay.jetty.plugin.JettyRunMojo.execute(JettyRunMojo.java:516)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:101)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:209)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:84)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:59)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.singleThreadedBuild(LifecycleStarter.java:183)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:161)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:320)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:156)
    at org.apache.maven.cli.MavenCli.execute(MavenCli.java:537)
    at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:196)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:290)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:230)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:409)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:352)
Caused by: java.lang.NoClassDefFoundError: org/eclipse/jetty/servlet/ServletContextHandler$Decorator
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:415)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:377)
    at org.jboss.weld.environment.servlet.util.Reflections.classForName(Reflections.java:51)
    ... 44 more
Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.servlet.ServletContextHandler$Decorator
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:415)
    at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:377)
    ... 56 more

The application was running but as the exception message states: CDI injection will not be available in Servlets.

A couple of days ago, I found the article Starting a CDI webapp from Maven with Weld-Servlet and Jetty Plugin and especially the section Last trick to allow injection in servlets. The author explains that Weld needs to decorate some Jetty internal classes in order to use the @Inject annotation in servlets and that we have to tell the Jetty classloader where to find this class in order to have the Weld servlet to decorate it.

I was waiting so long for this special information. So I added the file src/main/webapp/WEB-INF/jetty-context.xml:

<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">

<Configure id="webAppCtx" class="org.eclipse.jetty.webapp.WebAppContext">
    <Set name="serverClasses">
        <Array type="java.lang.String">
            <Item>org.eclipse.jetty.servlet.ServletContextHandler.Decorator</Item>
        </Array>
    </Set>
</Configure> 

I also updated the pom.xml in order to incorporate the file jetty-context.xml (last line):

...
<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>${jetty.version}</version>
    <configuration>
        <webApp>
            <contextPath>/${project.build.finalName}</contextPath>
        </webApp>
        <jettyEnvXml>src/main/webapp/WEB-INF/jetty-env.xml</jettyEnvXml>
        <contextXml>src/main/webapp/WEB-INF/jetty-context.xml</contextXml>
        ...

Now the above error disappeared. I was so happy for that short moment :)

In src/main/resources/log4j.properties I increased the log level for Weld:

...
log4j.logger.org.jboss.weld=DEBUG, stdout
log4j.additivity.org.jboss.weld=false
...

When starting the application I get the following Weld log messages:

...
2012-07-31 12:00:08,968 [main] INFO  org.jboss.weld.Bootstrap - WELD-000101 Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
2012-07-31 12:00:09,109 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000103 Enabled alternatives for Manager
Enabled alternatives: [] []
Registered contexts: [interface javax.enterprise.context.SessionScoped, interface javax.enterprise.context.ApplicationScoped, interface javax.enterprise.context.Dependent, interface javax.enterprise.context.RequestScoped, interface javax.enterprise.context.ConversationScoped, interface javax.inject.Singleton]
Registered beans: 0
: [] []
2012-07-31 12:00:09,109 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000104 Enabled decorator types for Manager
Enabled alternatives: [] []
Registered contexts: [interface javax.enterprise.context.SessionScoped, interface javax.enterprise.context.ApplicationScoped, interface javax.enterprise.context.Dependent, interface javax.enterprise.context.RequestScoped, interface javax.enterprise.context.ConversationScoped, interface javax.inject.Singleton]
Registered beans: 0
: []
2012-07-31 12:00:09,109 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000105 Enabled interceptor types for Manager
Enabled alternatives: [] []
Registered contexts: [interface javax.enterprise.context.SessionScoped, interface javax.enterprise.context.ApplicationScoped, interface javax.enterprise.context.Dependent, interface javax.enterprise.context.RequestScoped, interface javax.enterprise.context.ConversationScoped, interface javax.inject.Singleton]
Registered beans: 0
: []
2012-07-31 12:00:09,171 [main] INFO  org.jboss.weld.environment.jetty.JettyPost72Container - Jetty7 detected, JSR-299 injection will be available in Listeners, Servlets and Filters.
2012-07-31 12:00:09,265 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Implicit Bean [javax.enterprise.inject.spi.InjectionPoint] with qualifiers [@Default]
2012-07-31 12:00:09,265 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.DependentContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.util.MediaType] with qualifiers [@Any @Default]
2012-07-31 12:00:09,281 [main] WARN  org.jboss.weld.interceptor.util.InterceptionTypeRegistry - Class 'javax.ejb.PostActivate' not found, interception based on it is not enabled
2012-07-31 12:00:09,281 [main] WARN  org.jboss.weld.interceptor.util.InterceptionTypeRegistry - Class 'javax.ejb.PrePassivate' not found, interception based on it is not enabled
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.web.LoginBean] with qualifiers [@Any @Default]
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.http.HttpRequestContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.http.HttpSessionContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.web.CustomerBean] with qualifiers [@Any @Default @Named]
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.bound.BoundConversationContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.ApplicationContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.util.LocaleBean] with qualifiers [@Any @Default @Named]
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.bound.BoundRequestContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.SingletonContext
2012-07-31 12:00:09,281 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Implicit Bean [javax.enterprise.inject.Instance] with qualifiers [@Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.DataReader] with qualifiers [@Any @Default @Named]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.SessionBean] with qualifiers [@Any @Default @Named]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.ConnectionTable] with qualifiers [@Any @Default @Named]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.http.HttpConversationContext
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.servlet.ClientServlet] with qualifiers [@Any @Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.RequestContext
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-javax.enterprise.context.Conversation
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Implicit Bean [javax.enterprise.event.Event] with qualifiers [@Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.CustomerListBean] with qualifiers [@Any @Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.Customer] with qualifiers [@Any @Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.servlet.StreamingServlet] with qualifiers [@Any @Default]
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: org.jboss.weld.bean-flat-Built-in-org.jboss.weld.context.bound.BoundSessionContext
2012-07-31 12:00:09,296 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000106 Bean: Managed Bean [class de.telexiom.cardiom.SessionInfo] with qualifiers [@Any @Default]
2012-07-31 12:00:09,312 [main] DEBUG org.jboss.weld.Reflection - WELD-000601 interface javax.inject.Named is missing @Target. Weld will use this annotation, however this may make the application unportable.
2012-07-31 12:00:09,312 [main] DEBUG org.jboss.weld.Bootstrap - WELD-000100 Weld initialized. Validating beans
...

I use the Servlet 3.0 API, the StreamingServlet and the ClientServlet are configured via annotations. The problem is that the application scoped beans connectionTable and dataReader are not injected:

@WebServlet(value = "/stream", initParams = @WebInitParam(name = "maxIdleTime", value = "0"))
public class StreamingServlet extends HttpServlet
{
    @Inject
    private ConnectionTable connectionTable;
    ...

@WebServlet(value = "/push", loadOnStartup = 1, initParams = @WebInitParam(name = "maxIdleTime", value = "0"))
public class ClientServlet extends WebSocketServlet
{
    @Inject
    private ConnectionTable connectionTable;

    @Inject
    private DataReader dataReader;
    ...

Calling the (not) injected properties, I always get NullPointerExceptions. The problem also remains when configuring the Servlets via web.xml. I have no more ideas about what I am doing wrong. Reading all the articles about CDI in combination with Jetty and Maven, I wonder if I am the only one having these injection problems.

In my JSF bean class UserBean.java the injection works, I don't have to use setter methods for the properties connectionTable and dataReader to be initialized.

Using the output statements below in the DataReader constructor (and also in the ConnectionTable class)

@ApplicationScoped
public class DataReader implements Serializable
{
    ...
    public DataReader()
    {
        System.out.println("DataReader.DataReader(): " + this);
    }
    ...

I wonder about the 2 DataReader instances which show up on stdout:

...
2012-07-31 14:03:04,843 [qtp5435124-17] DEBUG org.jboss.weld.JSF - WELD-000504 Resuming conversation with id null
31.07.2012 14:03:05 org.apache.myfaces.util.ExternalSpecifications isUnifiedELAvailable
INFO: MyFaces Unified EL support enabled
DataReader.DataReader(): DataReader$Proxy$_$$_WeldClientProxy@506e6d
ConnectionTable.ConnectionTable(): ConnectionTable$Proxy$_$$_WeldClientProxy@93b16c

2012-07-31 14:03:19,093 [qtp5435124-16] DEBUG org.jboss.weld.JSF - WELD-000504 Resuming conversation with id null
DataReader.DataReader(): DataReader@1cbd382

The first 5 lines are printed after form-based authentication and the last 2 lines appear when my JSF action is handled for the first time where the injected dataReader property is used. Since the beans are application scoped, I really wonder about the 2 constructor passes. But from the behavior of the application I think there is nothing wrong with it.

My last assumption is that the servlets somehow live in seperated, independent contexts or containers or something like that. Because it is also not possible to get the FacesContext in the 2 other Servlets: FacesContext.getCurrentInstance() gives null. But I don't know if this is important in some way.

I hope anyone can help me to figure out the problem. Thank you in advance

Sebastian

Answer

Roosevelt Lai picture Roosevelt Lai · Jan 3, 2014

The error is obvious:

java.lang.NoClassDefFoundError: org/eclipse/jetty/servlet/ServletContextHandler$Decorator

Weld can't find the above Jetty server class. The reason it can't find this class is because of the classloading mechanism of Jetty. Jetty's WebAppClassloader has a default list of server classes that it will not load: see http://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading

Server Classes
-org.eclipse.jetty.continuation.             don't hide continuation classes
-org.eclipse.jetty.jndi.                     don't hide naming classes
-org.eclipse.jetty.plus.jaas.                don't hide jaas classes
-org.eclipse.jetty.websocket.                don't hide websocket extension
-org.eclipse.jetty.servlet.DefaultServlet    don't hide default servlet
org.eclipse.jetty.                           hide all other jetty classes

To allow Jetty to load this class, you need to add "-org.eclipse.jetty.servlet." to the server class list in jetty-web.xml.

 <Configure class="org.eclipse.jetty.webapp.WebAppContext">
   <Call name="prependServerClass"><Arg>-org.eclipse.jetty.servlet.</Arg></Call>
 </Configure>

Or call the following method, if you are using embedded jetty:

 context.prependServerClass("-org.eclipse.jetty.servlet.");