CDI provides a
pluggable architecture allowing you to easily process your own annotations.
Read this article to understand the inner workings of CDI and why this JSR is
so important.
CDI simplifies and sanitizes the API
for DI and AOP like JPA did for ORMs. Through its use ofInstance and @Produces, CDI provides a
pluggable architecture. This is a jaw dropping killer feature of CDI. Master
this and you start to tap into the power of CDI. The last article was just to lay the ground work to the uninitiated for this article.
This article continues our tutorial
of dependency injection with CDI.
This article
covers:
·
How to process annotations for
configuration (injection level and class level)
·
How to use an annotation for both
injection and configuration (@Nonbinding)
·
Using Instance to
manage instances of possible injection targets
·
CDI's plugin architecture for the
masses
With this pluggable
architecture you can write code that finds new dependencies dynamically. CDI
can be a framework to write frameworks. This is why it is so important that CDI
was led through the JSR process.
Just like last time, there are some
instructions on how to run the examples: Source code for this tutorial, and instructions for use. A programming article without working sample code is like a
sandwich with no condiments or dry toast without jelly.
Advanced CDI
tutorial
The faint of heart stop here. All of
the folks who want to understand the inner workings of CDI continue. So far, we
have been at the shallow, warm end of the pool. Things are about to get a
little deeper and colder. If you need to master CDI, then this article if for
you. If you don't know what CDI is then read thefirst CDI DI article.
Advanced: Using
@Produces and InjectionPoint to create configuration annotations
Our ultimate goal
is to define an annotation that we can use to configure the retry count on a
transport. Essentially, we want to pass a retry count to the transport.
We want something that looks like this:
Code Listing: TransportConfig annotations that does configuration
Code Listing: TransportConfig annotations that does configuration
view source
print?
1.@Inject @TransportConfig(retries=2)
2.private ATMTransport transport;
(This was my favorite section to write, because I
wanted to know how to create a annotation configuration from the start.)
Before we do that we need to learn
more about @Produces and InjectionPoints. We are going to
use a producer to read information (meta-data) about an injection point. A
major inflection point for learning how to deal with annotations is the InjectionPoints. The InjectionPoints has all the
metadata we need to process configuration annotations.
An InjectionPoint is a class that has information about an injection point. You can learn
things like what is being decorated, what the target injection type is, what
the source injection type, what is the class of the owner of the member that is
being injected and so forth.
Let's learn about passing an
injection point to @Produces. Below I have
rewritten our simple @Produces example from the previous article, except this time
I pass an InjectionPoint argument into the mix.
Code Listing: TransportFactory getting meta-data about the injection point
view source
print?
01.package org.cdi.advocacy;
02.
03.import javax.enterprise.inject.Produces;
04.import javax.enterprise.inject.spi.InjectionPoint;
05.
06.public class TransportFactory {
07.
08.
09.@Produces ATMTransport createTransport(InjectionPoint injectionPoint) {
10.
11.System.out.println("annotated
" + injectionPoint.getAnnotated());
12.System.out.println("bean
" + injectionPoint.getBean());
13.System.out.println("member
" + injectionPoint.getMember());
14.System.out.println("qualifiers
" + injectionPoint.getQualifiers());
15.System.out.println("type
" + injectionPoint.getType());
16.System.out.println("isDelegate
" + injectionPoint.isDelegate());
17.System.out.println("isTransient
" + injectionPoint.isTransient());
18.
19.return new StandardAtmTransport();
20.}
21.
22.}
Now we just run it and see what it produces. The
above produces this output. Output
view source
print?
01.annotated
AnnotatedFieldImpl[private org.cdi.advocacy.ATMTransport
org.cdi.advocacy.AutomatedTellerMachineImpl.transport]
02.bean
ManagedBeanImpl[AutomatedTellerMachineImpl, {@javax.inject.Named(value=atm), @Default(), @Any()}, name=atm]
03.member private org.cdi.advocacy.ATMTransport
org.cdi.advocacy.AutomatedTellerMachineImpl.transport
04.qualifiers [@Default()]
05.type interface org.cdi.advocacy.ATMTransport
06.isDelegate false
07.isTransient false
08.deposit called
09.communicating with bank via Standard transport
It appears from the output that annotated tells us about the area of the program we annotated. It also appears
that bean tells us which bean the injection is
happening on.
From this output you can see that the annotated property on the injectionPoint has information about which language feature (field, constructor
argument, method argument, etc.). In our case it is the field org.cdi.advocacy.AutomatedTellerMachineImpl.transport. is being used as
the target of the injection, it is the thing that was annotated.
From this output you can see that the bean property of the injectionPoint is being used to describe the bean whose member is getting injected. In
this case, it is the AutomatedTellerMachineImplwhose is getting
the field injected.
I won't describe each
property, but as an exercise you can.
Exercise: Look up the InjectionPoint in the API documentation. Find out what the
other properties mean. How might you use this meta-data? Can you think of a use
case or application where it might be useful? Send me your answers on the CDI
group mailing list. The first one to send gets put on the CDI wall of fame. (All others
get honorable mentions.)
Drilling further
you can see what is in the beans and annotated properties.
Code Listing: TransportFactory.createTransport drilling further into the meta-data about the injection point
view source
print?
01.@Produces ATMTransport createTransport(InjectionPoint injectionPoint) {
02.
03.System.out.println("annotated
" + injectionPoint.getAnnotated());
04.System.out.println("bean
" + injectionPoint.getBean());
05.System.out.println("member
" + injectionPoint.getMember());
06.System.out.println("qualifiers
" + injectionPoint.getQualifiers());
07.System.out.println("type
" + injectionPoint.getType());
08.System.out.println("isDelegate
" + injectionPoint.isDelegate());
09.System.out.println("isTransient
" + injectionPoint.isTransient());
10.
11.Bean<?> bean =
injectionPoint.getBean();
12.
13.System.out.println("bean.beanClass
" + bean.getBeanClass());
14.System.out.println("bean.injectionPoints
" + bean.getInjectionPoints());
15.System.out.println("bean.name
" + bean.getName());
16.System.out.println("bean.qualifiers
" + bean.getQualifiers());
17.System.out.println("bean.scope
" + bean.getScope());
18.System.out.println("bean.stereotypes
" + bean.getStereotypes());
19.System.out.println("bean.types
" + bean.getTypes());
20.
21.Annotated annotated =
injectionPoint.getAnnotated();
22.System.out.println("annotated.annotations
" + annotated.getAnnotations());
23.System.out.println("annotated.annotations
" + annotated.getBaseType());
24.System.out.println("annotated.typeClosure
" + annotated.getTypeClosure());
25.
26.return new StandardAtmTransport();
27.}
Now we are cooking with oil. Throw
some gas on that flame. Look at the wealth of information that theInjectionPoint defines.
Output
view source
print?
01....
02.bean.beanClass class org.cdi.advocacy.AutomatedTellerMachineImpl
03.bean.injectionPoints
[InjectionPointImpl[privateorg.cdi.advocacy.ATMTransport
org.cdi.advocacy.AutomatedTellerMachineImpl.transport]]
04.bean.name atm
05.bean.qualifiers [@javax.inject.Named(value=atm), @Default(), @Any()]
06.bean.scope interface javax.enterprise.context.Dependent
07.bean.stereotypes []
08.bean.types [class org.cdi.advocacy.AutomatedTellerMachineImpl, interfaceorg.cdi.advocacy.AutomatedTellerMachine, class java.lang.Object]
09.annotated.annotations
AnnotationSet[@javax.inject.Inject()]
10.annotated.annotations interface org.cdi.advocacy.ATMTransport
11.annotated.typeClosure
[interface org.cdi.advocacy.ATMTransport, classjava.lang.Object]
12....
We see that bean.beanClass gives up the class of the bean that is getting the injected field.
Remember that one, we will use it later.
We can see that bean.qualifiers gives up the list of qualifiers for the AutomatedTellerMachineImpl.
We can also see that annotated.annotations gives us the list of annotations that are associated with the injected
field. We will use this later to pull the configuration annotation and configure
the transport with it.
Exercise: Look up the Bean and Annotated in the API documentation. Find out what the
other properties mean. How might you use this meta-data? Can you think of a use
case or application where it might be useful? Send me your answers on the CDI
group mailing list. The first one to send gets put on the CDI wall of fame. (All others
get honorable mentions.)
Ok now that we armed with an idea of
what an Injection point is. Let's get configuring our transport.
First let's define an TransportConfig annotation. This is just a plain runtime annotation as follows:
Code Listing: TransportConfig an annotation used for configuration
view source
print?
01.package org.cdi.advocacy;
02.
03.
04.import java.lang.annotation.Retention;
05.import java.lang.annotation.Target;
06.import static java.lang.annotation.ElementType.*;
07.import static java.lang.annotation.RetentionPolicy.*;
08.
09.
10.
11.@Retention(RUNTIME) @Target({TYPE, METHOD,
FIELD, PARAMETER})
12.public @interface TransportConfig {
13.int retries() default 5;
14.}
Notice that this annotation has one
member retries, which we will use to configure the ATMTransport(transport).
Now go ahead and
use this to decorate the injection point as follows:
Code Listing: AutomatedTellerMachineImpl using TransportConfig to configure retries
view source
print?
1.public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {
2.
3.@Inject @TransportConfig(retries=2)
4.private ATMTransport transport;
Once it is
configured when you run it, you will see the following output from our
producer:
Output
view source
print?
1.annotated.annotations AnnotationSet[@javax.inject.Inject(),@org.cdi.advocacy.TransportConfig(retries=2)]
This means the
annotation data is there. We just need to grab it and use it. Stop and ponder
on this a bit. This is pretty cool. The producer allows me to customize how
annotations are consumed. This is powerful stuff and one of the many extension
points available to CDI. CDI was meant to be extensible. It is the first
mainstream framework that encourages you to consume your own annotation data.
This not some obscure framework feature. This is in the main usage.
Please recall that the injectionPoint.annotated.annotations gives us the list of annotations that are associated with the injected
field, namely, the transport field of the AutomatedTellerMachineImpl. Now we can use
this to pull the configuration annotation and configure the transport with it.
The party is rolling now.
Now we need to change the transport
implementations to handle setting retires. Since this is an example, I will do
this simply by adding a new setter method for retires (setRetries) to theATMTranport interface like so:
Code Listing: ATMTransport adding a retries property
view source
print?
1.package org.cdi.advocacy;
2.
3.public interface ATMTransport {
4.public void communicateWithBank(byte[] datapacket);
5.public void setRetries(int retries);
6.}
Then we need to change each of the
transports to handle this new retries property as follows:
Code Listing: StandardAtmTransport adding a retries property
view source
print?
01.package org.cdi.advocacy;
02.
03.public class StandardAtmTransport implements ATMTransport {
04.
05.private int retries;
06.
07.public void setRetries(int retries) {
08.this.retries = retries;
09.}
10.
11.
12.public void communicateWithBank(byte[] datapacket) {
13.System.out.println("communicating
with bank via Standard transport retries=" + retries);
14.}
15.
16.}
Continue reading... Click on the navigation links below the author bio
to read the other pages of this article.
Be sure to check out part I of this
series as well: CDI DI Tutorial!
About the author
This article was written with CDI advocacy in mind by Rick Hightower with some collaboration from others. Rick Hightower has worked as a CTO, Director of Development and a Developer for the last 20 years. He has been involved with J2EE since its inception. He worked at an EJB container company in 1999. He has been working with Java since 1996, and writing code professionally since 1990. Rick was an early Spring enthusiast. Rick enjoys bouncing back and forth between C, Python, Groovy and Java development.
Although not a fan of EJB 3, Rick is a big fan
of the potential of CDI and thinks that EJB 3.1 has come a lot closer to the
mark.
Rick Hightower is CTO of Mammatus and is an expert on Java and Cloud Computing. Rick is invovled
in Java CDI advocacy and Java EE. CDI Implementations - Resin
Candi - Seam Weld - Apache OpenWebBeans
Now we just change
the producer to grab the new annotation and configure the transport as follows:
(For clarity I took out all of the Sysout.prinltns.)
Code Listing: TransportFactory using the annotation configuration to configure a new instance of the transport
view source
print?
01.package org.cdi.advocacy;
02.
03....
04.import javax.enterprise.inject.spi.Annotated;
05.import javax.enterprise.inject.spi.Bean;
06.import javax.enterprise.inject.spi.InjectionPoint;
07.
08.public class TransportFactory {
09.@Produces ATMTransport createTransport(InjectionPoint injectionPoint) {
10.
11.Annotated annotated =
injectionPoint.getAnnotated();
12.
13.TransportConfig
transportConfig = annotated.getAnnotation(TransportConfig.class);
14.
15.
16.
17.StandardAtmTransport
transport = new StandardAtmTransport();
18.
19.transport.setRetries(transportConfig.retries());
20.return transport;
21.}
22.
23.}
(Side Note: we are
missing a null pointer check. The annotation configuration could be null if the
user did not set it, you may want to handle this. The example is kept
deliberately short.)
The code just gets
the annotation and shoves in the retires into the transport, and then just
returns the transport.
We now have a
producers that can use an annotation to configure an injection.
Here is our new
output:
Output
view source
print?
1....
2.deposit called
3.communicating with bank via Standard transport
retries=2
You can see our
retries are there as we configured them in the annotation. Wonderful!
Annotation processing for the masses!
Ok we are done with
this example. What remains is a victory lap. Let's say we had multiple
transports in a single ATM and you wanted to configure all of the outputs at
once.
Let's configure the transport based
on an annotation in the parent class of the injection target, namely,AutomatedTellerMachine.
Code Listing: TransportFactory using the annotation configuration from class not field to configure a new instance of the transport
view source
print?
01.public class TransportFactory {
02.@Produces ATMTransport createTransport(InjectionPoint injectionPoint) {
03.
04.Bean<?> bean =
injectionPoint.getBean();
05.TransportConfig
transportConfig = bean.getBeanClass().getAnnotation(TransportConfig.class);
06.
07.StandardAtmTransport
transport = new StandardAtmTransport();
08.
09.transport.setRetries(transportConfig.retries());
10.return transport;
It is an exercise
for the reader to make the injection level annotation (from the last example)
override the class level annotations. As always, if you are playing along in
the home version of CDI hacker, send me your solution. Best solution gets my
admiration.
Output
view source
print?
1.deposit called
2.communicating with bank via Standard transport
retries=7
Exercise: Make the injection from the
field override the injection from the class. It is a mere matter of Java code.
Send me your solution on the CDI group mailing list. The first one to
send gets put on the CDI wall of fame. (All others get honorable mentions.)
Advanced Using
@Nonbinding to combine a configuration annotation and a qualifier annotation
into one annotation
In the section titled "Using
@Qualfiers with members to discriminate injection and stop the explosion of
annotation creation" we covered adding additional members
to a qualifier annotation and then in *"Advanced: Using @Produces and
InjectionPoint to create configuration annotations"* we talked about how
to write an annotation to configure an injection. Wouldn't be great if we could
combine these two concepts into one annotation?
The problem is that
qualifier members are used to do the discrimination. We need some qualifier
members that are not used for configuration not discrimination.
To make an qualifier member just a
configuration member use @Nonbinding annotation as follows:
Code Listing: Transport qualifier annotation using @Nonbinding to add configuration retries param
view source
print?
01.package org.cdi.advocacy;
02.
03.import java.lang.annotation.Retention;
04.import java.lang.annotation.Target;
05.import static java.lang.annotation.ElementType.*;
06.import static java.lang.annotation.RetentionPolicy.*;
07.
08.import javax.enterprise.util.Nonbinding;
09.import javax.inject.Qualifier;
10.
11.
12.@Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER})
13.public @interface Transport {
14.TransportType type() default TransportType.STANDARD;
15.int priorityLevel() default -1;
16.String name() default "standard";
17.
18.@Nonbinding int retries() default 5;
19.
20.}
Now let's add the setRetries to the Fast
Transport:
Code Listing: Transport qualifier annotation using @Nonbinding to add configuration retries param
Code Listing: Transport qualifier annotation using @Nonbinding to add configuration retries param
view source
print?
01.package org.cdi.advocacy;
02.
03.@Transport(type=TransportType.STANDARD, priorityLevel=1, name="super")
04.public class SuperFastAtmTransport implements ATMTransport {
05.private int retries=0;
06.
07.public void setRetries(int retries) {
08.this.retries=retries;
09.}
10.
11.
12.public void communicateWithBank(byte[] datapacket) {
13.System.out.println("communicating
with bank via the Super Fast transport retries=" + retries);
14.}
15.
16.}
Then we use it as follows:
view source
print?
1.public class AutomatedTellerMachineImpl implements AutomatedTellerMachine {
2.
3.@Inject @Transport(type=TransportType.STANDARD, priorityLevel=1, name="super", retries=9)
4.private ATMTransport
transport;
5....
Ouptut
view source
print?
1.deposit called
2.communicating with bank via Standard transport
retries=9
The final result is
we have one annotation that does both qualification and configuration. Booyah!
Exercise: There is an easter egg in
this example. There is concept we talked about earlier (in the qualifier
discrimination but never added. Please find it and describe it. What are some
potential problems of using this approach? Send me your answers on the CDI
group mailing list. The first one to send gets put on the CDI wall of fame. (All others
get honorable mentions.)
Advanced: Using
Instance to inject transports
The use of the class Instance allows you to dynamically look up instances of a certain type. This is
the plugin architecture for the masses, built right into CDI. Grok this and you
will not only understand CDI but have a powerful weapon in your arsenal of mass
programming productivity.
These instances can be instances that
are in a jar files. For example the AutomatedTellerMachinecould work with
transports that did not even exist when the AutomatedTellerMachine was created. If you don't grok that, read the last sentence again. You
are tapping into the scanning capabilities of CDI. This power is there for the
taking. The Instance class is one of the things that makes CDI so cool and flexible. In this
section, I hope to give it some justice while still keeping the example small
and understandable.
Let's say we wanted
to work with multiple transports. But we don't know which transport is
configured and on the classpath. It could be that the build was special for a
certain type of transport, and it just does not exist on the classpath. Suspend
disbelief for a moment and let's look at the code.
No comments:
Post a Comment