Working with CDI and JSF 2.0 on Tomcat 7: Configuring Weld
By default, you can't use CDI in your JSF 2.0 applications when they are simply deployed to a standard servlet engine such as Tomcat 7. But, with Weld, you can make it work.
The JSF specification was ahead of the pack when it came to working with and annotating managed beans. However, as the JSF 2.0 specification was getting wrapped up, another JSR specification, JSR 299, was being developed as well. Frustratingly, JSR 299, the Contexts and Dependency Injection (CDI) specification, provides a much more flexible and extendible implementation than the fairly limited JSF implementation. As a result, it's recommended that if you're developing a JSF application that will be depolyed to a Java EE 6 compliant applicaiton server, then you should be using CDI, and not the standard JSF annotations.
Having two standards and sets of annotations is pretty confusing. Gavin King stepped into the fray on the issue of why we have two specifications, and why one should be used instead of the other. I'd like to take the liberty to quote him on the topic:
|
I'm going to recreate the Rock-Paper-Scissors application and use CDI annotations instead of the JSF ones. To keep things separate, I'm actually going to copy my current, working, JSF application which is saved in a folder named _easyjsf, and copy it all to a folder named _easyweld. With that done, I'm going to edit the GameBean to make is Serializable, and to make it use the CDI annotations that correspond to the JSF annotations we've been using up to this point.
So, to redo our GameBean using CDI instead of JSF annotations is fairly simple as far as the coding goes.We simply replace @ManagedBean with @Named and we replace the JSF @SessionScope annotation with one from the javax.enterprise.context package.
Here's more about using Apache Tomcat
- What is Tomcat? A definition
- A video tutorial on how to install Tomcat
- Mastering Tomcat: From configuration to optimization
- Tomcat microservices: How to embed Tomcat in an executable JAR
- Using CDI and JSF on Tomcat
- Packaging Java programs as microservices
Note than any @Named annotated JavaBean must implement the java.io.Serializable interface. The JSF frameworks isn't quite as strict about serialization, but CDI and Weld is unrelenting.
package com.mcnz.jsf;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
@javax.inject.Named
@javax.enterprise.context.SessionScoped
public class GameBean
implements java.io.Serializable
{ private String computerGesture = null; private String clientGesture = null; public void executeGameLogic(javax.faces.event.ActionEvent event)
{ this.computerGesture = "rock"; /* we always choose rock! */ }
public String getResult()
{ String result = "error"; if (clientGesture != null)
{ if (clientGesture.equals("paper"))
{ result = "win"; }
if (clientGesture.equals("scissors"))
{ result = "loss"; }
if (clientGesture.equals("rock"))
{ result = "draw"; }
}
return result;
}
public void reset(javax.faces.event.ActionEvent event)
{ computerGesture = null; }
public String getClientGesture()
{ return clientGesture; }
public void setClientGesture(String clientGesture)
{ this.clientGesture = clientGesture; }
public String getComputerGesture()
{ return computerGesture; }
public void setComputerGesture(String computerGesture)
{ this.computerGesture = computerGesture; }
}
Now, if you were using a Java EE 6 compliant application server then you could deploy this immediately and it would all work. Unfortuntately, CDI support doesn't come with a standard servlet engine like Tomcat 7, so if you want to deploy a JSF application that leverages CDI annotations, well, there's a little bit of work to do.
First, you need to get an implementation of the CDI specification. The reference implementation of JSR 299 is known as Weld, and can be downloaded from the Seam Framework website:
http://seamframework.org/Weld/
When the download is extracted, you'll find an \artifacts\weld subfolder within the distribution, and that weld folder includes a file named weld-servlet.jar that needs to go into the WEB-INF\lib folder of your JSF application. This weld-servlet.jar includes every Java class you need to link to at runtime in order to turn Tomcat 7 into a functional CDI compliant container.
Sadly though, it's not good enough to simply add the weld-servlet.jar file to the lib directory of your application. You also need to tell the web application hosting your JSF code that it's going to be leveraging some CDI functionality, and as such, a listener entry must go inside the web.xml file:
<listener> <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class> </listener>
Here's what the full faces-config.xml file looks like once the listener entry has been added:
<?xml version='1.0' encoding='UTF-8'?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="https://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">
<!-- Faces Servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.jboss.weld.environment.servlet.Listener</listener-class>
</listener>
</web-app>
And just so you know, if you mess up the listener entry in the web.xml file, you'll get an error message that looks something like this at runtime:
Unable to find BeanManager for org.apache.catalina.core.ApplicationContextFacade
And finally, if you want everything to work, you need to add in an empty beans.xml file alongside the web.xml and faces-config.xml file in the WEB-INF folder of the application.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="https://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>
It may seem rather odd and redundant to include an xml file that really doesn't contain anything of significance, but without it, you'll get the following runtime error:
java.lang.NullPointerException org.jboss.weld.context.ForwardingContextual.toString(ForwardingContextual.java)
or perhaps even this one:
Target Unreachable, identifier resolved to null
With all of that completed, my web application looks something like this:
_easyweld\ + META-INF\ + WEB-INF\ ++ beans.xml ++ web.xml ++ faces-config.xml ++ classes\ +++ com\mcnz\jsf\GameBean.java +++ com\mcnz\jsf\GameBean.class ++ lib\ +++ jsf-api.jar +++ jsf-impl.jar +++ jstl.jar +++ standard.jar +++ weld-servlet.jar |
From here, you can compile your code with the following command:
C:\_jdk1.6\bin\javac -classpath "C:\_easyweld\WEB-INF\lib\*;C:\tomcat\lib\*" com\mcnz\jsf\*.java
And you can war up your application with the following command:
%JAVA_HOME%\bin\jar -cvf C:\_tomcat\webapps\easyweld.war *.*
Notice that the name and folder being used here is easyweld, not easyjsf as was used in earlier examples
But that's all there is to it! Once you redeploy to Tomcat, and perhaps even bounce the server if need be, the application will re-run, and it will be using the JSR299 CDI annotations instead of the ones provided by the JSF framework..