12 Comments

The Guava EventBus on Guice

Guava’s EventBus provides a publish-subscribe event mechanism which allows objects to communicate with each other via the Observer Pattern. The EventBus shies away from the traditional “Event Listener” pattern seen in Java where an object implements a particular interface and then explicitly subscribes itself with another object.

In a recent project we chose to use the EventBus in conjunction with Guice (a dependency injection library) and have had a lot of success with it. Specifically, objects in our system only have to express what events they care about without being required to explicitly register with the EventBus or any other object.

Before we go over how we bootstrapped EventBus with Guice, I think it would be a useful exercise to review the traditional and “non-guice” approaches to subscribing to events in order to illustrate the advantages of the EventBus/Guice partnership.

Traditional

As stated above—the traditional method requires an interface declaration, an explicit subscription, and knowledge of the object that is posting the particular event. Additionally, it forces the object that is posting the event to invent its own method of publishing the event.

public interface ApplicationEvent {
    public void invoke(Object value);
}

public class ApplicationEventListener implements ApplicationEvent {
    private Application application;

    public ApplicationEventListener(Application application) {
        this.application = application;
        this.application.addApplicationEventListener(this);
    }

    public void invoke(Object value) {
        // handle event
    }
}

public class Application {
    private ArrayList<ApplicationEvent> events = new ArrayList<ApplicationEvent>();

    public void addApplicationEventListener(ApplicationEvent event) {
        events.add(event);
    }

    public void postEvent() {
        for (ApplicationEvent event : events) {
            event.invoke(this);
        }
    }
}

Non-Guice EventBus

Using the EventBus eliminates the need for an interface or reference to the object that is posting the event. However, the ApplicationEventListener is still required to reference a global EventBus and register itself with it. The coupling between the global EventBus and the ApplicationEventListener eliminates the possibility of providing a flexible way of swapping out one EventBus with another without having to refactor the ApplicationEventListener.

public class ApplicationEvent {
    private final Object value;

    public ApplicationEvent(Object value) {
        this.value = value;
    }
}

public class ApplicationEventListener {
    public ApplicationEventListener() {
        Application.globalEventBus().register(this);
    }
    
    @Subscribe
    public void applicationEvent(ApplicationEvent event) {
        // handle event
    }
}

public class Application {
    private static EventBus eventBus = new EventBus("Default EventBus");


    public void postEvent() {
        eventBus.post(new ApplicationEvent(this));
    }

    public static EventBus globalEventBus() {
        return eventBus;
    }
}

EventBus on Guice

We are almost there! The example below utilizes Guice to inject an instance of the EventBus into the objects that require it—giving us the flexibility to swap one EventBus with another without having to refactor code. Unfortunately, it requires that we inject an EventBus instance into objects that are only going to register with it and never reference it again.

public class ApplicationModule extends AbstractModule {
    private EventBus eventBus = new EventBus("Default EventBus");
    
    @Override
    protected void configure() {
        bind(EventBus.class).toInstance(eventBus);
    }
} 

public class ApplicationEventListener {
    private Application application;

    @Inject
    public ApplicationEventListener(EventBus eventBus) {
        eventBus.register(this);
    }

    @Subscribe
    public void applicationEvent(ApplicationEvent event) {
        // handle event
    }
}

public class Application {
    private EventBus eventBus;

    @Inject
    public Application(EventBus eventBus) {
        this.eventBus = eventBus;
    }
    
    public void postEvent() {
        eventBus.post(new ApplicationEvent(this));
    }
}

EventBus on a lot of Guice

Finally, we have arrived at our destination. Using Guice, we bind a TypeListener to every object that is created and ensure that it is registered with our default EventBus. Objects that subscribe to particular events are no longer required to explicitly subscribe with an EventBus and only need to express what kind of events they are interested in.

public class ApplicationModule extends AbstractModule {
    private final EventBus eventBus = new EventBus("Default EventBus");

    @Override
    protected void configure() {
        bind(EventBus.class).toInstance(eventBus);
        bindListener(Matchers.any(), new TypeListener() {
            public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
                typeEncounter.register(new InjectionListener<I>() {
                    public void afterInjection(I i) {
                        eventBus.register(i);
                    }
                });
            }
        });
    }
}

public class ApplicationEventListener {
    private Application application;

    @Subscribe
    public void applicationEvent(ApplicationEvent event) {
        // handle event
    }
}

public class Application {
    private EventBus eventBus;

    @Inject
    public Application(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public void postEvent() {
        eventBus.post(new ApplicationEvent(this));
    }
}