Search

CDI Dependency Injection - Tutorial II - Annotation Processing and Plugins - Java EE

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