1. Trang chủ >
  2. Công Nghệ Thông Tin >
  3. Kỹ thuật lập trình >

What’s New in CDI 1.1?

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">





org.agoncal.book.javaee7.chapter02.MockGenerator







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



Xem Thêm
Tải bản đầy đủ (.pdf) (597 trang)

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×