Event Programming with Google Guava EventBus

It’s a given in any software application there are objects that need to share information in order to get work done. In Java applications, one way of achieving information sharing is to have event listeners, whose sole purpose is to take some action when a desired event occurs. For the most part this process works and most experienced Java developers are used to writing the requisite anonymous inner class that implements some event listener interface. This post is about taking a different approach to handling Java events using Guava’s EventBus. The EventBus allows for objects to subscribe for or publish events, without having explicit knowledge of each other. The EventBus is not meant to be a general purpose publish/subscribe system or support interprocess communication.

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:

  1. Define a public method that takes a single argument of the desired event type and place a @Subscribe annotation on that method.
  2. 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

If you found this post helpful subscribe to my feed.    

Comments

  1. What supri

  2. If you have ever wondered why google guava uses strong references and this has been a problem for you, check out this message bus:

    https://github.com/bennidi/mbassador

    It uses weak references, support synchronous and asynchronous message delivery, object filtering etc. Handlers are marked using annotations (pretty much the same as in guava). It’s quite stable and production ready (has been used in production for half a year now). And it’ fast!

  3. @Bill, I don’t really get the multithreading handling in EventBus http://goo.gl/oRz02 … There is a ThreadLocal isDispatching variable and if you look at dispatchQueuedEvents() method, Cliff Biffle tests whether this thread is dispatching right now. How the hell ONE thread can be dispatching and test if (isDispatching.get()) at the same time :) ? In general I don’t get how a Boolen ThreadLocal variable of type : “Am I Doing Something” make any sense… It should be : “Is any other thread doing something”, right? I can’t figure it out, could you please have a look ? Just out of curiosity

    • This is how it should be for it to make sense to me :

      private volatile boolean unlocked = true;
      private Monitor.Guard available = new Monitor.Guard(monitor) {
      public boolean isSatisfied() {
      return unlocked;
      }
      };

      protected void dispatchQueuedEvents() {
      if (monitor.enterIf(available)) {
      unlocked = false;
      try {
      while (true) {
      EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
      if (eventWithHandler == null) {
      break;
      }

      dispatch(eventWithHandler.event, eventWithHandler.handler);
      }
      } finally {
      unlocked = true;
      }
      }
      }

  4. Hey, very helpful blog post. Just so you know, Monitor. reevaluateGuards(); method has been removed from Monitor, so it won’t compile.

    • Joseph,

      Thanks for the comments. Also thanks for the heads up on the code change. I’ll need to go back and fix my examples to match up with the latest version of Guava.

      Cheers,
      Bill

  5. Albert Kam says:

    I am currently using eventBus heavily in my app, sync and async.
    And i blame you for this.
    Hopefully you can find more time to share other guava stuffs.
    Thanks a lot !

    • Albert,

      Thanks for the comments. I’m glad to hear that the EventBus is working for you. I do plan on getting back to some more Guava posts at some point.

      Cheers,
      Bill

  6. Nice explanation of the event bus. I’ve, coincidentally, followed up with a blog post showing how to use the EventBus with Guice.

    http://spin.atomicobject.com/2012/01/13/the-guava-eventbus-on-guice/

    • Justin, Thanks for the comments. Using the EventBus with Guice is a great topic. I’ll be sure to check it out.

  7. Ngoc Truong says:

    Hello,

    From what I see in your article, @Subscribe annotation is mandatory. Then, annotation @AllowConcurrentEvents supports the async mode. I think that this annotation should be removed and the characteristic “allow concurrent events” should be just a property of the @Subscribe.

    Anyway, thanks for your explanation!

  8. I believe it is the case but I was not certain from the documentation. The publishing supports inheritance of message type, yes? E.g.,

    @Subscribe
    public void smorgasbord(Object o) {
    System.out.println(o);
    }

    Will print every published message to the console?

    • @binkley
      Yes messages are delivered to any handler that the event object is assignable to. So from the example you presented here, every published message would be printed to the console

  9. Great explanation, that’s exactly what the Javadocs are missing. Also thanks for your other Guava articles!

  10. Louis Wasserman says:

    Guava developer here — if you’re using EventBus with some kind of dependency injection, you may be able to set up your injector to register _every_ created object with the EventBus. This can avoid issues with forgetting to register objects explicitly.

  11. I am curious how you find the performance. Event driven programming seems like a good fit for many aspects of game programming if it were performant enough.

  12. Wow, I didn’t even know that there was an eventbus in the guava libs !
    Thank you for that code !

Trackbacks

  1. [...] Event Programming with Google Guava EventBus – This post is about taking a different approach to handling Java events using Guava’s EventBus. The EventBus allows for objects to subscribe for or publish events, without having explicit knowledge of each other. The EventBus is not meant to be a general pu [...]

  2. Java Server – EventBus refactoring…

    Goal To clean the code and standardize the implementation. Scope Currently EventBus and EventBus2 are used mainly by: Asynch Regulations Hazelcast distributed tasks…….

Speak Your Mind

*