Google Guava EventBus - an Easy and Elegant Way for Your Publisher - Subscriber Use Cases

Google Guava in version number 10 introduced new package eventbus with a few very interesting classes to deal with listener (or publisher – subscriber) use case. Below I present my short introduction to EventBus class and its family.

Basics

To listen to some events we need a listener class. Such class created in google-guava-way doesn’t have to implement any particular interface or extend any specified class. It can be any class with just one required element: a method marked with @Subscribe annotation:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class EventListener {

    public int lastMessage = 0;

    @Subscribe
    public void listen(OurTestEvent event) {
        lastMessage = event.getMessage();
    }

    public int getLastMessage() {
        return lastMessage;
    }
}

lastMessage property is used in tests below to check if events were received successfully.

And of course we need an event class to send it around:

1
2
3
4
5
6
7
8
9
10
11
12
public class OurTestEvent {

    private final int message;

    public OurTestEvent(int message) {
        this.message = message;
    }

    public int getMessage() {
        return message;
    }
}

How it works

The best way to show something in action is to write some tests, so let’s see how simple usage of EventBus looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void shouldReceiveEvent() throws Exception {

    // given
    EventBus eventBus = new EventBus("test");
    EventListener listener = new EventListener();

    eventBus.register(listener);

    // when
    eventBus.post(new OurTestEvent(200));

    // then
    assertThat(listener.getLastMessage()).isEqualTo(200);
}

This test can not be simpler :) We create EventBus instance and listener class instance, then register listener and post new event. And of course this test passes :)

MultiListener

Guava also allows to create listener that is reacting for many different events. We just need to annotate many methods with @Subscribe and that’s all:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MultipleListener {

    public Integer lastInteger;
    public Long lastLong;

    @Subscribe
    public void listenInteger(Integer event) {
        lastInteger = event;
    }

    @Subscribe
    public void listenLong(Long event) {
        lastLong = event;
    }

    public Integer getLastInteger() {
        return lastInteger;
    }

    public Long getLastLong() {
        return lastLong;
    }
}

and a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void shouldReceiveMultipleEvents() throws Exception {

    // given
    EventBus eventBus = new EventBus("test");
    MultipleListener multiListener = new MultipleListener();

    eventBus.register(multiListener);

    // when
    eventBus.post(new Integer(100));
    eventBus.post(new Long(800));

    // then
    assertThat(multiListener.getLastInteger()).isEqualTo(100);
    assertThat(multiListener.getLastLong()).isEqualTo(800L);
}

We don’t have to implement multiple interfaces, Guava provides a nice and clean solution with one annotation.

Beyond the basics

Now let’s analyze some more interesting features of EventBus.

Dead Event

First unusal thing is a DeadEvent class, predefined event which is fired when we’ve posted any type of event but no one was there to receive it. To see how it works let’s create listener waiting for dead events:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Listener waiting for the event that any message was posted but not delivered to anyone
 */
public class DeadEventListener {

    boolean notDelivered = false;

    @Subscribe
    public void listen(DeadEvent event) {
        notDelivered = true;
    }

    public boolean isNotDelivered() {
        return notDelivered;
    }
}

and test case showing how it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void shouldDetectEventWithoutListeners() throws Exception {

    // given
    EventBus eventBus = new EventBus("test");

    DeadEventListener deadEventListener = new DeadEventListener();
    eventBus.register(deadEventListener);

    // when
    eventBus.post(new OurTestEvent(200));

    assertThat(deadEventListener.isNotDelivered()).isTrue();
}

So if there was no listener waiting for event which was posted, EventBus fires DeadEvent so we could do something or at least add info to log files to be aware that such situation occured.

Events hierarchy

Another interesting feature is that 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. This can be presented with short example. Let’s create two listeners, one listening for Number class events and second one for Integer (which in case you didn’t know is a subclass of Number :) ):

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NumberListener {

    private Number lastMessage;

    @Subscribe
    public void listen(Number integer) {
        lastMessage = integer;
    }

    public Number getLastMessage() {
        return lastMessage;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class IntegerListener {

    private Integer lastMessage;

    @Subscribe
    public void listen(Integer integer) {
        lastMessage = integer;
    }

    public Integer getLastMessage() {
        return lastMessage;
    }
}

And a test method showing this feauture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void shouldGetEventsFromSubclass() throws Exception {

    // given
    EventBus eventBus = new EventBus("test");
    IntegerListener integerListener = new IntegerListener();
    NumberListener numberListener = new NumberListener();
    eventBus.register(integerListener);
    eventBus.register(numberListener);

    // when
    eventBus.post(new Integer(100));

    // then
    assertThat(integerListener.getLastMessage()).isEqualTo(100);
    assertThat(numberListener.getLastMessage()).isEqualTo(100);

    //when
    eventBus.post(new Long(200L));

    // then
    // this one should has the old value as it listens only for Integers
    assertThat(integerListener.getLastMessage()).isEqualTo(100);
    assertThat(numberListener.getLastMessage()).isEqualTo(200L);
}

In this method we see that first event (new Integer(100)) is received by both listeners, but second one (new Long(200L)) reaches only NumberListener as Integer one isn’t created for this type of events.

This feature can be used to create more generic listeners listening for a broader range of events and more detailed ones for specific purposes.

Summary

In this post I’ve presented some features of less known fragment of Guava Library, an EventBus class. It’s a very elegant and easy to apply solution when you need to listen for some events and publish them without creating sophisticated classes and interfaces hierarchy. And finally, this class is next good reason to add Guava to dependencies in your project :)


Java

Comments