In this blog we’ll build on part 1 by adding several important features to the event bus to make it production ready. Since these features are fairly well contained, you can start with the section that most interests you, download the code, or view the project on GitHub.

Event Filtering

Event filters help reduce noise by allowing listeners to automatically ignore events they would normally receive. This is accomplished by including a new EventFilter object alongside every listener added to the bus.

The EventFilter class has a single abstract allow(event, listener) method that returns true if the event should be delivered to the listener.

Several filters are already are part of the bus, including EventSourceFilter. This filter only allow events that were produced by one of a set of sources.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

publicclassEventSourceFilterimplementsEventFilter{

privatefinalObject[]source;

publicEventSourceFilter(Object...source){

this.source=source;

}

@Override

publicbooleanallow(Event<?>event,Objectlistener){

if(source==null||source.length==0){

returntrue;

}

for(Objects:source){

if(event.getEventSource()==s){

returntrue;

}

}

returnfalse;

}

}

In addition to the new EventFilter interface and the addListener update, a few other changes are needed to handle filters:

A new EventListenerStub class to hold a listener and filter together.

The eventListeners field on EventBus is converted from a Set<? extends EventListener> to a List<EventListenerStub<? extends EventListener>>. We swap Set for List because we can no longer depend on the listener’s object identity to prevent duplicate listeners in the set (now that it’s wrapped in an EventListenerStub).

The bus’ deliverEvent() method calls EventFilter.allow()to check if the event should be delivered.

Event Topics

It’s not always ideal for us to filter by the event’s source. Sometimes it’s more convenient to filter by a value known to both publishers and listeners. That’s where topics come in. Topic values can bet passed as an optional parameter while obtaining a publisher from the bus. Every method call made on that publisher will include the topic value. Listeners can watch for specific topics by including a TopicFilter when registering with the bus. Topics can be any java.lang.Object or subclass we like. I recommend enums because they are IDE and refactoring friendly, but a string or any object will do.

Publishers

A few small changes on the publisher side will handle the registration and propagation of topics for us. First, both the Event and EventSourceInvocationHandler classes will track topics. EventSourceInvocationHandler will have it set during construction and pass it on to every event it creates.

Listeners

The TopicFilter class allows listeners to receive events matching one or more topic values. The implementation uses exact matching, but if that’s too simplistic for your case, it should be easy enough to implement your own hierarchical or pattern matching filter.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

publicclassTopicFilterimplementsEventFilter{

privatefinalObject[]topic;

publicTopicFilter(Object...topic){

this.topic=topic;

}

@Override

publicbooleanallow(Event<?>event,Objectlistener){

if(topic==null||topic.length==0){

returntrue;

}

finalObjectt=event.getTopic();

for(Objectt2:topic){

if(t==t2||(t!=null&&t.equals(t2))){

returntrue;

}

}

returnfalse;

}

}

Here’s an example that uses enum topics to publish and filter events.

Java

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

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

publicclassTopicFilterExample{

publicstaticenumTopic{STROLL,RUSH}

publicstaticvoidmain(String[]args)throwsThrowable{

TopicFilterExample eventSource=newTopicFilterExample();

Location store=newLocation();

EventBus eventBus=newEventBus();

// listen for events under the STROLL topic

eventBus.addListener(WalkListener.class,

newTopicFilter(Topic.STROLL),

newWalkListener(){

publicvoidwalkTo(Location location){

System.out.println("walking slowly...out for a stroll");

}

});

// listen for events under the RUSH topic

eventBus.addListener(WalkListener.class,

newTopicFilter(Topic.RUSH),

newWalkListener(){

publicvoidwalkTo(Location location){

System.out.println("walking quickly...in a rush");

}

});

// RUSH is set as the topic for this publisher

WalkListener publisher=eventBus.

getPublisher(eventSource,WalkListener.class,Topic.RUSH);

publisher.walkTo(store);

publisher.walkTo(store);

publisher.walkTo(store);

Thread.sleep(2000L);

eventBus.shutdown();

System.out.println("The End.");

}

}

Untyped Listeners

Some use-cases require that we listen to all events passing through the bus regardless of their type. Audit, monitoring, or even debug requirements might call for such a feature. In these special cases we can register an UntypedEventListener with the bus. These listeners are notified in the form of the Event object after all typed listeners have received their notifications.

EventListenerStub Refactoring

Now that we’re handling both typed and untyped listeners, EventListenerStub will undergo refactoring to reduce code duplication. First, EventListenerStub is divided into three classes. The original EventListenerStub and two inner subclasses: Typed and Untyped. The new subclasses are responsible for providing the delivery logic for their kind of listeners while allowing EventListenerStub to handle filtering and other common behaviour.

Just as before, the Typed subclass invokes the original called method on each listener.

Exception Handling

So far we haven’t said anything about exceptions. If you’ve looked at the source code from part 1, you’ll see that exceptions were caught, wrapped in RuntimeExceptions, and then rethrown. This saved us from the extra coding to declare checked exceptions, but did nothing to actually handle them. What we need now is an exception handling strategy that:

Handles all exceptions regardless of which thread throws them

Handles exceptions for both typed and untyped listeners

Doesn’t require knowledge of the bus’ internal workings

Is decoupled from listeners

Is simple to use

Exception Events

We can accomplish all of these requirements by simply leveraging the bus itself. By letting the event bus publish exceptions like any other event, we gain all of its advantages (decoupling, threading, filters, etc.) for the price of a small change.

The new ExceptionListener interface is used for both publishing and observing exceptions. The bus holds a publisher reference which it exposes through its handleException method.Since the bus delegates the actual event delivery to EventListenerStub, that class is also responsible for reporting exceptions to the bus by calling handleException. The handleException method takes care not to introduce infinite loops by not publishing exceptions for events where it was the publisher.

Garbage Collection

Memory leaks are a real concern when using the observer pattern. Observers that are never unregistered may never get garbage collected and your application’s memory could continue to grow until it eventually fails with an OutOfMemoryError. Memory leaks are arguably even more of a concern when working with event buses because they are responsible for holding on to all observers for all subjects. If a bus lives for the entire run of your app while holding on to every single all observer, we could get that OutOfMemoryError even sooner.

Soft References

The java.lang.ref package is Java’s answer to allowing objects to be garbage collected, even while there are still live references to them. The key is to wrap those objects with one of the classes in this package. In the event bus, we now wrap each listener in a SoftReference. This allows listeners to be automatically garbage collected when memory gets low, but now introduces a level of indirection to our code. We chose to use SoftReference over a WeakReference because they are garbage collected less aggressively by the JVM. Since most listeners tend to be anonymous inner classes, only referenced by the bus, I’d hate to see them disappear while the system is still flush with memory. (For more on Java references, read Ethan Nicholas’ classic blog Understanding Weak References. )

EventListenerStub

The code that manages listeners through soft references can e found in EventListenerStub and its two subclasses — Typed and Untyped. EventListenerStub wraps the listener and ensures the getter method is used for all access. The Typed and Untyped subclasses now perform null checks (in case listeners were garbage collected) before actually delivering events.

Reference Clean-up

You might have noticed the ReferenceQueue parameter in EventListenerStub‘s constructor. Reference queues allow the garbage collector to inform us when the objects in the soft references are actually released. The exact details differ for each kind of reference, but generally the garbage collector will put released references into the queue for us. We simply have to poll the queue periodically to be notified. Using reference queues allow us to be very efficient at detecting garbage collection, it saves us from having to test every listener reference to do a proper clean-up.

The event bus now holds two reference queues for garbage. One queue for typed listeners and the other for untyped. The queues are passed into the EventListenerStub subclasses along with each listener during registration. Finally, each event delivery now finishes with a call to the bus’ cleanupGarbage() method which polls the queues and removes anything found there. Although cleanupGarbage() returns very quickly when queues are empty, you could probably come up with an even better approach than checking every time.

Here’s an example that shows soft references surviving a forced garbage collection (System.gc()), but not a true memory exhaustion (allocating 100 GB or RAM). This example was run with the following configuration:

Thread-local Events

In the Untyped Listeners section we saw how to implement a bus wide listener. This gave us access to the raw Event object containing things like the event source, listener interface, and invoked method. Unfortunately, this forced us into choice. Either we listen for typed events and used whatever parameters the listener interface defines or go untyped and have the full Event object. Going untyped means extra filtering coding to select the exact event we’re interested in. Fortunately, we don’t have to make that choice. We can continue to work with typed listeners and access the full event using a thread-local when needed.

Thread-locals

Thread-locals allow us attach values to the current thread then access those values from any method running on that thread. This saves us having to modify methods to explicitly pass values around. For the event bus, this means linking the Event object to the delivery thread which we can then access anywhere, including inside a typed listener.

The event bus contains all of the changes:

A new thread-local field for the event (currentEvent)

A getter method for use anywhere (as long as it’s in the delivery thread)

Next Time

That’s a wrap. We’ve covered a lot of ground in this blog. Hopefully, I left you with a few ideas you can use in your own event bus or applications. Do you have a question or suggestion to improve some of the ideas we’ve discussed? Leave a comment, I’d like to hear about it.

You should consider putting the code up on github so others can browse/contribute to it (I’d be willing to help you with this if you’d like). Also since this is generally useful library code it’d also be useful if you were to publish it to a centralized maven repository so that others can use it more easily.