Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (15.81 MB, 597 trang )
Chapter 2 ■ Context and Dependency Injection
Writing a CDI Bean
A CDI Bean can be any kind of class that contains business logic. It may be called directly from Java code via injection,
or it may be invoked via EL from a JSF page. As you can see in Listing 2-1, a bean is a POJO that doesn’t inherit or
extend from anything, can inject references to other beans (@Inject), has its life cycle managed by the container
(@PostConstruct), and can get its method invocation intercepted (here @Transactional is an interceptor
binding—more on that later).
Listing 2-1. A BookService Bean Using Injection, Life-Cycle Management, and Interception
public class BookService {
@Inject
private NumberGenerator numberGenerator;
@Inject
private EntityManager em;
private Date instanciationDate;
@PostConstruct
private void initDate() {
instanciationDate = new Date();
}
@Transactional
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
book.setInstanciationDate(instanciationDate);
em.persist(book);
return book;
}
}
Anatomy of a CDI Bean
According to the CDI 1.1 specification, the container treats any class that satisfies the following conditions as a
CDI Bean:
•
It is not a non-static inner class,
•
It is a concrete class, or is annotated @Decorator, and
•
It has a default constructor with no parameters, or it declares a constructor annotated
@Inject.
Then a bean can have an optional scope, an optional EL name, a set of interceptor bindings, and an optional
life-cycle management.
28
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Dependency Injection
Java is an object-oriented programming language, meaning that the real world is represented using objects.
A Book class represents a copy of “H2G2,” a Customer represents you, and a PurchaseOrder represents you buying
this book. These objects depend on each other: a book can be read by a customer and a purchase order refers to
several books. This dependence is one value of object-oriented design.
For example, the process of creating a book (BookService) can be reduced to instantiating a Book object,
generating a unique number using another service (NumberGenerator), and persisting the book to a database. The
NumberGenerator service can generate an ISBN number made of 13 digits or an older format called ISSN with 8 digits.
The BookService would then end up depending on either an IsbnGenerator or an IssnGenerator according to some
condition or environment.
Figure 2-3 shows a class diagram of the NumberGenerator interface that has one method (String
generateNumber()) and is implemented by IsbnGenerator and IssnGenerator. The BookService depends on the
interface to generate a book number.
Figure 2-3. Class diagram with the NumberGenerator interface and implementations
How would you connect a BookService to the ISBN implementation of the NumberGenerator interface? One
solution is to use the good old new keyword as shown in Listing 2-2.
Listing 2-2. A BookService POJO Creating Dependencies Using the New Keyword
public class BookService {
private NumberGenerator numberGenerator;
public BookService() {
this.numberGenerator = new IsbnGenerator();
}
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
return book;
}
}
29
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
The code in Listing 2-2 is pretty simple and does the job. In the constructor the BookService creates an instance
of IsbnGenerator and affects it to the numberGenerator attribute. Invoking the numberGenerator.generateNumber()
method would generate a 13-digit number.
But what if you want to choose between implementations and not just get wired to the IsbnGenerator? One
solution is to pass the implementation to the constructor and leave an external class to choose which implementation
it wants to use (see Listing 2-3).
Listing 2-3. A BookService POJO Choosing Dependencies Using the Constructor
public class BookService {
private NumberGenerator numberGenerator;
public BookService(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
return book;
}
}
So now an external class could use the BookService with the implementation it needs.
BookService bookService = new BookService(new IsbnGenerator());
BookService bookService = new BookService(new IssnGenerator());
This illustrates what inversion of control is: the control of creating the dependency between BookService and
NumberGenerator is inverted because it’s given to an external class, not the class itself. Since you end up connecting
the dependencies yourself, this technique is referred to as construction by hand. In the preceding code we used the
constructor to choose implementation (constructor injection), but another common way is to use setters (setter
injection). However, instead of constructing dependencies by hand you can leave it for an injector (i.e., CDI) to do.
@Inject
As Java EE is a managed environment you don’t need to construct dependencies by hand but can leave the container
to inject a reference for you. In a nutshell, CDI dependency injection is the ability to inject beans into others in a
typesafe way, which means no XML but annotations.
Injection already existed in Java EE 5 with the @Resource, @PersistentUnit or @EJB annotations, for example.
But it was limited to certain resources (datasource, EJB . . .) and into certain components (Servlets, EJBs, JSF backing
bean . . .). With CDI you can inject nearly anything anywhere thanks to the @Inject annotation. Note that in Java EE 7
you can still use the other injection mechanisms (@Resource . . .) but you should consider using @Inject whenever it
is possible (see the “Producers” section later in this chapter).
Listing 2-4 shows how you would inject a reference of the NumberGenerator into the BookService using the
CDI @Inject.
30
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Listing 2-4. BookService Using @Inject to Get a Reference of NumberGenerator
public class BookService {
@Inject
private NumberGenerator numberGenerator;
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
return book;
}
}
As you can see in Listing 2-4, a simple @Inject annotation on the property will inform the container that it has
to inject a reference of a NumberGenerator implementation into the numberGenerator property. This is called the
injection point (the place where the @Inject annotation is). Listing 2-5 shows the IsbnGenerator implementation.
As you can see there are no special annotations and the class implements the NumberGenerator interface.
Listing 2-5. The IsbnGenerator Bean
public class IsbnGenerator implements NumberGenerator {
public String generateNumber() {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}
Injection Points
The @Inject annotation defines an injection point that is injected during bean instantiation. Injection can occur via
three different mechanisms: property, setter, or constructor.
Until now, in all the previous code examples, you’ve seen the @Inject annotation on attributes (properties).
@Inject
private NumberGenerator numberGenerator;
Notice that it isn’t necessary to create a getter and a setter method on an attribute to use injection. CDI can access
an injected field directly (even if it’s private), which sometimes helps eliminate some wasteful code. But instead of
annotating the attributes, you can add the @Inject annotation on a constructor as follows:
@Inject
public BookService (NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
But the rule is that you can only have one constructor injection point. The container is the one doing injection,
not you (you can’t invoke a constructor in a managed environment); therefore, there is only one bean constructor
allowed so that the container can do its work and inject the right references.
31
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
The other choice is to use setter injection, which looks like constructor injection. You just need to annotate the
setter with @Inject.
@Inject
public void setNumberGenerator(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
You may ask, “When should I use a field over a constructor or setter injection?” There is no real technical answer
to that question; it’s a matter of your own personal taste. In a managed environment, the container is the one doing all
the injection’s work; it just needs the right injection points.
Default Injection
Assume that NumberGenerator only has one implementation (IsbnGenerator). CDI will then be able to inject it
simply by using @Inject on its own.
@Inject
private NumberGenerator numberGenerator;
This is termed default injection. Whenever a bean or injection point does not explicitly declare a qualifier, the
container assumes the qualifier @javax.enterprise.inject.Default. In fact, the following code is identical to
the previous one:
@Inject @Default
private NumberGenerator numberGenerator;
@Default is a built-in qualifier that informs CDI to inject the default bean implementation. If you define a bean
with no qualifier, the bean automatically has the qualifier @Default. So code in Listing 2-6 is identical to the one
in Listing 2-5.
Listing 2-6. The IsbnGenerator Bean with the @Default Qualifier
@Default
public class IsbnGenerator implements NumberGenerator {
public String generateNumber() {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}
If you only have one implementation of a bean to inject, the default behavior applies and a straightforward
@Inject will inject the implementation. The class diagram in Figure 2-4 shows the @Default implementation
(IsbnGenerator) as well as the default injection point (@Inject @Default). But sometimes you have to choose
between several implementations. That’s when you need to use qualifiers.
32
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Figure 2-4. Class diagram with @Default injection
Qualifiers
At system initialization time, the container must validate that exactly one bean satisfying each injection point exists.
Meaning that if no implementation of NumberGenerator is available, the container would inform you of an unsatisfied
dependency and will not deploy the application. If there is only one implementation, injection will work using the
@Default qualifier (see the diagram in Figure 2-4). If more than one default implementation were available, the
container would inform you of an ambiguous dependency and will not deploy the application. That’s because the
typesafe resolution algorithm fails when the container is unable to identify exactly one bean to inject.
So how does a component choose which implementation (IsbnGenerator or IssnGenerator) is to get injected?
Most frameworks heavily rely on external XML configuration to declare and inject beans. CDI uses qualifiers, which
basically are Java annotations that bring typesafe injection and disambiguate a type without having to fall back on
String-based names.
Let’s say we have an application with a BookService that creates books with a 13-digit ISBN number and a
LegacyBookService that creates books with an 8-digit ISSN number. As you can see in Figure 2-5, both services inject
a reference of the same NumberGenerator interface. The services distinguish between the two implementations by
using qualifiers.
Figure 2-5. Services using qualifiers for non-ambiguous injection
A qualifier represents some semantics associated with a type that is satisfied by some implementation of that
type. It is a user-defined annotation, itself annotated with @javax.inject.Qualifer. For example, we could introduce
qualifiers to represent 13- and 8-digit number generators both shown in Listing 2-7 and Listing 2-8.
Listing 2-7. The ThirteenDigits Qualifier
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface ThirteenDigits { }
33
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Listing 2-8. The EightDigits Qualifier
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface EightDigits { }
Once you have defined the needed qualifiers, they must be applied on the appropriate implementation. As you
can see in both Listing 2-9 and Listing 2-10, the @ThirteenDigits qualifier is applied to the IsbnGenerator bean and
@EightDigits to IssnGenerator.
Listing 2-9. The IsbnGenerator Bean with the @ThirteenDigits Qualifier
@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {
public String generateNumber() {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}
Listing 2-10. The IssnGenerator Bean with the @EightDigits Qualifier
@EightDigits
public class IssnGenerator implements NumberGenerator {
public String generateNumber() {
return "8-" + Math.abs(new Random().nextInt());
}
}
These qualifiers are then applied to injection points to distinguish which implementation is required by the
client. In Listing 2-11 the BookService explicitly defines the 13-digit implementation by injecting a reference of the
@ThirteenDigits number generator and in Listing 2-12 the LegacyBookService injects the 8-digit implementation.
Listing 2-11. BookService Using the @ThirteenDigits NumberGenerator Implementation
public class BookService {
@Inject @ThirteenDigits
private NumberGenerator numberGenerator;
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
return book;
}
}
34
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Listing 2-12. LegacyBookService Using the @EightDigits NumberGenerator Implementation
public class LegacyBookService {
@Inject @EightDigits
private NumberGenerator numberGenerator;
public Book createBook(String title, Float price, String description) {
Book book = new Book(title, price, description);
book.setIsbn(numberGenerator.generateNumber());
return book;
}
}
For this to work you don’t need external configuration; that’s why CDI is said to use strong typing. You can
rename your implementations to whatever you want, rename your qualifier—the injection point will not change
(that’s loose coupling). As you can see, CDI is an elegant way to have typesafe injection. But if you start creating
annotations each time you need to inject something, your application will end up being very verbose. That’s when
qualifiers with members can help you.
Qualifiers with Members
Each time you need to choose between implementations, you create a qualifier (i.e., an annotation). So if you need an
extra two digits and a ten-digit number generator you will create extra annotations (e.g., @TwoDigits, @EightDigits,
@TenDigits, @ThirteenDigits). Imagine that the generated numbers can either be odd or even, you would then end
up with an large number of annotations: @TwoOddDigits, @TwoEvenDigits, @EightOddDigits, etc. One way to avoid
the multiplication of annotations is to use members.
In our example we could replace all these qualifiers by using the single qualifier @NumberOfDigits with an
enumeration as a value and a Boolean for the parity (see Listing 2-13).
Listing 2-13. The @NumberOfDigits with a Digits Enum and a Parity Boolean
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface NumberOfDigits {
Digits value();
boolean odd();
}
public enum Digits {
TWO,
EIGHT,
TEN,
THIRTEEN
}
35
www.it-ebooks.info
Chapter 2 ■ Context and dependenCy InjeCtIon
The manner in which you would use this qualifier with members doesn’t change from what you’ve seen so far.
The injection point will qualify the needed implementation by setting the annotation members as follows:
@Inject @NumberOfDigits(value = Digits.THIRTEEN, odd = false)
private NumberGenerator numberGenerator;
And the concerned implementation will do the same.
@NumberOfDigits(value = Digits.THIRTEEN, odd = false)
public class IsbnEvenGenerator implements NumberGenerator {...}
Multiple Qualifiers
Another way of qualifying a bean and an injection point is to specify multiple qualifiers. So instead of having multiple
qualifiers for parity (@TwoOddDigits, @TwoEvenDigits . . .) or having a qualifier with members (@NumberOfDigits),
we could have used two different set of qualifiers: one set for the parity (@Odd and @Even) and another one for the
number of digits. This is how you could qualify a generator of 13 even digits.
@ThirteenDigits @Even
public class IsbnEvenGenerator implements NumberGenerator {...}
The injection point would use the same syntax.
@Inject @ThirteenDigits @Even
private NumberGenerator numberGenerator;
Then only a bean that has both qualifier annotations would be eligible for injection. Qualifiers should be
meaningful. Having the right names and granularity of qualifiers is important for an application.
Alternatives
Qualifiers let you choose between multiple implementations of an interface at development time. But sometimes you
want to inject an implementation depending on a particular deployment scenario. For example, you may want to use
a mock number generator in a testing environment.
Alternatives are beans annotated with the special qualifier javax.enterprise.inject.Alternative. By default
alternatives are disabled and need to be enabled in the beans.xml descriptor to make them available for instantiation
and injection. Listing 2-14 shows a mock number generator alternative.
Listing 2-14. A Default Mock Generator Alternative
@Alternative
public class MockGenerator implements NumberGenerator {
public String generateNumber() {
return "MOCK";
}
}
36
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
As you can see in Listing 2-14, the MockGenerator implements the NumberGenerator interface as usual. It is
annotated with @Alternative, meaning that CDI treats it as the default alternative of the NumberGenerator. As in
Listing 2-6, this default alternative could have used the @Default built-in qualifier as follows:
@Alternative @Default
public class MockGenerator implements NumberGenerator {...}
Instead of a default alternative, you can specify the alternative by using qualifiers. For example, the following
code tells CDI that the alternative of a 13-digit number generator is the mock:
@Alternative @ThirteenDigits
public class MockGenerator implements NumberGenerator {...}
By default, @Alternative beans are disabled and you need to explicitly enable them in the beans.xml descriptor
as shown in Listing 2-15.
Listing 2-15. The beans.xml Deployment Descriptor Enabling an Alternative
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">
In terms of injection point, nothing changes. So your client code is not impacted. The code that follows injects
the default implementation of a number generator. If the alternative is enabled, then the MockGenerator defined in
Listing 2-14 will be injected.
@Inject
private NumberGenerator numberGenerator;
You can have several beans.xml files declaring several alternatives depending on your environment
(development, production, test . . .).
Producers
I’ve shown you how to inject CDI Beans into other CDI Beans. But you can also inject primitives (e.g., int, long,
float . . .), array types and any POJO that is not CDI enabled, thanks to producers. By CDI enabled I mean any class
packaged into an archive containing a beans.xml file.
By default, you cannot inject classes such as a java.util.Date or java.lang.String. That’s because all these
classes are packaged in the rt.jar file (the Java runtime environment classes) and this archive does not contain a
beans.xml deployment descriptor. If an archive does not have a beans.xml under the META-INF directory, CDI will not
trigger bean discovery and POJOs will not be able to be treated as beans and, thus, be injectable. The only way to be
able to inject POJOs is to use producer fields or producer methods as shown in Listing 2-16.
37
www.it-ebooks.info
Chapter 2 ■ Context and Dependency Injection
Listing 2-16. Producer Fields and Methods
public class NumberProducer {
@Produces @ThirteenDigits
private String prefix13digits = "13-";
@Produces @ThirteenDigits
private int editorNumber = 84356;
@Produces @Random
public double random() {
return Math.abs(new Random().nextInt());
}
}
The NumberProducer class in Listing 2-16 has several attributes and methods all annotated with
javax.enterprise.inject.Produces. This means that all the types and classes produced can now be injected
with @Inject using a qualifier (@ThirteenDigits, @EightDigits or @Random).
The producer method (random() in Listing 2-16) is a method that acts as a factory of bean instances. It allows
the return value to be injected. We can even specify a qualifier (e.g., @Random), a scope, and an EL name (as you will
see later). A producer field (prefix13digits and editorNumber) is a simpler alternative to a producer method and it
doesn’t have any business code. It is just a property that becomes injectable.
In Listing 2-9 the IsbnGenerator generates an ISBN number with the formula "13-84356-" + Math.abs(new
Random().nextInt()). Using the NumberProducer (Listing 2-16) we can use the produced types to change this
formula. In Listing 2-17 the IsbnGenerator now injects both a String and an integer with @Inject @ThirteenDigits
representing the prefix ("13-") and the editor identifier (84356) of an ISBN number. The random number is injected
with @Inject @Random and returns a double.
Listing 2-17. IsbnGenerator Injecting Produced Types
@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {
@Inject @ThirteenDigits
private String prefix;
@Inject @ThirteenDigits
private int editorNumber;
@Inject @Random
private double postfix;
public String generateNumber() {
return prefix + editorNumber + postfix;
}
}
In Listing 2-17 you can see strong typing in action. Using the same syntax (@Inject @ThirteenDigits), CDI
knows that it needs to inject a String, an integer, or an implementation of a NumberGenerator. The advantage of using
injected types (Listing 2-17) rather than a fixed formula (Listing 2-9) for generating numbers is that you can use all the
CDI features such as alternatives (and have an alternative ISBN number generator algorithm if needed).
38
www.it-ebooks.info