In the last post, we learnt event-based communication on basis of Observer / Event Listener and Mediator patterns. Due to their shortcomings I would like to show more efficient ways for event-based communication. We will start with Google Guava EventBus and end up with CDI (Contexts and Dependency Injection for the Java EE platform).
Guava EventBus
Google Guava library has an useful package eventbus. The class EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another. Because we develop web applications, we should encapsulate an instance of this class in a scoped bean. Let's write EventBusProvider bean.
As you may see, a manually registration is still needed (EventBusProvider.getEventBus().register(this)) and the problem with scoped beans, I mentioned in the previous post, is still existing. We should be aware of scoping of EventBusProvider and scoping of publish / subscriber beans. But as you may also see, we have some improvements in comparison to the Mediator pattern: no special interfaces are needed, the subscriber's method names are not fix defined, multi-listeners are possible too, no effort to manage registered instances, etc. Last but not least - asynchronous AsyncEventBus and subscription to DeadEvent (for listening for any events which were dispatched without listeners - handy for debugging). Follow this guide please to convert an existing EventListener-based system to EventBus-based one.
CDI (Contexts and Dependency Injection)
Every JEE 6 compliant application server supports CDI (the JSR-299 specification). It defines a set of complementary services that help improve the structure of application code. The best-known implementations of CDI are OpenWebBeans and JBoss Weld. Events in CDI allow beans to interact with no dependency at all. Event producers raise events that are delivered to event observers by the container. This basic schema might sound like the familiar Observer / Observable pattern, but there are a couple of benefits.
CDI event mechanism is the best approach for the event-based communication in my opinion. The subject is complex. Let's only show the basic features. An observer method is a method of a bean with a parameter annotated @Observes.
Edit: Found today a great CDI specific possibility how to obtain a list of listeners for an event. Solution: Inject BeanManager (allows to interact directly with the container) and call
Guava EventBus
Google Guava library has an useful package eventbus. The class EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another. Because we develop web applications, we should encapsulate an instance of this class in a scoped bean. Let's write EventBusProvider bean.
public class EventBusProvider implements Serializable {I would like to demonstrate all main features of Guava EventBus in only one example. Let's write the following event hierarchy:
private EventBus eventBus = new EventBus("scopedEventBus");
public static EventBus getEventBus() {
// access EventBusProvider bean
ELContext elContext = FacesContext.getCurrentInstance().getELContext();
EventBusProvider eventBusProvider =
(EventBusProvider) elContext.getELResolver().getValue(elContext, null, "eventBusProvider");
return eventBusProvider.eventBus;
}
}
public class SettingsChangeEvent {The next steps are straightforward. To receive events, an object (bean) should expose a public method, annotated with @Subscribe annotation, which accepts a single argument with desired event type. The object needs to pass itself to the register() method of EventBus instance. Let's create two beans:
}
public class LocaleChangeEvent extends SettingsChangeEvent {
public LocaleChangeEvent(Object newLocale) {
...
}
}
public class TimeZoneChangeEvent extends SettingsChangeEvent {
public TimeZoneChangeEvent(Object newTimeZone) {
...
}
}
public MyBean1 implements Serializable {To post an event, simple provide the event object to the post() method of EventBus instance. The EventBus instance will determine the type of event and route it to all registered listeners.
@PostConstruct
public void initialize() throws Exception {
EventBusProvider.getEventBus().register(this);
}
@Subscribe
public void handleLocaleChange(LocaleChangeEvent event) {
// do something
}
@Subscribe
public void handleTimeZoneChange(TimeZoneChangeEvent event) {
// do something
}
}
public MyBean2 implements Serializable {
@PostConstruct
public void initialize() throws Exception {
EventBusProvider.getEventBus().register(this);
}
@Subscribe
public void handleSettingsChange(SettingsChangeEvent event) {
// do something
}
}
public class UserSettingsForm implements Serializable {Guava EventBus allows to create any listener that is reacting for many different events - just annotate many methods with @Subscribe and that's all. Listeners can leverage existing events hierarchy. So if Listener A is waiting for events A, and event A has a subclass named B, this listener will receive both type of events: A and B. In our example, we posted three events: SettingsChangeEvent, LocaleChangeEvent and TimeZoneChangeEvent. The handleLocaleChange() method in the MyBean1 will only receive LocaleChangeEvent. The method handleTimeZoneChange() will only receive TimeZoneChangeEvent. But look at the method handleSettingsChange() in the MyBean2. It will receive all three events!
private boolean changed;
public void localeChangeListener(ValueChangeEvent e) {
changed = true;
// notify subscribers
EventBusProvider.getEventBus().post(new LocaleChangeEvent(e.getNewValue()));
}
public void timeZoneChangeListener(ValueChangeEvent e) {
changed = true;
// notify subscribers
EventBusProvider.getEventBus().post(new TimeZoneChangeEvent(e.getNewValue()));
}
public String saveUserSettings() {
...
if (changed) {
// notify subscribers
EventBusProvider.getEventBus().post(new SettingsChangeEvent());
return "home";
}
}
}
As you may see, a manually registration is still needed (EventBusProvider.getEventBus().register(this)) and the problem with scoped beans, I mentioned in the previous post, is still existing. We should be aware of scoping of EventBusProvider and scoping of publish / subscriber beans. But as you may also see, we have some improvements in comparison to the Mediator pattern: no special interfaces are needed, the subscriber's method names are not fix defined, multi-listeners are possible too, no effort to manage registered instances, etc. Last but not least - asynchronous AsyncEventBus and subscription to DeadEvent (for listening for any events which were dispatched without listeners - handy for debugging). Follow this guide please to convert an existing EventListener-based system to EventBus-based one.
CDI (Contexts and Dependency Injection)
Every JEE 6 compliant application server supports CDI (the JSR-299 specification). It defines a set of complementary services that help improve the structure of application code. The best-known implementations of CDI are OpenWebBeans and JBoss Weld. Events in CDI allow beans to interact with no dependency at all. Event producers raise events that are delivered to event observers by the container. This basic schema might sound like the familiar Observer / Observable pattern, but there are a couple of benefits.
- Event producers and event observers are decoupled from each other.
- Observers can specify a combination of "selectors" to narrow the set of event notifications they will receive.
- Observers can be notified immediately or with delaying until the end of the current transaction.
- No headache with scoping by conditional observer methods (remember problem of scoped beans and Mediator / EventBus?).
CDI event mechanism is the best approach for the event-based communication in my opinion. The subject is complex. Let's only show the basic features. An observer method is a method of a bean with a parameter annotated @Observes.
public MyBean implements Serializable {The event parameter may also specify qualifiers if the observer method is only interested in qualified events - these are events which have those qualifiers.
public void onLocaleChangeEvent(@Observes Locale locale) {
...
}
}
public void onLocaleChangeEvent(@Observes @Updated Locale locale) {An event qualifier is just a normal qualifier, defined using @Qualifier. Here is an example:
...
}
@QualifierEvent producers fire events using an instance of the parametrized Event interface. An instance of this interface is obtained by injection. A producer raises events by calling the fire() method of the Event interface, passing the event object.
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Updated {}
public class UserSettingsForm implements Serializable {The container calls all observer methods, passing the event object as the value of the event parameter. If any observer method throws an exception, the container stops calling observer methods, and the exception is re-thrown by the fire() method. @Any annotation above acts as an alias for any and all qualifiers. You see, no manually registration of observers is necessary. Easy? Specifying other qualifiers at the injection point is simple as well:
@Inject @Any Event<Locale> localeEvent;
public void localeChangeListener(ValueChangeEvent e) {
// notify all observers
localeEvent.fire((Locale)e.getNewValue());
}
}
// this will raise events to observers having parameter @Observes @Updated LocaleYou can also have multiple event qualifiers. The event is delivered to every observer method that has an event parameter to which the event object is assignable, and does not have any event qualifier except the event qualifiers matching those specified at the Event injection point. The observer method may have additional parameters, which are injection points. Example:
@Inject @Updated Event<Locale> localeEvent;
public void onLocaleChangeEvent(@Observes @Updated Locale locale, User user) {What is about a specifying the qualifier dynamically? CDI allows to obtain a proper qualifier instance by means of AnnotationLiteral. This way, we can pass the qualifier to the select() method of Event. Example:
...
}
public class DocumentController implements Serializable {Let's talk about "conditional observer methods". By default, if there is no instance of an observer in the current context, the container will instantiate the observer in order to deliver an event to it. This behaviour isn't always desirable. We may want to deliver events only to instances of the observer that already exist in the current context. A conditional observer is specified by adding receive = IF_EXISTS to the @Observes annotation.
Document document;
@Inject @Updated @Deleted Event<Document> documentEvent;
public void updateDocument() {
...
// notify observers with @Updated annotation
documentEvent.select(new AnnotationLiteral<Updated>(){}).fire(document);
}
public void deleteDocument() {
...
// notify observers with @Deleted annotation
documentEvent.select(new AnnotationLiteral<Deleted>(){}).fire(document);
}
}
public void onLocaleChangeEvent(@Observes(receive = IF_EXISTS) @Updated Locale locale) {Read more about Scopes and Contexts here. In this short post we can not talk more about further features like "event qualifiers with members" and "transactional observers". I would like to encourage everybody to start learn CDI. Have much fun!
...
}
Edit: Found today a great CDI specific possibility how to obtain a list of listeners for an event. Solution: Inject BeanManager (allows to interact directly with the container) and call
<T> java.util.Set<ObserverMethod<? super T>> resolveObserverMethods(T event, java.lang.annotation.Annotation... qualifiers)