Google Guava EventBus for Event Programming
EventBus Class
The EventBus is very flexible and can be used as a singleton, or an application can have several instances to accomodate transferring events in different contexts. The EventBus will dispatch all events serially, so it is important to keep the event handling methods lightweight. If you need to do heavier processing in the event handlers, there is another flavor of the EventBus, AsyncEventBus. The AsyncEventBus is identical in functionality, but takes an ExecutorService as a constructor argument to allow for asynchronous dispatching of events.
Subscribing for Events
An object subscribes for events by taking the following steps:
- Define a public method that takes a single argument of the desired event type and place a @Subscribe annotation on that method.
- Register with the EventBus by passing an instance of the object to the EventBus.register method.
Here is a brief example with details omitted for clarity:
public class PurchaseSubscriber { @Subscribe public void handlePurchaseEvent(PurchaseEvent event) { ..... } ..... EventBus eventBus = new EventBus(); PurchaseSubscriber purchaseSubscriber = new PurchaseSubscriber(); eventBus.register(purchaseSubscriber);
There is another annotation that can be used in conjunction with @Subscribe, and that is @AllowConcurrentEvents. The @AllowConcurrentEvents marks a handler method as thread safe so an EventBus (most likely an AsyncEventBus) could potentially call the event handler from simultaneous threads. One interesting thing I discovered in unit testing is that if a handler method does not have the @AllowConcurrentEvents annotation, it will invoke handlers for an event serially even when using the AsyncEventBus. It’s important to note that @AllowConcurrentEvents will not mark a method as an event handler, the @Subscribe annotation still needs to be present. Finally, the event handling method must have one and only one parameter or an IllegalArgumentException will be thrown when you register the object with the EventBus.
Publishing Events
Publishing events with the EventBus is equally straight forward. In the section of code where you want to send the notification of an event, call EventBus.post and all subscribers registered for that event object will be notified.
public void handleTransaction(){ purchaseService.purchase(item,amount); eventBus.post(new CashPurchaseEvent(item,amount)); .... }
While it might be obvious, it’s important that the subscribing and publishing classes share the same EventBus instance, and it would make sense to use Guice or Spring to help manage the dependencies.
More About Event Handlers
A very powerful feature of the EventBus is that you can make your handlers as course or fine grained as needed. The EventBus will call the registered subscribers for all subtypes and implemented interfaces of a posted event object. For example, to handle any and all events one could create an event handler that takes a parameter of type Object. To handle only singular events, create a handler that is very type specific. To help illustrate, consider the following simple event hierarchy:
public abstract class PurchaseEvent { String item; public PurchaseEvent(String item){ this.item = item; } } public class CashPurchaseEvent extends PurchaseEvent { int amount; public CashPurchaseEvent(String item, int amount){ super(item); this.amount = amount; } } public class CreditPurchaseEvent extends PurchaseEvent { int amount; String cardNumber; public CreditPurchaseEvent(String item,int amount, String cardNumber){ super(item); this.amount = amount; this.cardNumber = cardNumber; } }
Here are the corresponding event handling classes:
//Would only be notified of Cash purchase events public class CashPurchaseSubscriber { @Subscribe public void handleCashPurchase(CashPurchaseEvent event){ ... } } //Would only be notified of credit purchases public class CreditPurchaseSubscriber { @Subscribe public void handleCreditPurchase(CreditPurchaseEvent event) { .... } } //Notified of any purchase event public class PurchaseSubscriber { @Subscribe public void handlePurchaseEvent(PurchaseEvent event) { ..... } }
If there is a need to capture a broad range of event types, an alternative would be to have more than one event handling method in a class. Having multiple handlers could be a better solution as you would not have to do any “instanceof” checks on the event object parameter. Here’s a simple example from my unit test:
public class MultiHandlerSubscriber { List<CashPurchaseEvent> cashEvents = new ArrayList<ashPurchaseEvent>(); List<CreditPurchaseEvent> creditEvents = new ArrayList<CreditPurchaseEvent>(); List<SimpleEvent> simpleEvents = new ArrayList<SimpleEvent>(); public MultiHandlerSubscriber(EventBus eventBus){ eventBus.register(this); } @Subscribe public void handleCashEvents(CashPurchaseEvent event){ cashEvents.add(event); } @Subscribe public void handleCreditEvents(CreditPurchaseEvent event){ creditEvents.add(event); } @Subscribe public void handleSimpleEvents(SimpleEvent event){ simpleEvents.add(event); } ....
Testing
Since the event handlers are just plain methods, they can easily be tested by instantiating an EventBus in the test case or simulate the EventBus by passing the appropriate event object. When working with the EventBus I found it was very easy to:
- Forget to register the subscribing object with the EventBus
- Neglect to add the @Subscribe annotation.
If it seems that an event handler is not being called, check for those two errors first.
A useful debugging technique is to subscribe to the DeadEvent class. An EventBus will wrap any published event with no handlers in a DeadEvent instance. DeadEvent provides the getEvent method that returns the original event object.
Conclusion
The Guava EventBus class provides an attractive and useful alternative to the standard Java event handling mechanism. It is hoped the reader will find the EventBus as useful as I do. As always comments and suggestions are welcomed.
Resources
Reference: Event Programming with Google Guava EventBus from our JCG partner Bill Bejeck at the Random Thoughts On Coding blog.
What about multiple handlers subscribed to same event type but not all have interest to listen for all events of that type, something similar to JMS queus and topics? Is it possible with EventBus?
Thank you for this post!