JSF Event-based communication: New-school approach

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.

public class EventBusProvider implements Serializable {

    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;
    }
}

I would like to demonstrate all main features of Guava EventBus in only one example. Let’s write the following event hierarchy:

public class SettingsChangeEvent {

}

public class LocaleChangeEvent extends SettingsChangeEvent {

    public LocaleChangeEvent(Object newLocale) {
        ...
    }
}

public class TimeZoneChangeEvent extends SettingsChangeEvent {

    public TimeZoneChangeEvent(Object newTimeZone) {
        ...
    }
}
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 MyBean1 implements Serializable {

    @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
    }
}

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.

public class UserSettingsForm implements Serializable {

    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";
        }
    }
}
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!
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?).
Conditional observer methods allow to obtain the bean instance that already exists, only if the scope of the bean that declares the observer method is currently active, without creating a new bean instance. If the observer method is not conditional, the corresponding bean will be always created. You are flexible!
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 {

    public void onLocaleChangeEvent(@Observes Locale locale) {
        ...
    }
}

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 @Updated Locale locale) {
    ...
}

An event qualifier is just a normal qualifier, defined using @Qualifier. Here is an example:

@Qualifier
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Updated {}

Event 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.

public class UserSettingsForm implements Serializable {

    @Inject @Any Event<Locale> localeEvent;

    public void localeChangeListener(ValueChangeEvent e) {
        // notify all observers
        localeEvent.fire((Locale)e.getNewValue());
    }
}
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:
// this will raise events to observers having parameter @Observes @Updated Locale
@Inject @Updated Event<Locale> localeEvent;
You 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:
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 {

    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);
    }
}
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.
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!
Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


1 × six =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books