In this article by David R. Heffelfinger, the author of Java EE 7 Development with NetBeans 8, we will introduce Contexts and Dependency Injection (CDI) and other aspects of it. CDI can be used to simplify integrating the different layers of a Java EE application. For example, CDI allows us to use a session bean as a managed bean, so that we can take advantage of the EJB features, such as transactions, directly in our managed beans.
In this article, we will cover the following topics:
Introduction to CDI
Qualifiers
Stereotypes
Interceptor binding types
Custom scopes
(For more resources related to this topic, see here.)
Introduction to CDI
JavaServer Faces (JSF) web applications employing CDI are very similar to JSF applications without CDI; the main difference is that instead of using JSF managed beans for our model and controllers, we use CDI named beans. What makes CDI applications easier to develop and maintain are the excellent dependency injection capabilities of the CDI API.
Just as with other JSF applications, CDI applications use facelets as their view technology. The following example illustrates a typical markup for a JSF page using CDI:
<?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
>
<h:head>
<title>Create New Customer</title>
</h:head>
<h:body>
<h:form>
<h3>Create New Customer</h3>
<h:panelGrid columns="3">
<h:outputLabel for="firstName" value="First Name"/>
<h:inputText id="firstName" value="#{customer.firstName}"/>
<h:message for="firstName"/>
<h:outputLabel for="middleName" value="Middle Name"/>
<h:inputText id="middleName"
value="#{customer.middleName}"/>
<h:message for="middleName"/>
<h:outputLabel for="lastName" value="Last Name"/>
<h:inputText id="lastName" value="#{customer.lastName}"/>
<h:message for="lastName"/>
<h:outputLabel for="email" value="Email Address"/>
<h:inputText id="email" value="#{customer.email}"/>
<h:message for="email"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{customerController.navigateToConfirmation}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>
As we can see, the preceding markup doesn't look any different from the markup used for a JSF application that does not use CDI. The page renders as follows (shown after entering some data):
In our page markup, we have JSF components that use Unified Expression Language expressions to bind themselves to CDI named bean properties and methods. Let's take a look at the customer bean first:
package com.ensode.cdiintro.model;
import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named
@RequestScoped
public class Customer implements Serializable {
private String firstName;
private String middleName;
private String lastName;
private String email;
public Customer() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
The @Named annotation marks this class as a CDI named bean. By default, the bean's name will be the class name with its first character switched to lowercase (in our example, the name of the bean is "customer", since the class name is Customer). We can override this behavior if we wish by passing the desired name to the value attribute of the @Named annotation, as follows:
@Named(value="customerBean")
A CDI named bean's methods and properties are accessible via facelets, just like regular JSF managed beans.
Just like JSF managed beans, CDI named beans can have one of several scopes as listed in the following table. The preceding named bean has a scope of request, as denoted by the @RequestScoped annotation.
Scope
Annotation
Description
Request
@RequestScoped
Request scoped beans are shared through the duration of a single request. A single request could refer to an HTTP request, an invocation to a method in an EJB, a web service invocation, or sending a JMS message to a message-driven bean.
Session
@SessionScoped
Session scoped beans are shared across all requests in an HTTP session. Each user of an application gets their own instance of a session scoped bean.
Application
@ApplicationScoped
Application scoped beans live through the whole application lifetime. Beans in this scope are shared across user sessions.
Conversation
@ConversationScoped
The conversation scope can span multiple requests, and is typically shorter than the session scope.
Dependent
@Dependent
Dependent scoped beans are not shared. Any time a dependent scoped bean is injected, a new instance is created.
As we can see, CDI has equivalent scopes to all JSF scopes. Additionally, CDI adds two additional scopes. The first CDI-specific scope is the conversation scope, which allows us to have a scope that spans across multiple requests, but is shorter than the session scope. The second CDI-specific scope is the dependent scope, which is a pseudo scope. A CDI bean in the dependent scope is a dependent object of another object; beans in this scope are instantiated when the object they belong to is instantiated and they are destroyed when the object they belong to is destroyed.
Our application has two CDI named beans. We already discussed the customer bean. The other CDI named bean in our application is the controller bean:
package com.ensode.cdiintro.controller;
import com.ensode.cdiintro.model.Customer;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
@Named
@RequestScoped
public class CustomerController {
@Inject
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public String navigateToConfirmation() {
//In a real application we would
//Save customer data to the database here.
return "confirmation";
}
}
In the preceding class, an instance of the Customer class is injected at runtime; this is accomplished via the @Inject annotation. This annotation allows us to easily use dependency injection in CDI applications. Since the Customer class is annotated with the @RequestScoped annotation, a new instance of Customer will be injected for every request.
The navigateToConfirmation() method in the preceding class is invoked when the user clicks on the Submit button on the page. The navigateToConfirmation() method works just like an equivalent method in a JSF managed bean would, that is, it returns a string and the application navigates to an appropriate page based on the value of that string. Like with JSF, by default, the target page's name with an .xhtml extension is the return value of this method. For example, if no exceptions are thrown in the navigateToConfirmation() method, the user is directed to a page named confirmation.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
>
<h:head>
<title>Success</title>
</h:head>
<h:body>
New Customer created successfully.
<h:panelGrid columns="2" border="1" cellspacing="0">
<h:outputLabel for="firstName" value="First Name"/>
<h:outputText id="firstName" value="#{customer.firstName}"/>
<h:outputLabel for="middleName" value="Middle Name"/>
<h:outputText id="middleName"
value="#{customer.middleName}"/>
<h:outputLabel for="lastName" value="Last Name"/>
<h:outputText id="lastName" value="#{customer.lastName}"/>
<h:outputLabel for="email" value="Email Address"/>
<h:outputText id="email" value="#{customer.email}"/>
</h:panelGrid>
</h:body>
</html>
Again, there is nothing special we need to do to access the named beans properties from the preceding markup. It works just as if the bean was a JSF managed bean. The preceding page renders as follows:
As we can see, CDI applications work just like JSF applications. However, CDI applications have several advantages over JSF, for example (as we mentioned previously) CDI beans have additional scopes not found in JSF. Additionally, using CDI allows us to decouple our Java code from the JSF API. Also, as we mentioned previously, CDI allows us to use session beans as named beans.
Qualifiers
In some instances, the type of bean we wish to inject into our code may be an interface or a Java superclass, but we may be interested in injecting a subclass or a class implementing the interface. For cases like this, CDI provides qualifiers we can use to indicate the specific type we wish to inject into our code.
A CDI qualifier is an annotation that must be decorated with the @Qualifier annotation. This annotation can then be used to decorate the specific subclass or interface. In this section, we will develop a Premium qualifier for our customer bean; premium customers could get perks that are not available to regular customers, for example, discounts.
Creating a CDI qualifier with NetBeans is very easy; all we need to do is go to File | New File, select the Contexts and Dependency Injection category, and select the Qualifier Type file type.
In the next step in the wizard, we need to enter a name and a package for our qualifier.
After these two simple steps, NetBeans generates the code for our qualifier:
package com.ensode.cdiintro.qualifier;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Premium {
}
Qualifiers are standard Java annotations. Typically, they have retention of runtime and can target methods, fields, parameters, or types. The only difference between a qualifier and a standard annotation is that qualifiers are decorated with the @Qualifier annotation.
Once we have our qualifier in place, we need to use it to decorate the specific subclass or interface implementation, as shown in the following code:
package com.ensode.cdiintro.model;
import com.ensode.cdiintro.qualifier.Premium;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named
@RequestScoped
@Premium
public class PremiumCustomer extends Customer {
private Integer discountCode;
public Integer getDiscountCode() {
return discountCode;
}
public void setDiscountCode(Integer discountCode) {
this.discountCode = discountCode;
}
}
Once we have decorated the specific instance we need to qualify, we can use our qualifiers in the client code to specify the exact type of dependency we need:
package com.ensode.cdiintro.controller;
import com.ensode.cdiintro.model.Customer;
import com.ensode.cdiintro.model.PremiumCustomer;
import com.ensode.cdiintro.qualifier.Premium;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
@Named
@RequestScoped
public class PremiumCustomerController {
private static final Logger logger = Logger.getLogger(
PremiumCustomerController.class.getName());
@Inject
@Premium
private Customer customer;
public String saveCustomer() {
PremiumCustomer premiumCustomer =
(PremiumCustomer) customer;
logger.log(Level.INFO, "Saving the following information n"
+ "{0} {1}, discount code = {2}",
new Object[]{premiumCustomer.getFirstName(),
premiumCustomer.getLastName(),
premiumCustomer.getDiscountCode()});
//If this was a real application, we would have code to save
//customer data to the database here.
return "premium_customer_confirmation";
}
}
Since we used our @Premium qualifier to decorate the customer field, an instance of the PremiumCustomer class is injected into that field. This is because this class is also decorated with the @Premium qualifier.
As far as our JSF pages go, we simply access our named bean as usual using its name, as shown in the following code;
<?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
>
<h:head>
<title>Create New Premium Customer</title>
</h:head>
<h:body>
<h:form>
<h3>Create New Premium Customer</h3>
<h:panelGrid columns="3">
<h:outputLabel for="firstName" value="First Name"/>
<h:inputText id="firstName"
value="#{premiumCustomer.firstName}"/>
<h:message for="firstName"/>
<h:outputLabel for="middleName" value="Middle Name"/>
<h:inputText id="middleName"
value="#{premiumCustomer.middleName}"/>
<h:message for="middleName"/>
<h:outputLabel for="lastName" value="Last Name"/>
<h:inputText id="lastName"
value="#{premiumCustomer.lastName}"/>
<h:message for="lastName"/>
<h:outputLabel for="email" value="Email Address"/>
<h:inputText id="email"
value="#{premiumCustomer.email}"/>
<h:message for="email"/>
<h:outputLabel for="discountCode" value="Discount Code"/>
<h:inputText id="discountCode"
value="#{premiumCustomer.discountCode}"/>
<h:message for="discountCode"/>
<h:panelGroup/>
<h:commandButton value="Submit"
action="#{premiumCustomerController.saveCustomer}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>
In this example, we are using the default name for our bean, which is the class name with the first letter switched to lowercase.
Now, we are ready to test our application:
After submitting the page, we can see the confirmation page.
Stereotypes
A CDI stereotype allows us to create new annotations that bundle up several CDI annotations. For example, if we need to create several CDI named beans with a scope of session, we would have to use two annotations in each of these beans, namely @Named and @SessionScoped. Instead of having to add two annotations to each of our beans, we could create a stereotype and annotate our beans with it.
To create a CDI stereotype in NetBeans, we simply need to create a new file by selecting the Contexts and Dependency Injection category and the Stereotype file type.
Then, we need to enter a name and package for our new stereotype.
At this point, NetBeans generates the following code:
package com.ensode.cdiintro.stereotype;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.inject.Stereotype;
@Stereotype
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface NamedSessionScoped {
}
Now, we simply need to add the CDI annotations that we want the classes annotated with our stereotype to use. In our case, we want them to be named beans and have a scope of session; therefore, we add the @Named and @SessionScoped annotations as shown in the following code:
package com.ensode.cdiintro.stereotype;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Stereotype;
import javax.inject.Named;
@Named
@SessionScoped
@Stereotype
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface NamedSessionScoped {
}
Now we can use our stereotype in our own code:
package com.ensode.cdiintro.beans;
import com.ensode.cdiintro.stereotype.NamedSessionScoped;
import java.io.Serializable;
@NamedSessionScoped
public class StereotypeClient implements Serializable {
private String property1;
private String property2;
public String getProperty1() {
return property1;
}
public void setProperty1(String property1) {
this.property1 = property1;
}
public String getProperty2() {
return property2;
}
public void setProperty2(String property2) {
this.property2 = property2;
}
}
We annotated the StereotypeClient class with our NamedSessionScoped stereotype, which is equivalent to using the @Named and @SessionScoped annotations.
Interceptor binding types
One of the advantages of EJBs is that they allow us to easily perform aspect-oriented programming (AOP) via interceptors. CDI allows us to write interceptor binding types; this lets us bind interceptors to beans and the beans do not have to depend on the interceptor directly. Interceptor binding types are annotations that are themselves annotated with @InterceptorBinding.
Creating an interceptor binding type in NetBeans involves creating a new file, selecting the Contexts and Dependency Injection category, and selecting the Interceptor Binding Type file type.
Then, we need to enter a class name and select or enter a package for our new interceptor binding type.
At this point, NetBeans generates the code for our interceptor binding type:
package com.ensode.cdiintro.interceptorbinding;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface LoggingInterceptorBinding {
}
The generated code is fully functional; we don't need to add anything to it. In order to use our interceptor binding type, we need to write an interceptor and annotate it with our interceptor binding type, as shown in the following code:
package com.ensode.cdiintro.interceptor;
import com.ensode.cdiintro.interceptorbinding.LoggingInterceptorBinding;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
@LoggingInterceptorBinding
@Interceptor
public class LoggingInterceptor implements Serializable{
private static final Logger logger = Logger.getLogger(
LoggingInterceptor.class.getName());
@AroundInvoke
public Object logMethodCall(InvocationContext invocationContext)
throws Exception {
logger.log(Level.INFO, new StringBuilder("entering ").append(
invocationContext.getMethod().getName()).append(
" method").toString());
Object retVal = invocationContext.proceed();
logger.log(Level.INFO, new StringBuilder("leaving ").append(
invocationContext.getMethod().getName()).append(
" method").toString());
return retVal;
}
}
As we can see, other than being annotated with our interceptor binding type, the preceding class is a standard interceptor similar to the ones we use with EJB session beans.
In order for our interceptor binding type to work properly, we need to add a CDI configuration file (beans.xml) to our project.
Then, we need to register our interceptor in beans.xml as follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xsi_schemaLocation="http://>
<interceptors>
<class>
com.ensode.cdiintro.interceptor.LoggingInterceptor
</class>
</interceptors>
</beans>
To register our interceptor, we need to set bean-discovery-mode to all in the generated beans.xml and add the <interceptor> tag in beans.xml, with one or more nested <class> tags containing the fully qualified names of our interceptors.
The final step before we can use our interceptor binding type is to annotate the class to be intercepted with our interceptor binding type:
package com.ensode.cdiintro.controller;
import com.ensode.cdiintro.interceptorbinding.LoggingInterceptorBinding;
import com.ensode.cdiintro.model.Customer;
import com.ensode.cdiintro.model.PremiumCustomer;
import com.ensode.cdiintro.qualifier.Premium;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
@LoggingInterceptorBinding
@Named
@RequestScoped
public class PremiumCustomerController {
private static final Logger logger = Logger.getLogger(
PremiumCustomerController.class.getName());
@Inject
@Premium
private Customer customer;
public String saveCustomer() {
PremiumCustomer premiumCustomer = (PremiumCustomer) customer;
logger.log(Level.INFO, "Saving the following information n"
+ "{0} {1}, discount code = {2}",
new Object[]{premiumCustomer.getFirstName(),
premiumCustomer.getLastName(),
premiumCustomer.getDiscountCode()});
//If this was a real application, we would have code to save
//customer data to the database here.
return "premium_customer_confirmation";
}
}
Now, we are ready to use our interceptor. After executing the preceding code and examining the GlassFish log, we can see our interceptor binding type in action.
The lines entering saveCustomer method and leaving saveCustomer method were added to the log by our interceptor, which was indirectly invoked by our interceptor binding type.
Custom scopes
In addition to providing several prebuilt scopes, CDI allows us to define our own custom scopes. This functionality is primarily meant for developers building frameworks on top of CDI, not for application developers. Nevertheless, NetBeans provides a wizard for us to create our own CDI custom scopes.
To create a new CDI custom scope, we need to go to File | New File, select the Contexts and Dependency Injection category, and select the Scope Type file type.
Then, we need to enter a package and a name for our custom scope.
After clicking on Finish, our new custom scope is created, as shown in the following code:
package com.ensode.cdiintro.scopes;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Scope;
@Inherited
@Scope // or @javax.enterprise.context.NormalScope
@Retention(RUNTIME)
@Target({METHOD, FIELD, TYPE})
public @interface CustomScope {
}
To actually use our scope in our CDI applications, we would need to create a custom context which, as mentioned previously, is primarily a concern for framework developers and not for Java EE application developers. Therefore, it is beyond the scope of this article. Interested readers can refer to JBoss Weld CDI for Java Platform, Ken Finnigan, Packt Publishing. (JBoss Weld is a popular CDI implementation and it is included with GlassFish.)
Summary
In this article, we covered NetBeans support for CDI, a new Java EE API introduced in Java EE 6. We provided an introduction to CDI and explained additional functionality that the CDI API provides over standard JSF. We also covered how to disambiguate CDI injected beans via CDI Qualifiers. Additionally, we covered how to group together CDI annotations via CDI stereotypes. We also, we saw how CDI can help us with AOP via interceptor binding types. Finally, we covered how NetBeans can help us create custom CDI scopes.
Resources for Article:
Further resources on this subject:
Java EE 7 Performance Tuning and Optimization [article]
Java EE 7 Developer Handbook [article]
Java EE 7 with GlassFish 4 Application Server [article]
Read more