We saw that Chapter 3, Introducing Java EE 7 – EJBs, was challenging since we had to cover lots of ground, including Java Enterprise enhancements and a Maven-specific configuration. In this chapter, we'll discuss Contexts and Dependency Injection (CDI), which was added to the Java EE specification in Java EE 6 (starting from JSR 299). It provides several benefits to Java EE developers that were missing, such as allowing any JavaBean to be used as a JSF managed bean, including stateless and stateful session beans. You can find more information on CDI and the newest version of the specification itself (JSR 346) at http://www.cdi-spec.org/.
Some of the topics that will be covered in this chapter are as follows:
This chapter assumes familiarity with JavaServer Faces (JSF), which will be used to provide a graphical interface for our applications. If you are looking for a start up guide for JSF, there are several excellent resources available online, including the relevant sections in the official Java EE 7 tutorial at http://docs.oracle.com/javaee/7/tutorial/doc/jsf-develop.htm#BNATX.
CDI for the Java EE platform introduces a standard set of component management services to the Java EE platform. As a component of Java EE 7, CDI is in many ways a standardization of concepts that have been brewing in Spring for a long time, such as dependency injection and interceptors. In fact, CDI and Spring 3 share many similar features. There are also other dependency injection frameworks available for developers that are more lightweight and easier to use in a Java SE environment. Google Guice (https://github.com/google/guice) is a notable example. Providing full-blown support for the CDI container in a standalone Java SE application and separation from the application server are one of the goals of the upcoming CDI 2.0 specification. This will allow developers to use a common programming model on both client and server sides.
CDI lets you decouple concerns by what it refers to as loose coupling and strong typing. In doing so, it provides an almost liberating escape from the banalities of everyday Java programming, allowing injections of its objects and controlling their lifetimes.
Why is CDI required for Java EE?
If you have been programming with Java EE 5, you might argue that it already features resources injection of resources. However, this kind of injection can be used only for resources known to the container (for example, @EJB
, @PersistenceContext
, @PersistenceUnit
, and @Resource
). CDI, on the other hand, provides a general-purpose dependency injection scheme, which can be used for any component.
The CDI elementary unit is still the bean. Compared to EJBs, CDI features a different, more flexible kind of bean, which would often be a good place to put your business logic in. One of the most important differences between the two approaches is that CDI Beans are contextual; that is, they live in a well-defined scope.
Consider the following code snippet:
public class HelloServlet extends HttpServlet { @EJB private EJBSample ejb; public void doGet (HttpServletRequestreq, HttpServletResponse res) throws ServletException, IOException { try(PrintWriter out = res.getWriter()) { out.println(ejb.greet()); } } }
Here, the injected EJB proxy (let's just assume that it is a POJO class annotated with a @Stateless
annotation) just points to a pool of stateless instances (or a single bean instance for stateful beans). There is no automatic association between the HTTP request or HTTP session and a given EJB instance.
The opposite is true for CDI Beans, which live in well-defined scopes. For example, the following CDI Bean lives in RequestScoped
; that is, it will be destroyed at the end of the request:
@RequestScoped public class Customer { private String name; private String surname; public String getName(){ return name; } public String getSurname(){ return surname; } }
The preceding CDI Bean can be safely injected into our former servlet; at the end of an HTTP session or HTTP request, all the instances associated with this scope are automatically destroyed, and thus, garbage collected:
public class HelloServlet extends HttpServlet { @Inject private Customer customer; public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // some code } }
In the earlier section, we came across the @Named
annotation. Named beans allow us to easily inject our beans into other classes that depend on them and refer to them from JSF pages via the Unified Expression Language (UEL). Recall the earlier example:
@RequestScoped @Named public class Customer { private String name; private String surname; public String getName(){ return name; } public String getSurname(){ return surname; } }
This class, decorated with the @Named
annotation, can then be referenced from a JSF page:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html "> <h:body> <h:form> <h:panelGrid columns="2"> <h:outputLabel for="name" value="Name" /> <h:inputText id="name" value="#{customer.name}" /> <h:outputLabel for="lastName" value="Surname" /> <h:inputText id="surname" value="#{customer.surname}" /> <h:panelGroup /> </h:panelGrid> </h:form> </h:body> </html>
If you want to use a different naming policy for your bean, you could use the @Named
annotation as follows:
@Named(value="customNamed")
This way, we will be able to reference our CDI Beans using the identified customNamed
value.
Instead of two @RequestScoped
and @Named
annotations, we can just use the @Model
annotation that aggregates them.
CDI Beans come with a set of predefined scopes and annotations, and each CDI Bean has a distinct life cycle determined by the scope it belongs to. The following table describes the built-in CDI scopes and annotations required to set these scopes:
Other parts of Java EE can extend the list of available scopes. In Java EE 7 (in the Java Transaction API specification), a new scope has been introduced: @TransactionScoped
. It bounds the life cycle of a bean with the current transaction. It is of course possible to introduce your own custom scopes.
In this chapter example, we will use the RequestScoped
and SessionScoped
beans to drive our simple ticket-booking system. In the next chapter, we will further enhance our example using ConversationScoped
beans, which are a peculiar scope of CDI Beans. Providing a detailed explanation of all the named beans scopes is beyond the scope of this book. However, you can quench your thirst for knowledge by having a look at CDI Reference Implementation (JBoss Weld) docs at http://docs.jboss.org/weld/reference/latest/en-US/html/scopescontexts.html.
Weld is the CDI Reference Implementation that originated as part of the Seam 3 project (http://www.seamframework.org/). Weld provides a complete CDI implementation, which can be a part of a Java EE 7 container such as WildFly.
Therefore, in order to run CDI-based applications on WildFly, you don't need to download any extra libraries as Weld is part of the server modules, and it is included in all server configurations as stated by the following extension:
<extension module="org.jboss.as.weld"/>
Having your module installed, however, does not mean that you can blindly use it in your applications. The general rule is that on WildFly, every application module is isolated from other modules; this means, by default, it does not have visibility on the AS modules, nor do the AS modules have visibility on the application.
To be accurate, we could state that all WildFly modules fall into the following three categories:
javax.activation
, javax.annotation
, javax.security
, javax.transaction
, javax.jms
, and javax.xml
. Using these modules does not require any extra effort as WildFly will add them for you if you are referencing them in your application.javax.ejb
, org.jboss.resteasy
and org.hibernate
, org.jboss.as.web
, and finally org.jboss.as.weld
. All these modules will be added on the condition that you supply its core annotations (such as @Stateless
for EJB) or its core configuration files, for example, web.xml
for a web application.META-INF/MANIFEST.MF
file. For example, if you want to trigger the log4j dependency, you have to code your manifest file as follows:Dependencies: org.apache.log4j
There is also a custom descriptor file available, which is used by WildFly to resolve dependencies – jboss-deployment-structure.xml
. It allows the developer to configure the required dependencies in a fine-grained matter. The file is placed in the top-level deployment file, in the META-INF
directory (or WEB-INF
for a web archive). A sample content of the XML file (along with the XSD schema) is available at https://docs.jboss.org/author/display/WFLY8/Class+Loading+in+WildFly.
So, if you have followed our checklist carefully, you will be aware that in order to let Weld libraries kick in and automatically discover your CDI beans, you should add its core configuration file, which is beans.xml
. This file can be placed in your application at the following locations:
WEB-INF
folder if you are developing a web applicationMETA-INF
folder if you are deploying a JAR archiveThe beans.xml
file is based on the following schema reference:
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" version="1.1" bean-discovery-mode="all"> </beans>
However, it is perfectly legal to place an empty beans.xml
file in the correct location; if you do so, CDI will be enabled in your application. If you, however, do not place a beans.xml
file, then only an annotated subset of classes will be considered as beans. In such a case, the container will create beans only for classes that are annotated with CDI-related annotations and ignore the rest. Most of the times, this is not the behavior we expect, and it differs from the default mode in Java EE 6 (when the beans.xml
file was required).
You might have noticed that the bean-discovery-mode
attribute is set to all
in our beans.xml
file. This allows us to configure the CDI discovery mode we discussed in the previous paragraph. It states that every legible class in our archive will be treated as a managed bean. You can place a @Vetoed
annotation on a class to filter it out from the bean discovery process. It is also possible to set the discovery mode to annotated
so that you can place a scope annotation for every class that you would like to use as a bean. This is the default value of the newest CDI version (also when there is no beans.xml
), so be sure to set it on for all our samples.
Once you have learned the basics of CDI, we will start re-engineering the ticket-booking system using CDI Beans wherever necessary. We will turn it into a leaner application by dropping a few items such as remote interfaces or asynchronous methods, which are not needed in this example. By doing this, you will be able to focus just on the components that are actually used in the web application.
Let's create a new Maven project, just as we did in the previous chapter:
com.packtpub.wflydevelopment.chapter4
as Group Id, ticket-agency-cdi
as Artifact Id, and set packaging to war:java
(for Java classes) and resources
(for configuration files) folders, a new directory named webapp
that will host the web application views.In order to compile and run the project, our Maven's pom.xml
file will require the following set of dependencies known from the previous chapter:
<dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jboss.logging</groupId> <artifactId>jboss-logging</artifactId> <version>3.1.4.GA</version> <scope>provided</scope> </dependency> </dependencies>
We will also require two plugins from the previous chapter (note that we changed the extension of the filename from jar
to war
):
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- WildFly plugin to deploy the application -->
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<version>1.0.2.Final</version>
<configuration>
<filename>${project.build.finalName}.war</filename>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<!-- enforce Java 8 -->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
In case you have any problems with the POM
configuration file, be sure that you check the source code attached to this book and the material from the previous chapter.
Once your project is properly configured, we can start modeling our beans. The first bean we will upgrade is TheatreBooker
, which will drive the user session, accessing the ticket list from our TheatreBox
bean:
package com.packtpub.wflydevelopment.chapter4.controller; import com.packtpub.wflydevelopment.chapter4.boundary.TheatreBox; import org.jboss.logging.Logger; import javax.annotation.PostConstruct; import javax.enterprise.context.SessionScoped; import javax.faces.application.FacesMessage; import javax.faces.context.FacesContext; import javax.inject.Inject; import javax.inject.Named; import java.io.Serializable; @Named [1] @SessionScoped [2] public class TheatreBooker implements Serializable { @Inject private Logger logger; [3] @Inject private TheatreBox theatreBox; [4] @Inject private FacesContext facesContext; [5] private int money; @PostConstruct public void createCustomer() { this.money = 100; } public void bookSeat(int seatId) { logger.info("Booking seat " + seatId); int seatPrice = theatreBox.getSeatPrice(seatId); if (seatPrice > money) { FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Not enough Money!", "Registration unsuccessful"); [6] facesContext.addMessage(null, m); return; } theatreBox.buyTicket(seatId); FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_INFO, "Booked!", "Booking successful"); facesContext.addMessage(null, m); logger.info("Seat booked."); money = money - seatPrice; } public int getMoney() { return money; } }
As you can see, the bean has been tagged as Named [1]
, which means that it can be directly referenced in our JSF pages. The bean is SessionScoped [2]
since it stores the amount of money available to the customer during its session.
We would also like to inject logger [3]
and FacesContextFacexContexts [5]
instead of manually defining it. To do this, we will need to register a bean that produces loggers, which are parameterized with the name of the class. We will cover this process of producing beans in a moment.
Finally, notice that we can safely inject EJBs into our CDI Beans using the Inject [4]
annotation. Also, the reverse is perfectly legal, that is, injecting CDI Beans into EJBs.
Compared to our earlier project, here we don't raise Java exceptions when the customer is not able to afford a ticket. Since the application is web based, we simply display a warning message to the client using JSF Faces Messages [6]
.
The other bean that we still use in our application is TheatreInfo
, which has been moved to the controller
package as it will actually provide the application with the list of available seats:
package com.packtpub.wflydevelopment.chapter4.controller; import com.google.common.collect.Lists; import com.packtpub.wflydevelopment.chapter4.boundary.TheatreBox; import com.packtpub.wflydevelopment.chapter4.entity.Seat; import javax.annotation.PostConstruct; import javax.enterprise.event.Observes; import javax.enterprise.event.Reception; import javax.enterprise.inject.Model; import javax.enterprise.inject.Produces; import javax.inject.Inject; import javax.inject.Named; import java.util.Collection; @Model [1] public class TheatreInfo { @Inject private TheatreBox box; private Collection<Seat> seats; @PostConstruct public void retrieveAllSeatsOrderedByName() { seats = box.getSeats(); } @Produces [2] @Named public Collection<Seat> getSeats() { return Lists.newArrayList(seats); } public void onMemberListChanged(@Observes(notifyObserver = Reception.IF_EXISTS) final Seat member) { retrieveAllSeatsOrderedByName(); [3] } }
At first, have a look at the @Model
annotation [1]
, which is an alias (we call this kind of annotations stereotypes) for two commonly used annotations: @Named
and @RequestScoped
. Therefore, this bean will be named into our JSF page and will carry a request scope.
Next, pay attention to the getSeats
method. This method returns a list of seats, exposing it as a producer
method [2]
.
The producer
method allows you to have control over the production of the dependency objects. As a Java factory pattern, they can be used as a source of objects whose implementation may vary at runtime or if the object requires some custom initialization that is not to be performed in the constructor.
It can be used to provide any kind of concrete class implementation; however, it is especially useful to inject Java EE resources into your application.
One advantage of using a @Producer
annotation for the getSeats
method is that its objects can be exposed directly via JSF's Expression Language (EL), as we will see in a minute.
Finally, another feature of CDI that was unleashed in this example is the observer. An observer, as the name suggests, can be used to observe events. An observer method is notified whenever an object is created, removed, or updated. In our example, it allows the list of seats to be refreshed whenever they are needed.
To be precise, in our example, we are using a conditional observer that is denoted by the expression notifyObserver = Reception.IF_EXISTS
. This means that in practice, the observer
method is only called if an instance of the component already exists. If not specified, the default option (ALWAYS
) will be that the observer method is always called. (If an instance doesn't exist, it will be created.)
In the newest CDI version, it is possible to get additional information about the fired event in the observer by adding an EventMetadata
parameter to the observer's method.
Whenever a change in our list of seats occurs, we will use the javax.enterprise.event.Event
object to notify the observer about the changes. This will be done in our singleton bean, which gets injected with the seat's event [1]
, and notifies the observer by firing the event when a seat is booked [2]
:
package com.packtpub.wflydevelopment.chapter4.boundary; import javax.enterprise.event.Event; @Singleton @Startup @AccessTimeout(value = 5, unit = TimeUnit.MINUTES) public class TheatreBox { @Inject [1] private Event<Seat> seatEvent; @Lock(WRITE) public void buyTicket(int seatId) { final Seat seat = getSeat(seatId); final Seat bookedSeat = seat.getBookedSeat(); addSeat(bookedSeat); seatEvent.fire(bookedSeat); [2] } // Rest of the code stays the same, as in the previous chapter }
Earlier, we mentioned that a preconfigured logger should be injected to a bean if it requests it. We will create a simple logger producer that will use the information about the injection point (the bean that requests a logger) to configure an instance:
package com.packtpub.wflydevelopment.chapter4.util; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.InjectionPoint; import org.jboss.logging.Logger; public class LoggerProducer { @Produces public Logger produceLogger(InjectionPoint injectionPoint) { return Logger.getLogger(injectionPoint.getMember().getDeclaringClass().getName()); } }
We also allowed the injection of FacesContext
instead of using the standard FacesContext.getCurrentInstance()
static method. This context is used, for example, to display the stated error messages:
package com.packtpub.wflydevelopment.chapter4.util; import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Produces; import javax.faces.context.FacesContext; public class FacesContextProducer { @Produces @RequestScoped public FacesContext produceFacesContext() { return FacesContext.getCurrentInstance(); } }
The last class we will include in our project is the Seat
bean, known from the previous chapter, which will be used as our model without any change (remember to include it in your project with a proper package).
Once we have coded the server side of our example, creating the front end will be quite easy, as we have made all our resources available through CDI Beans.
One notable difference between some of the earlier editions of this book is that Facelets are now the preferred view technology for JSF. Earlier versions of JSF used JavaServer Pages (JSP) as their default view technology. As JSP technology predates JSF, using JSP with JSF sometimes felt unnatural or created problems. For example, the life cycle of JSPs is different from the life cycle of JSF.
Compared to the simpler request-response paradigm on which the JSP life cycle is based, the JSF life cycle is much more complex since the core of JSF is the MVC pattern, which has several implications. User actions in JSF-generated views take place in a client that does not have a permanent connection to the server. The delivery of user actions or page events is delayed until a new connection is established. The JSF life cycle must handle this delay between event and event processing. Also, the JSF life cycle must ensure that the view is correct before rendering it, and also that the JSF system includes a phase to validate inputs and another to update the model only after all the inputs pass validation.
Most of the time Facelets are used to build JavaServer Faces views using HTML-style templates and component trees. Templating is a useful feature available with Facelets that allows you to create a page that will act as the template for the other pages in an application (something like Struts Tiles). The idea is to obtain portions of reusable code without repeating the same code on different pages.
So here's the main application structure that contains a template page named default.xhtml
that is referenced by views in the template attribute of the page's composition element. The template contains two main HTML div
elements that will be used to contain the main application panel (content
) and a footer div (footer
), which will barely output the application title.
In order to add the template at first, add a new JSF page to the WEB-INF/templates
folder of your application and name it default.xhtml
:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <h:outputStylesheet name="style.css"/> </h:head> <h:body> <div id="container"> <div id="content"> <ui:insert name="content"> [Template content will be inserted here] </ui:insert> </div> <div id="footer"> <p> <em>WildFly Development Ticket Booking example.</em><br/> </p> </div> </div> </h:body> </html>
Next, we will add the main page view, which will be embedded into your template. For this purpose, add a JSF page named index.xhtml
to the webapp
folder of your Maven project:
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:h="http://xmlns.jcp.org/jsf/html" template="/WEB-INF/templates/default.xhtml"> [1] <ui:define name="content"> <h1>TicketBooker Machine</h1> <h:form id="reg"> <h3>Money: $ #{theatreBooker.money}</h3> [2] <h:messages errorClass="error" infoClass="info" globalOnly="true"/> <h:panelGrid columns="1" border="1" styleClass="smoke"> <h:dataTable var="_seat" value="#{seats}" [3] rendered="#{not empty seats}" styleClass="simpletablestyle"> <h:column> <f:facet name="header">Id</f:facet> #{_seat.id} </h:column> <h:column> <f:facet name="header">Name</f:facet> #{_seat.name} </h:column> <h:column> <f:facet name="header">Price</f:facet> #{_seat.price}$ </h:column> <h:column> <f:facet name="header">Booked</f:facet> #{_seat.booked} </h:column> <h:column> <f:facet name="header">Action</f:facet> <h:commandButton id="book" action="#{theatreBooker.bookSeat(_seat.id)}" [4] disabled="#{_seat.booked}" value="#{_seat.booked ? 'Reserved' : 'Book'}" /> </h:column> </h:dataTable> </h:panelGrid> </h:form> </ui:define> </ui:composition>
The ui:composition
element is a templating tag that wraps content to be included in another Facelet. Specifically, it will be included in the default.xhtml[1]
template.
The creation of the view is done in three steps. First, we will display the customer's money [2]
, which is bound to the session variable called money
.
The next thing on the checklist is printing all JSF messages [3]
that are meant to be produced by the application via the messages
element.
The main task of this view is to produce a view of all tickets and let the users purchase them. This is achieved by means of a dataTable
object [3]
that can be used to produce a tabular list of objects, which are generally stored as java.util.List
in your beans.
Pay attention to the value attribute of the dataTable
object:
<h:dataTable var="_seat" value="#{seats}" rendered="#{not empty seats}" styleClass="simpletablestyle">
In this case, we don't directly reference a CDI Bean, but we reference an object that has been produced by a CDI Bean. To be precise, it has been produced by TheatreInfo
that, as we have seen, has a @Produces
and @Named
annotation on our list of seats:
private List<Seat> seats; @Produces @Named public List<Seat>getSeats() { return seats; }
This dataTable
object will be displayed only if it contains some data in it (as dictated by the not empty seats
EL expression). In one of the dataTable
columns, we have added commandButton [4]
that will be used to book the seat displayed on that row. Notice one of the JSF 2 goodies here, as we call the bookSeat
method of TheatreBooker
passing an argument as one parameter, which is the seatId
field.
By enabling JSF 2 facets on your project configuration, you can enjoy some additional benefits while designing your views.
Enabling JSF 2 project facets takes half a minute. Right-click on your project and navigate to Properties | Project Facets. Then, select the JSF 2.2 Project facets checkbox and click on the OK button:
Once JSF 2 facets are configured, if you press Ctrl + Space bar before referencing a field or method, a suggestion pop-up window will let you choose the method or attribute of the Bean you want to reference.
OK, now your application is almost ready. We just need to configure a JSF mapping in a web.xml
file as follows:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>/faces/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>faces/index.xhtml</welcome-file> </welcome-file-list> </web-app>
This will then run the FacesServlet
servlet for all the pages at /faces/* url
.
Finally, as stated previously, in order to activate our war
file as an explicit bean archive, we need to add an empty beans.xml
file to the WEB-INF
folder of your application.
So, if you follow the same naming convention used in this chapter, you will end up with the following project structure:
At this point, you must be familiar with building and deploying your Maven applications using Eclipse or a shell. Assuming that you are managing your application from a shell, start by building up the project using the following:
mvn package
Then, publish it using the WildFly Maven plugin, as we did in the previous chapter.
If the WildFly server is started, you can execute the following command:
mvn wildfly:deploy
If the WildFly server is not started, you can execute the following command and then the WildFly Maven plugin will automatically start an instance:
mvn wildfly:run
The application will be available at http://localhost:8080/ticket-agency-cdi
.
Then, to do this with a unique command, you can execute the following:
mvn clean package wildfly:deploy
After so much work, you will be pleased to have your application running on your browser:
Right now, you will be able to book tickets up to the budget ($ 100) defined in your SessionScoped
bean. So enjoy this first taste of JSF and CDI.
Of course, in this chapter, we only scratched the surface of JSF features. There is also a new higher-level approach introduced in JSF 2.2 that can be used for flow-based scenarios such as a shopping cart. The new feature is called FacesFlow and comes with a @FlowScoped
annotation. However, we will now focus on adding some other features to our current application.
Up to now, we have not included the scheduler, which was in charge of simulating other customer-requesting tickets, into our application. This was not an oversight; as a matter of fact, introducing an external system in a web application poses some challenges. For example, what if the scheduler updates some data used by the application? How will the user know it?
There are several strategies to address this requirement; however, they all boil down to using some intelligence in your client application. For example, if you are familiar with web scripting languages, you can use the popular jQuery API to poll the server for some updates. The newest version of JSF 2.2 comes with great support for HTML5 and JavaScript frameworks, thanks to the custom data attributes and pass-through elements. These are simple mechanisms that allow the JSF's render kit to render parts of the page without any further changes so that custom tags may be interpreted by the browser (or a JavaScript framework).
Since not all Java EE developers might be skilled in JavaScript, we would rather show a simple and effective way to fulfill our requirement using RichFaces libraries (http://www.jboss.org/richfaces), which provide advanced Ajax support along with a rich set of ready-to-use components.
Installing RichFaces requires a set of core libraries that are generally available at the RichFaces download page.
Additionally, you need to provide a set of third-party dependencies that are used by the RichFaces API. Never mind, that's what Maven is for! Start by adding the latest Bill of Materials (BOM) for the RichFaces
API in the upper dependency-management section:
<dependencyManagement> ... <dependency> <groupId>org.richfaces</groupId> <artifactId>richfaces-bom</artifactId> <version>4.3.5.Final</version> <scope>import</scope> <type>pom</type> </dependencies> </dependencyManagement>
Then, it's just a matter of adding the rich UI libraries and the core API:
<dependency> <groupId>org.richfaces.ui</groupId> <artifactId>richfaces-components-ui</artifactId> </dependency> <dependency> <groupId>org.richfaces.core</groupId> <artifactId>richfaces-core-impl</artifactId> </dependency>
Once we have installed RichFaces libraries, we will just need to reference them on each XHTML page in your project. Here's the new index.xhtml
page using the RichFaces namespaces:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich" template="/WEB-INF/templates/default.xhtml"> <ui:define name="content"> <f:view> <h:form> <a4j:poll id="poll" interval="2000" enabled="#{pollerBean.pollingActive}" render="poll,grid,bookedCounter"/> <rich:panel header="TicketBooker Machine" style="width:350px"> <h2>Book your Ticket</h2> <h3>Money: $ #{theatreBooker.money}</h3> <h:messages errorClass="error" infoClass="info" globalOnly="true"/> <rich:dataTable id="grid" var="_seat" value="#{seats}" rendered="#{not empty seats}" styleClass="simpletablestyle"> <h:column> <f:facet name="header">Id</f:facet> #{_seat.id} </h:column> <h:column> <f:facet name="header">Name</f:facet> #{_seat.name} </h:column> <h:column> <f:facet name="header">Price</f:facet> #{_seat.price} </h:column> <h:column> <f:facet name="header">Booked</f:facet> #{_seat.booked} </h:column> <h:column> <f:facet name="header">Action</f:facet> <h:commandButton id="book" action="#{theatreBooker.bookSeat(_seat.id)}" disabled="#{_seat.booked}" value="#{_seat.booked ? 'Not Available' : 'Book'}"/> </h:column> </rich:dataTable> <h:outputText value="Booked seats on this page: #{bookingRecord.bookedCount}" id="bookedCounter" /> </rich:panel> </h:form> </f:view> </ui:define> </ui:composition>
We have highlighted the core enhancements added to this page. At first, as we said, we need to reference the RichFaces libraries at the top of the XHTML page.
Next, we added a rich Ajax component, a4j:poll, which does a simple but an effective job of polling the server for updates, allowing the re-rendering of our components—grid
(which contains the main datatable), poller
(to check whether it should still be running), and bookedCounter
.
Additionally, this component references a CDI bean named Poller
, which acts just as an on/off flag for our poller. We expect to turn off polling as soon as all the seats are sold out:
package com.packtpub.wflydevelopment.chapter4.controller; import java.util.Optional; import javax.enterprise.inject.Model; import javax.inject.Inject; import com.packtpub.wflydevelopment.chapter4.boundary.TheatreBox; import com.packtpub.wflydevelopment.chapter4.entity.Seat; @Model public class Poller { @Inject private TheatreBox theatreBox; public boolean isPollingActive() { return areFreeSeatsAvailable(); } private boolean areFreeSeatsAvailable() { final Optional<Seat> firstSeat = theatreBox.getSeats().stream().filter(seat -> !seat.isBooked()).findFirst(); return firstSeat.isPresent(); } }
Our seller service stays nearly the same as in the previous chapter (the only difference is the logger
injection):
package com.packtpub.wflydevelopment.chapter4.control; import com.packtpub.wflydevelopment.chapter4.boundary.TheatreBox; import com.packtpub.wflydevelopment.chapter4.entity.Seat; import org.jboss.logging.Logger; import javax.annotation.Resource; import javax.ejb.EJB; import javax.ejb.Schedule; import javax.ejb.Stateless; import javax.ejb.Timer; import javax.ejb.TimerService; import javax.inject.Inject; import java.util.Collection; import java.util.Optional; @Stateless public class AutomaticSellerService { @Inject private Logger logger; @Inject private TheatreBox theatreBox; @Resource private TimerService timerService; @Schedule(hour = "*", minute = "*", second = "*/30", persistent = false) public void automaticCustomer() { final Optional<Seat> seatOptional = findFreeSeat(); if (!seatOptional.isPresent()) { cancelTimers(); logger.info("Scheduler gone!"); return; // No more seats } final Seat seat = seatOptional.get(); theatreBox.buyTicket(seat.getId()); logger.info("Somebody just booked seat number " + seat.getId()); } private Optional<Seat> findFreeSeat() { final Collection<Seat> list = theatreBox.getSeats(); return list.stream().filter(seat -> !seat.isBooked()).findFirst(); } private void cancelTimers() { for (Timer timer : timerService.getTimers()) { timer.cancel(); } } }
Finally, we'll add a booking record, which will be bounded with the current view using the view scope. Its role will be to count the number of bookings done by the user in the current view (a single browser tab is considered a single view):
package com.packtpub.wflydevelopment.chapter4.controller; import com.packtpub.wflydevelopment.chapter4.entity.Seat; import java.io.Serializable; import javax.enterprise.event.Observes; import javax.faces.view.ViewScoped; import javax.inject.Named; @Named @ViewScoped public class BookingRecord implements Serializable { private int bookedCount = 0; public int getBookedCount() { return bookedCount; } public void bookEvent(@Observes Seat bookedSeat) { bookedCount++; } }
You can experiment with the booked counter by trying to book tickets via two separate tabs in your browser.
You might have noticed that we placed two annotations on the bean: @Named
and @ViewScoped
. If you would like to define multiple beans with a specific set of CDI annotations, it would be a good idea to create your own custom annotation that already contains the desired ones. This kind of construction is called a stereotype. It is possible to incorporate the following elements:
@Named
annotation@Alternative
annotationTo create a stereotype, you need to add the wanted annotations along with the @Stereotype
annotation:
@ViewScoped @Named @Stereotype @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface NamedView { }
Now you can define the BookinRecord
bean as follows:
@NamedView public class BookingRecord implements Serializable { //Some code here }
The @Model
stereotype is available in CDI by default. It defines a request scoped named bean, and you can use it on your beans right out of the box.
With all the libraries in place, you can now test run your new rich application. As you can see, every 30 seconds a ticket is sold out and buttons are turned, in real time, into Not available:
There is one more CDI feature worth mentioning here, the interceptors. Sometimes, applications contain logic and cross-cutting multiple layers; the most simple example is logging. Under the Java EE platform, it can be achieved using interceptors. First, we need to create a new annotation:
@Inherited
@InterceptorBinding [1]
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface Logged {
// empty
}
This annotation defines an interceptor binding. It can be used to specify methods that you would like to intercept. The bindings can be used on types as well; in that case, every method call on that type is intercepted. The most important part of this definition is the @InterceptorBinding [1]
annotation. Be sure to add it!
Then, we have to create the interceptor definition itself:
@Interceptor @Logged [1] public class LoggingInterceptor implements Serializable { @AroundInvoke [2] public Object log(InvocationContext context) throws Exception { final Logger logger = Logger.getLogger(context.getTarget().getClass()); logger.infov("Executing method {0}", context.getMethod().toString()); return context.proceed() [3]; } }
We start by stating that our class is @Interceptor
and it will be using the interceptor binding that we've defined earlier (@Logged [1]
). Next, we create a method log that will be executed around every method execution (@AroundInvoke [2]
) on annotated classes. Inside of it, we will call the context.proceed()
method that will basically forward the call to the original receiver. Note that the interceptor can decide (based on some security logic, for instance) whether the call should be dropped. It could even analyze or change the returned value.
Finally, we have to enable it in the beans.xml
file by adding the following code:
<interceptors> <class>com.packtpub.wflydevelopment.chapter4.util.LoggingInterceptor</class> </interceptors>
Now, let's move on to just the annotated classes or methods that you want to log using the @Logged
annotation. For instance, refer to the following:
@Named @SessionScoped @Logged public class TheatreBooker implements Serializable { // Some code }
All calls to the TheatreBooker
public methods will now be logged in to the console:
21:02:11 INFO [com.packtpub.wflydevelopment.chapter4 .controller.TheatreBooker$Proxy$_$$_WeldSubclass] (default task-8) Executing method public int com.packtpub.wflydevelopment.chapter4.controller. TheatreBooker.getMoney()
In the case of multiple interceptors, the order in which they are executed is determined by the @Interceptor.Priority
annotation. Interceptors with lowest priorities will be called first. Be sure to check the constants defined in the Priority
annotation. Your own interceptor's priorities should be between the APPLICATION
and LIBRARY_AFTER
scope.
There are also other interesting CDI mechanisms that we will not cover in this book, but are definitely worth exploring: decorators and alternatives. Decorators are basically strongly typed interceptors that are focused on the business logic of your application. Alternatives can be used to provide alternative implementations for specific beans.
At the end of this chapter, we would like to give our honest opinion about a common question posed by developers, that is, how EJB, JSF Managed Beans, and CDI interact and where the boundary between them lies. Are there redundancies between them? It is indeed a bit confusing since there are now multiple component models available in Java EE.
JSF Managed Beans have been, for a long time, the actual glue between the application view and the business methods. Since Release 2.0 of JSF, you can declare JSF Managed Beans via an annotation, and the scopes are expanded with a view scope and the ability to create custom scopes. However, there is very little still going on for JSF Managed Beans. Most of its features can be replaced by CDI Beans that are much more flexible and allow you to have a better integration with other Java EE components. Even the view scope, in the newest version of JSF, has been implemented as a CDI custom scope (javax.faces.view.ViewScoped
), which replaces the old javax.faces.bean.ViewScoped
(notice the name of the package; it's a common mistake to mix them up).
On the other hand, EJBs, even though they use a less flexible injection mechanism, still maintain some unique features such as schedulable timers, asynchronous operations, and pooling that are essential for throttling and assuring that the application provides a good quality of service. Beginning from Java EE 7, EJBs no longer are the only components that have a transactional nature. The new @Transactional
annotation allows you to use declarative transactions in CDI beans by simply placing it on selected methods.
Despite this, it's likely that EJBs are not disappearing from our code, rather it is likely (and desirable too) that they will continue to be used for some of their unique features. For the remaining part though, its functionality will be exposed via CDI instead of EJBs' own annotations such as @Stateless
and @EJB
.
18.218.5.12