Working with Events in CDI

This tutorial needs a review. You can edit it in GitHub following these contribution guidelines.

Contributed by Andy Gibson

Contexts and Dependency Injection

Contexts and Dependency Injection (CDI), specified by JSR-299, is an integral part of Java EE 6 and provides an architecture that allows Java EE components such as servlets, enterprise beans, and JavaBeans to exist within the lifecycle of an application with well-defined scopes. In addition, CDI services allow Java EE components such as EJB session beans and JavaServer Faces (JSF) managed beans to be injected and to interact in a loosely coupled way by firing and observing events.

This tutorial is based on the blog post by Andy Gibson, entitled Getting Started with CDI part 3 – Events. It demonstrates how to take advantage of the Java EE concept of events, in which you produce and subscribe to (i.e., observe) events occuring in your application in a way that enables you to maintain decoupled code between producers and observers. You use the javax.enterprise.event.Event class to create events, and CDI’s @Observes annotation to subscribe to events.

NetBeans IDE provides built-in support for Contexts and Dependency Injection, including the option of generating the beans.xml CDI configuration file upon project creation, editor and navigation support for annotations, as well as various wizards for creating commonly used CDI artifacts.

To complete this tutorial, you need the following software and resources.

Software or Resource Version Required

NetBeans IDE

7.2, 7.3, 7.4, 8.0, Java EE version

Java Development Kit (JDK)

version 7 or 8

GlassFish server

Open Source Edition 3.x or 4.x

cdiDemo3.zip

n/a

  • The NetBeans IDE Java EE bundle also includes the GlassFish Server Open Source Edition which is a Java EE-compliant container.

  • The solution sample project for this tutorial can be downloaded: cdiDemoComplete.zip

Utilizing Events

In the previous tutorial, Applying @Alternative Beans and Lifecycle Annotations, we had an application that obtained a list of items, validated them and took a specific action when an invalid item was found. Let’s say in the future we want to expand our system to handle all sorts of things happening when we find an invalid item. This could range from an email being sent, changes made to other data such as an order being canceled, or storing a list of rejections in a file or database table. To completely decouple the implementation we can use events in Java EE. Events are raised by the event producer and subscribed to by event observers. Like most of CDI, event production and subscription is type-safe and allows qualifiers to determine which events observers will be observing.

Using the application we’ve been building from the previous tutorials in the series, we don’t require many changes to implement this. We can just provide another implementation of ItemErrorHandler (created in the previous tutorial), which raises an event each time it handles an item. We’ll name this class EventItemHandler, inject it into the ItemProcessor, and use a Notify qualifier to select it for injection.

cdi diagram events
Figure 1. Use CDI injection to loosely couple classes in your application
  1. Begin by extracting the sample start project from the cdiDemo3.zip file (See the table listing required resources above.) Open the project in the IDE by choosing File > Open Project (Ctrl-Shift-O; ⌘-Shift-O on Mac), then selecting the project from its location on your computer.

  2. Create a class named EventItemHandler. Click the New File ( images:./new-file-btn.png[] ) button or press Ctrl-N (⌘-N on Mac) to open the File wizard.

  3. Select the Java category, then select Java Class. Click Next.

  4. Type in EventItemHandler as the class name, then enter exercise4 as the package.

  5. Click Finish. The new class and package are generated, and the class opens in the editor.

  6. Implement EventItemHandler as follows.

public class EventItemHandler *implements ItemErrorHandler* {

    *@Inject
    private Event<Item> itemEvent;

    @Override
    public void handleItem(Item item) {
        System.out.println("Firing Event");
        itemEvent.fire(item);
    }*
}

We inject an instance of an Event where the event payload will be an Item. The event payload is the state data passed from the event producer to the event observer which in this case passes the rejected Item. When the invalid item is handled, we fire the event and pass in the invalid item we received. This event based item handler is injected the same as any other item handler would be so we can swap it in and out whenever we need to and also can substitute it during testing.

  1. Fix all imports. Either right-click in the editor and choose Fix Imports, or press Ctrl-Shift-I (⌘-Shift-I on Mac). Be sure to select javax.enterprise.event.Event as the fully qualified name to the Event class.

fix all imports
Figure 2. Right-click in the editor and choose Fix Imports to invoke the Fix Imports dialog
Press Ctrl-Space on Event to view the Javadoc definition of the class. The fire() method, used above, is also defined.#
event javadoc
Figure 3. Press Ctrl-Space to view Javadoc documentation on classes in the API
  1. Create a qualifier named Notify. (Qualifiers were discussed in Working with Injection and Qualifiers in CDI.)

  2. Click the New File ( images:./new-file-btn.png[] ) button or press Ctrl-N (⌘-N on Mac) to open the File wizard.

  3. Select the Context and Dependency Injection category, then select Qualifier Type. Click Next.

  4. Enter Notify as the class name, then enter exercise4 as the package.

  5. Click Finish. The new Notify qualifier opens in the editor.

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Notify {
}
  1. Add the @Notify annotation to EventItemHandler.

*@Notify*
public class EventItemHandler implements ItemErrorHandler {

    ...
}

We created a @Notify qualifier annotation to identify this error handler for injection and can use it in our ItemProcessor by adding it to the injection point.

  1. Add the @Notify annotation to EventItemHandler’s injection point in `exercise2.ItemProcessor.

@Named
@RequestScoped
public class ItemProcessor {

    @Inject @Demo
    private ItemDao itemDao;

    @Inject
    private ItemValidator itemValidator;

    @Inject *@Notify*
    private ItemErrorHandler itemErrorHandler;

    public void execute() {
        List<Item> items = itemDao.fetchItems();
        for (Item item : items) {
            if (!itemValidator.isValid(item)) {
                itemErrorHandler.handleItem(item);
            }
        }
    }
}

(Use the editor’s hint to add the import statement for exercise4.Notify.)

  1. Click the Run Project ( images:./run-project-btn.png[] ) button to run the project.

  2. In the browser, click the ‘Execute’ button, then return to the IDE and examine the server log in the Output window (Ctrl-4; ⌘-4 on Mac). Because the application that you have been building currently uses the DefaultItemDao to set up four Item`s, then applies the `RelaxedItemValidator on the Item`s, you expect to see the `itemErrorHandler fire twice.

output window
Figure 4. View the GlassFish server log displayed in Output window

Currently though, we don’t have anything observing the event. We can fix this by creating an observer method using the @Observes annotation. This is the only thing needed to observe an event. To demonstrate, we can modify the FileErrorReporter (created in the previous tutorial) to respond to fired events by adding an observer method that calls its handleItem() method.

  1. To make our FileErrorReporter respond to the event, add the following method to the class.

public class FileErrorReporter implements ItemErrorHandler {

    *public void eventFired(@Observes Item item) {
        handleItem(item);
    }*

    ...
}

(Use the editor’s hint to add an import statement for javax.enterprise.event.Observes.)

  1. Run the project (F6; fn-F6 on Mac) again, click the ‘Execute’ button, then return to the IDE and examine the server log in the Output window.

output window2
Figure 5. View the GlassFish server log displayed in Output window

You see that the events are fired on the invalid objects as they were previously, but now the item information is being saved when each event is fired. You can also note that the lifecycle events are being observed, since a FileErrorReporter bean is created and closed for each fired event. (See Applying @Alternative Beans and Lifecycle Annotations for a discussion of lifecycle annotations, e.g., @PostConstruct and @PreDestroy.)

As shown in the above steps, the @Observes annotation provides an easy way to observe an event.

Events and observers can also be annotated with qualifiers to enable observers to only observe specific events for an item. See Getting Started with CDI part 3 – Events for a demonstration.

Handling Scopes

In the present state of the application, a FileErrorReporter bean is created each time the event is raised. In this case, we don’t want to create a new bean each time since we don’t want to open and close the file for each item. We still want to open the file at the start of the process, and then close it once the process it completed. Therefore, we need to consider the scope of the FileErrorReporter bean.

Currently, the FileErrorReporter bean doesn’t have a scope defined. When no scope is defined, CDI uses the default pseudo-dependent scope. What this means in practice is that the bean is created and destroyed over a very short space of time, typically over a method call. In our present scenario, the bean is created and destroyed for the duration of the event being fired. To fix this, we can lengthen the bean’s scope by manually adding a scope annotation. We’ll make this bean @RequestScoped so when the bean is created with the first event being fired, it will continue to exist for the duration of the request. This also means that for any injection points that this bean is qualified to be injected to, the same bean instance will be injected.

  1. Add the @RequestScope annotation and corresponding import statement for javax.enterprise.context.RequestScoped to the FileErrorReporter class.

*import javax.enterprise.context.RequestScoped;*
...

*@RequestScoped*
public class FileErrorReporter implements ItemErrorHandler { ... }

Press Ctrl-Space while you type in order to invoke the editor’s code completion support. When choosing an item through code completion, any associated import statements are automatically added to the class.

code completion
Figure 6. Press Ctrl-Space when typing to invoke code completion suggestions
  1. Run the project (F6; fn-F6 on Mac) again, click the ‘Execute’ button, then return to the IDE and examine the server log in the Output window.

output window3
Figure 7. View the GlassFish server log displayed in Output window

Note that the FileErrorReporter bean is only created when the first event is fired, and is closed after the final event has been fired.

INFO: Firing Event
*INFO: Creating file error reporter*
INFO: Saving exercise2.Item@48ce88f6 [Value=34, Limit=7] to file
INFO: Firing Event
INFO: Saving exercise2.Item@3cae5788 [Value=89, Limit=32] to file
*INFO: Closing file error reporter*

Events are a great way to decouple parts of the system in a modular fashion, as event observers and producers know nothing about each other, nor do they require any configuration for them to do so. You can add pieces of code that subscribe to events with the event producer unaware of the observer. (Without using events, you would typically need to have the event producer call the observer manually.) For example, if someone updates an order status, you could add events to email the sales representative, or notify an account manager if a tech support issue is open for more than a week. These kinds of rules can be implemented without events, but events make it easier to decouple the business logic. Additionally, there is no compile or build time dependency. You can just add modules to your application and they will automatically start observing and producing events.