TOTD #145: CDI Events - a light-weight producer/consumer in Java EE 6

Contexts & Dependency Injection (JSR 299) defines a standards-based type-safe Dependency Injection mechanism in the Java EE 6 platform. The type-safety comes from the fact that no String-based identifiers are used to identify the dependencies and instead all the information is specified using the Java object model. The loose coupling is possible because the bean requesting injection need not be aware of the lifecycle, concrete implementation, threading model and similar details of the injected bean. The CDI runtime automatically figures out the right bean in the right scope and context and then injects it correctly for the requestor.

CDI Events take loose coupling to the next level by following the Observer pattern. Event producers raise events that are consumed by observers. The event object carries state from producer to consumer.

Code is king, so lets understand the above text with a simple sample. Consider the following POJO that captures the state showing how many pages have been printed:

public class PrintEvent {
    private int pages;

    public PrintEvent(int pages) {
        this.pages = pages;
    }

    public int getPages() {
        return pages;
    }
}

A producer of this event will look like:

@Inject Event printEvent;

public void print() {
    printEvent.fire(new PrintEvent(5));
}

Inject a "PrintEvent" using @Inject and "fire" the event with the payload and state. And the consumer of this event will look like:

public void onPrint(@Observes PrintEvent event) {
    System.out.println(event.getPages() + " pages printed");
}

Any method with "@Observes" and the observed event type as parameter will receive the event. In this case, this method will observe all the events. However the observer can specify qualifiers to narrow the set of event notifications received. So lets create a CDI qualifier as:

package com.example.events;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface ShortPrintJob {
}

and create two more similar qualifiers as "MediumPrintJob" and "LongPrintJob". The "producer" can then look like:

@Inject @ShortPrintJob Event shortPrint;
@Inject @MediumPrintJob Event mediumPrint;
@Inject @LongPrintJob Event longPrint;

public void print2() {
    shortPrint.fire(new PrintEvent(5));
    mediumPrint.fire(new PrintEvent(30));
    longPrint.fire(new PrintEvent(80));
}

The consumer can then look like:

public void onShortPrint(@Observes @ShortPrintJob PrintEvent event) {
    System.out.println(event.getPages() + " pages printed (short)");
}

public void onMediumPrint(@Observes @MediumPrintJob PrintEvent event) {
    System.out.println(event.getPages() + " pages printed (medium)");
}

public void onLongPrint(@Observes @LongPrintJob PrintEvent event) {
    System.out.println(event.getPages() + " pages printed (long)");
}

Notice how qualifiers are specified in each method to narrow the set of event notifications. This will print the output as:

INFO: 5 pages printed (short)
INFO: 30 pages printed (medium)
INFO: 80 pages printed (long)

In the above case, "producer" is "qualified" by annotating at the Event inject point. CDI also allows for the qualifiers to be dynamically specified. So

@Inject @ShortPrintJob Event shortPrint;
shortPrint.fire(new PrintEvent(5));

can be replaced with

@Inject Event printEvent;
printEvent.select(new AnnotationLiteral<ShortPrintJob>(){}).fire(new PrintEvent(10));

Now, if we kept our original method "onPrint" in the consumer as well then each fired event will be delivered to this common method and the "qualified" method.

CDI also defines "Transactional observer methods" that receive event notifications during the different phases of a transaction in which the event was fired. There are pre-defined phases IN_PROGRESS, BEFORE_COMPLETION, AFTER_COMPLETION, AFTER_FAILURE, and AFTER_SUCCESS that allows you to receive and track events and take actions, if any, based upon the result. One use case is to keep the cache updated after receiving events when products are added / deleted from the database. Another use case is to it push data to the front end after a long-running transaction is successful.

You can certainly try all of this in GlassFish and NetBeans provides extensive tooling around CDI and broader Java EE 6. Check out screencast #30 for the complete Java EE 6 tooling using NetBeans.

How are you using CDI events ?

Technorati: totd javaee6 cdi events producer consumer glassfish netbeans

Comments:

I'm likin' CDI, but have a couple of questions:

1. Maybe it's a bug, but in Glassfish if I add a qualifier to an inject statement like this:

@Inject @Hot MyBean bean,

if I don't have a producer with the qualifer, then the default is used... That's cool. BUT, if I have a Qualified Decorator, and an unqualified producer, my decorator is ignored... Is this a bug or the way it should behave?

2. Can EE 7 please unify Managed Beans and EJB, please? There's no real need for both @RequestScoped Managed Beans, and @Stateless EJBs (and the same is true for @Singletons/@ApplicationScoped, and @Stateful/@Session) If I need pooling and/or transactional support, let me specify that....

3. Why aren't JPA Entities considered managed beans? I really need to be able to Inject into them to be able to make my Entities real, robust domain objects (State + Behavior).

Posted by Bob on October 06, 2010 at 03:01 PM PDT #

FYI: in NetBeans 6.10 Milestone 2 we've enhanced "Inspect CDI" action to better support CDI Events. Try Inspect CDI action on an event declaration and you will get list of observer methods and vice versa: on an event observer method you can inspect list of event declarations which are being observed by that method. Have fun and let us know if something else is desirable. David

Posted by David on October 07, 2010 at 11:21 AM PDT #

Thanks David, I'll try this with NetBeans 7 M2 and then blog about it.

Posted by Arun Gupta on October 12, 2010 at 02:59 AM PDT #

Looks horrible.

If you want to use a lightweight producer/consumer using a standard java.util.concurrent.BlockingQueue imho is my preferred solution. No need to use this horrible code.

Posted by Peter Veentjer on October 15, 2010 at 03:43 PM PDT #

The artice describes the interface of a publish/subscribe event registry. Producer/consumer refers to a queue that blocks the consumer thread when the queue is empty. Nevertheless, nice article. Is there any standalone implementation or am I forced to go with Glassfish if I want this :-) ?

Cheers, Oliver

Posted by Oliver on October 19, 2010 at 12:18 AM PDT #

Oliver,

You can download standalone Weld from http://seamframework.org/Weld/WeldDistributionDownloads

Posted by Arun Gupta on October 21, 2010 at 02:10 AM PDT #

Post a Comment:
Comments are closed for this entry.
About

profile image
Arun Gupta is a technology enthusiast, a passionate runner, author, and a community guy who works for Oracle Corp.


Java EE 7 Samples

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today