Events - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 7. Events

We're now going to find out about events within CDI, such as how they are fired, how we listen for new events, what we can use as an event payload, and how we can narrow what events we can listen to and fire. All of these we will discover while staying within the boundaries of typesafe Java, making our runtime less error-prone.

Events may be produced and consumed by beans of our application, but there is absolutely no coupling between the bean producing an event and the one consuming it. This allows our beans to interact without any coupling or compile-time dependencies between them.

What is a payload?

The payload of an event, the event object, carries any necessary state from the producer to the consumer and is nothing but an instance of a Java class.

Note

An event object may not contain a type variable, such as <T>.

We can assign qualifiers to an event and thus distinguish an event from other events of an event object type. These qualifiers act like selectors, narrowing the set of events that will be observed for an event object type.

There is no distinction between a qualifier of a bean type and that of an event, as they are both defined with @Qualifier. This commonality provides a distinct advantage when using qualifiers to distinguish between bean types, as those same qualifiers can be used to distinguish between events where those bean types are the event objects.

An event qualifier is shown here:

@Qualifier

@Target( { FIELD, PARAMETER } )

@Retention( RUNTIME )

public @interface Removed {}

How do I listen for an event?

An event is consumed by an observer method, and we inform Weld that our method is used to observe an event by annotating a parameter of the method, the event parameter, with @Observes. The type of event parameter is the event type we want to observe, and we may specify qualifiers on the event parameter to narrow what events we want to observe.

We may have an observer method for all events produced about a Book event type, as follows:

public void onBookEvent(@Observes Book book) { ... }

Or we may choose to only observe when a Book is removed, as follows:

public void onBookRemoval(@Observes @Removed Book book) { ... }

Note

Any additional parameters on an observer method are treated as injection points.

An observer method will receive an event to consume if:

· The observer method is present on a bean that is enabled within our application

· The event object is assignable to the event parameter type of the observer method

How do I fire an event?

We fire events with an instance of the parameterized Event interface, which can be obtained through injection as follows:

@Inject

Event<Book> bookEvent;

We then fire the event using the following:

bookEvent.fire(book);

This event can be consumed by any observer method that matches the event object type and does not specify additional qualifiers. In our case, that would be the first observer method from the previous section.

Note

If an exception is thrown within an observer method, Weld stops further calls to matching observer methods and the exception is rethrown by fire().

Event qualifiers

When we want to specify qualifiers on an event we intend to fire, there are two ways it can be achieved:

· Annotating the Event injection point with qualifiers

· Passing qualifiers to the select() method of Event dynamically

As we've seen in previous examples, specifying qualifiers at an injection point is easy.

@Inject

@Removed

Event<Book> bookRemovedEvent;

Every call to bookRemovedEvent.fire() will have the event qualifier @Removed and would match the second observer method we defined earlier in the chapter.

The downside of specifying event qualifiers on the injection point, as just done, is that we are not able to specify event qualifiers dynamically. We can modify our previous Event injection point to the following:

@Inject

@Any

Event<Book> bookRemovedEvent;

And instead set the qualifier dynamically by using this:

bookEvent

.select( new AnnotationLiteral<Removed>(){} )

.fire(book);

The AnnotationLiteral class is a helper class provided by CDI to make it easier for us to obtain a qualifier instance without having to create concrete classes.

Note

Event qualifiers can comprise a combination of annotations at the Event injection point and qualifier instances passed to select().

Members of event qualifiers

As with other qualifiers, we're able to add members to our event qualifiers that can either be part of differentiating qualifier instances or just for providing additional information.

@Qualifier

@Target( { PARAMETER, FIELD } )

@Retention( RUNTIME )

public @interface Book {

BookType value();

}

Say we fire an event as follows:

public void addBook(Book book) {

bookEvent

.select(new BookLiteral(book.getBookType()))

.fire(book);

}

To complete the preceding code, we need to create a BookLiteral class using the CDI AnnotationLiteral helper class.

public abstract class BookLiteral

extends AnnotationLiteral<Book>

implements Book {

private BookType type;

public BookLiteral(BookType type) {

this.type = type;

}

public BookType value() {

return type;

}

}

Note

We could have also created an empty BookLiteral implementation and overridden value() within our call to select(), but the preceding approach ensures we aren't repeating code by firing the same event and event qualifier somewhere else within our applications.

In such cases, we can then listen to these events with observer methods as follows:

public void fictionBookAdded(@Observes @Book(FICTION) Book book) {

...

}

public void nonFictionBookAdded(@Observes @Book(NONFICTION)

Book book) {

...

}

Instead of using BookLiteral to dynamically set the event qualifiers when firing the event, we could have specified them on the injection point as follows:

@Inject

@Book(FICTION)

Event<Book> bookEvent;

Note

Event qualifier members can be marked with @Nonbinding to prevent them from being a part of the process of matching observer methods to fired events.

Combining event qualifiers

Just as with qualifiers, we can combine any number of event qualifiers either on an injection point, by dynamically setting them on the event, or a combination of both approaches.

@Inject

@Book(FICTION)

Event<Book> bookEvent;

...

bookEvent.select(new AnnotationLiteral<Added>(){}).fire(book);

To have an observer method notified when the preceding event is fired, it needs to match all event qualifiers associated with the event when it was fired. If we're missing just one event qualifier, or a member of an event qualifier has a different value, our observer method will not be called as we expected.

Say we have observer methods such as the following:

public void afterFictionBookAdded(@Observes @Book(FICTION)

@Added Book book) { ... }

public void afterBookAdded(@Observes @Added Book book) { ... }

public void onBookEvent(@Observes Book book) { ... }

In such cases, only afterFictionBookAdded() will be called as it matches the fired event with @Added and @Book(FICTION).

Now say we had an observer method such as the following:

public void afterAdding(@Observes @Any Book book) { ... }

The preceding observer method would also be notified, as @Any informs Weld that we want to be notified of all Book events, irrespective of what event qualifiers may be set.

Observing events in different transaction phases

If an observer method wishes to receive events as part of the before or after completion phases of a transaction in which the event was fired, they are referred to as transactional observer methods.

We could incorporate the transaction phase into our preceding observer with the following:

public void refreshOnBookRemoval

(@Observes( during = AFTER_SUCCESS ) @Removed Book bk) { ... }

The possible values for during are defined on javax.enterprise.event.TransactionPhase. In the context of a transactional observer method, the values for TransactionPhase have the following meanings:

· IN_PROGRESS: An observer method is called immediately. As this is the default value for all observers, there is no need to set it if the standard behavior is required.

· BEFORE_COMPLETION: An observer method is triggered during the before completion phase of the transaction.

· AFTER_COMPLETION: An observer method is triggered during the after completion phase of the transaction.

· AFTER_SUCCESS: In the same phase of the transaction as AFTER_COMPLETION, but only if the transaction completes successfully.

· AFTER_FAILURE: In the same phase of the transaction as AFTER_COMPLETION, but only if the transaction fails to complete successfully.

Although AFTER_COMPLETION, AFTER_SUCCESS, and AFTER_FAILURE all result in an observer method being called at the same point in the transaction lifecycle, it can be advantageous to distinguish between whether the transaction completed, completed successfully, or failed.

Note

If there is no transaction in progress when an event is fired, a transactional observer method receives the event at the same time as other observer methods. It acts as though the during member of @Observes had not been set.

Now that we've learned about transactional observer methods, it's time to see how useful they can be! When our application utilizes a stateful object model, and we have state that is maintained for longer than a single transaction, we want the ability to update that long-held state without refreshing an entire set of objects through additional database calls.

For a bookstore application, we may want to retain a list of books that are currently available for purchase, but we don't want to retrieve that data for every user request as it changes infrequently. We need an applicationscoped bean to retain the list of books.

@ApplicationScoped

@Singleton

public class BookCatalog {

@PersistenceContext

EntityManager em;

List<Book> books;

@Produces

@Available

public List<Book> getAvailableBooks() {

if (null == books) {

books = em.createQuery

("select b from Book b where b.archived = false")

.getResultList();

}

return books;

}

}

Say we have a bean that raises an event when it adds a new Book element:

bookEvent.select(new AnnotationLiteral<Added>(){}).fire(book);

And when it removes a Book element:

bookEvent.select(new AnnotationLiteral<Removed>(){}).fire(book);

In such cases, we can modify BookCatalog to update the list of available books when it receives the events for adding and removing books, as follows:

public void addBook(@Observes(during = AFTER_SUCCESS)

@Added Book book) {

books.add(book);

}

public void removeBook(@Observes(during = AFTER_SUCCESS)

@Removed Book book) {

books.remove(book);

}

Event-observer bean creation

When Weld is in the process of delivering events to observers during our call to Event.fire(), it will automatically instantiate the bean that defines the observer method to be called if there isn't currently an instance of that bean present within the current context.

For most cases that is perfectly fine, but there may be situations where that default behavior is unwanted. Thankfully, CDI allows us to define a conditional observer, such that the bean will only observe events if it's already present within the current context.

We can specify a conditional observer by setting a value for the notifyObserver member of @Observes.

public void refreshOnBookRemoval

(@Observes( notifyObserver = IF_EXISTS ) @Removed Book bk)

{ ... }

Note

Any bean in the scope of @Dependent is not allowed to be a conditional observer, as there would be no way for the bean to be instantiated and observe the event.

The possible values for notifyObserver are defined in javax.enterprise.event.Reception. Apart from IF_EXISTS, the only other option is ALWAYS, which is the default.

Summary

We discussed some of the advantages of decoupling our application beans by using event producers and consumers. We then looked at what constitutes an event payload and how to define an event qualifier. Using event qualifiers and @Observes, we created observer methods to consume events that were produced with a call to Event.fire().

We expanded on event qualifiers to cover how to use qualifier members with events and the different approaches for combining multiple event qualifiers when firing and observing events. We also saw how events can be observed based on different phases of the transaction lifecycle, and whether a transaction was successfully completed or not.

Lastly, we looked at how a bean with an observer method is automatically instantiated, if it isn't already present in the current context, and how to prevent that instantiation if it is not desired.