In the previous part of CDI, we saw some injection, qualifiers and scope. Now, it’s time to browse through more advanced features.
Producers
Previous examples cannot resolve all our use-cases. Some of these include:
- injection of random values
- injection of context-dependent value
- in general, places where the injection process cannot be narrowed down to a simple
new()
These hint at a very well-known pattern, the factory. Factories are implemented in JSR-299 as producers.
Let’s take a simple example, the injection of a connection from a data source. The code that gets the connection either creates it with a direct connection to the database or retrieves it from a data source pool. In the latest case, the following code would fit:
public @interface FromDataSource {}
public class ConnectionProducer {
@Produces @FromDataSource
public Connection getConnection() throws Exception {
Context ctx = new InitialContext();
// Read the data source name from web.xml
String name = ...
DataSource ds = (DataSource) ctx.lookup(name);
return ds.getConnection();
}
}
Interceptors
With Java EE 6, you can harness the power of AOP without AOP. Like in the previous example, using interceptors is very straightforward. There are 3 steps. Let’s implement a simple timer, for benchmarking purposes.
The first step is the declaration of the interceptor.
To do so, just use the @InterceptorBinding
:
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Benchmarkable{}
The second step is the interceptor implementation. It uses the `@Interceptor annotation, coupled with the previously defined one:
@Benchmarkable @Interceptor
public class BenchmarkInterceptor {
@AroundInvoke
public Object logPerformance(InvocationContext ic) throws Exception {
long start = System.currentTimeMillis();
Object value = ic.proceed();
System.out.println(System.currentTimeMillis() - start);
return value;
}
}
Notice:
- the method annotated with
@AroundInvoke
returns anObject
- it uses a parameter of type
InvocationContext
The last step is to declare such interceptors in `WEB-INF/beans.xml because interceptors are deactivated by default.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>ch.frankel.blog.weld.BenchmarkInterceptor</class>
</interceptors>
</beans>
The beans.xml also tells the container about how to order the interceptors in case they is more than one.
There are two other interceptor types, @PostConstruct
and @AroundTimeout
(for EJB).
Decorators
Decorators, guess what, implement the Decorator design pattern. They are very similar to interceptors with two interesting differences:
- a decorator must implement the interface it is decorating (and yet can be abstract, so it does not have to implement the methods)
- a decorator can have a reference to the object it decorates. It is done through injection
Like interceptors, they must be referenced in the beans.xml file in order to be activated. Let’s take a simple example and create an interface which contract is to return an HTML representation of an object:
public interface Htmlable {
String toHtml();
}
Now I need a date class that knows its HTML representation. I know the design is quite bad but bear with me.
public class HtmlDate extends Date implements Htmlable {
public String toHtml() {
return toString();
}
}
If I want a decorator that puts the HTML inside <strong>
tags, here’s the way:
@Decorator
public class StrongDecorator implements Htmlable {
@Inject @Delegate @Any
private Htmlable html;
public String toHtml() {
return "<strong>" + html.toHtml() + "</strong>";
}
}
Observers
CDI also implements the Observer design pattern, thus at last enabling simple event-driven development paradigm on the Java EE platform. The basis for it is the event type. An event type is a simple POJO.
The Observer is also a POJO: in order for a method of the Observer to be called when an event is fired, just add a parameter of the right event type and annotate it with @Observes
:
public class EventObserverService {
public void afterPostEvent(@Observes PostEvent event) {
... // Do what must be done
}
}
On the other side, the event producer should have an attribute of type javax.enterprise.Event
parameterized with the same event type.
In order to fire the event, call event.fireEvent()
with an event instance:
public class WeldServlet extends HttpServlet {
@Inject
private Event<PostEvent> event;
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
event.fire(new PostEvent());
}
}
Now, when sending a POST
request to the servlet, the afterPostEvent()
method of the EventObserverService
will be called.
Alternatives
In the previous article, I adressed the mock service with calling the setter and passing a newly created instance "by hand". This is all fine and well in a unit testing case, but I also want to manage integration testing. The situation is thus the following:
- there are two implementations of the same interface on the classpath
- you can’t change the servlet code (for example, add a qualifier to the service attribute)
Given the deterministic nature of CDI, you should basically be toast.
In fact, nothing could be further from the truth.
Just use the @Alternative
annotation and CDI will conveniently ignore the annotated class.
@Report(MAIL) @Alternative
public class MockMailReportServiceImpl implements ReportService {
...
}
What’s the point then to create it in the first place? Remember the unused-till-then beans.xml
from above.
It will come to our help, since it accepts <alternative>
tags.
These tags activate the alternatives.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<alternatives>
<class>ch.frankel.blog.weld.service.MockMailReportServiceImpl</class>
</alternatives>
</beans>
As such, you could have two beans.xml:
- a basically empty standard context
- and another integration testing context full of alternatives
Platform
This article was written with GlassFish v3, which uses Weld v1.0.1, as a platform. Weld is CDI reference implementation, and also a part of the Seam framework.
I had no problems using the platform overall, yet, I couldn’t make alternatives, interceptors and decorators work. Strangely enough, all three must be configured in the WEB-INF/beans.xml. I do not know if I did something wrong or if there’s a bug in the current implementation though.
Conclusion
This 2-parts article only brushes at the surface of CDI. Nevertheless, IMHO, it looks very promising and I wish it much success.
To go further:
- CDI, an overview - part 1
- Commons annotations (JSR-250) page:
Commons annotations has annotation for DI in Java (
@Resource
) - CDI (JSR-299) page: amazingly enough, CDI is about DI in Java EE
- Weld’s documentation: Weld is CDI JBoss implementation and also the reference implementation
- Article on the merits of JSR-299 compared to the merits of JSR-330