Use dynamic proxies to create a simple, powerful event bus (Part 1)

In this blog I’m going to walk you through one of my favourite uses for Java’s dynamic proxies. Why favourite? Because it takes a powerful, sometimes misunderstood, feature of Java and creates a simple, useful tool that we can use every day.

Event Buses

Event buses act as brokers or clearing houses for notifications. They provide a single point of contact where:

Observers can register to be notified of events.

Subjects can publish events as they occur.

Event buses are an evolution on the observer pattern. They share many of the same advantages (loose coupling and event broadcasting), while removing several shortcomings:

Code duplication. Subjects no longer need to explicitly track their observers. The list of observers, along with the registration and notification logic, can be moved from each subject into the event bus.

Decentralized event management. Event registration and notification are no longer spread out over a potentially large number of classes. Centralized management also aids in debugging, eases maintenance, and reduces our overall software complexity.

Simplified event wiring. Observers do not need to seek out subjects to register their interest and subjects do not need to notify observers directly when events occur.

Dynamic Proxies

Dynamic proxies are a way to route calls from all methods on one or more Java interfaces to a single function automagically (without programming). This happens while your program is running and doesn’t require recompilation or restart. All it needs is a set of interfaces to automatically implement and a concrete implementation of the java.lang.reflect.InvocationHandler interface to receive the calls.

One Call, Many Receivers

What if we could call a method that instead of being invoked on one object was invoked on several objects automatically? For example, person.walkTo(store) invokes walkTo on a single person object. But what if we could invoke that call on many person objects at once without having to call a fireWalkTo method or create a WalkEvent class (a common way of implementing the observer pattern)? Well, that’s exactly what this event bus does. It lets subjects call methods on the listener interface then invokes those calls on all registered observers.

Quick Example

Before we dive into the details, let’s look at a quick example of how the final product can be used.

First we create a listener interface:

Java

1

2

3

publicinterfaceWalkListenerextendsEventListener{

voidwalkTo(Location location);

}

Then we use WalkListener to both observer and publisher 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

publicclassEventBusExample{

publicstaticvoidmain(String[]args){

EventBusExample eventSource=newEventBusExample();

Location store=newLocation();

EventBus eventBus=newEventBus();

eventBus.addListener(WalkListener.class,newWalkListener(){

publicvoidwalkTo(Location location){

System.out.println("walking slowly...");

}

});

eventBus.addListener(WalkListener.class,newWalkListener(){

publicvoidwalkTo(Location location){

System.out.println("walking quickly...");

}

});

WalkListener publisher=eventBus.getPublisher(eventSource,

WalkListener.class);

publisher.walkTo(store);

publisher.walkTo(store);

publisher.walkTo(store);

eventBus.shutdown();

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

}

}

Implementation Details

The Event Bus is made up of three collaborating classes: Event, EventSourceInvocationHandler, and EventBus. For most of our use-cases, EventBus is all we’ll need to work with. In some rare cases we may need direct access to the Event object — for example — to log all events flowing through the bus (regardless of type or method). We’ll discuss how to accomplish this in part 2 of this series.

Event Class

The Event class represents a single method invocation from an event source (publisher). It holds the actual invoked java.lang.reflect.Method along with any arguments that were passed into it.

Java

1

2

3

4

5

6

7

8

9

publicclassEvent{

privatefinalObjecteventSource;

privatefinalClass<?extendsEventListener>eventListenerClass;

privatefinalMethod method;

privatefinalObject[]arguments;

...

}

EventSourceInvocationHandler Class

EventSourceInvocationHandler is the concrete implementation of InvocationHandler for use with dynamic proxies. Its invoke() method receives all event publisher method calls and posts them to the event bus for delivery.

EventBus Class

EventBus is where all the magic happens. It provides all of the registration, queuing, and delivery services to make the whole thing work. Let’s first look at the bus’ surface area, followed by implementation details of the business methods: getPublisher, addListener, deliverEvent, and publishEvent.

First the basic structure of the bus:

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

publicclassEventBus{

privatefinalMap<Class<?extendsEventListener>,

Set<?extendsEventListener>>eventListeners=

newHashMap<Class<?extendsEventListener>,

Set<?extendsEventListener>>();

privatefinalExecutorService executorService=

Executors.newFixedThreadPool(1);

publicEventBus(){

}

public<TextendsEventListener>voidaddListener(

Class<T>eventListenerClass,Tlistener){

}

public<TextendsEventListener>voidremoveListener(

Class<T>eventListenerClass,Tlistener){

}

publicvoidremoveListener(EventListener listener){

}

public<TextendsEventListener>TgetPublisher(ObjecteventSource,

Class<T>eventListenerClass){

returnnull;

}

protectedvoidpublishEvent(Event event){

}

protectedvoiddeliverEvent(Event event){

}

publicvoidshutdown()throwsInterruptedException{

}

}

EventBus.getPublisher()

The getPublisher method takes an event source and a listener class then returns an instance of that listener we can call to publish events. The event source can be any object for now. In the next blog we’ll see how we can use it to filter the events observers receive.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

public<TextendsEventListener>TgetPublisher(ObjecteventSource,

Class<T>eventListenerClass){

EventSourceInvocationHandler handler=

newEventSourceInvocationHandler(this,eventSource,

eventListenerClass);

return(T)Proxy.newProxyInstance(

eventListenerClass.getClassLoader(),

newClass[]{eventListenerClass},

handler);

}

EventBus.addListener()

Observers register for events by calling addListener() with the EventListener interface they are interested in and an object that implements it. CopyOnWriteArraySet is used because it is thread-safe without requiring much synchronization. This means we have the option to notify observers using separate threads without blocking registrations.

EventBus.deliverEvent()

Event delivery is performed by invoking the original method called by the producer on every registered observer.

Java

1

2

3

4

5

6

7

8

9

10

11

protectedvoiddeliverEvent(Event event)throwsThrowable{

Set<?extendsEventListener>listeners;

synchronized(eventListeners){

listeners=eventListeners.get(event.getEventListenerClass());

}

if(listeners!=null){

for(EventListener listener:listeners){

event.getMethod().invoke(listener,event.getArguments());

}

}

}

EventBus.publishEvent()

Events are queued for delivery by adding a Runnable with the actual delivery task to the executor service. Executors are a great way to benefit from concurrency without the complexity (and subtle bugs) that come with writing multi-threaded code.

Java

1

2

3

4

5

6

7

8

9

10

11

12

protectedvoidpublishEvent(finalEvent event){

executorService.execute(newRunnable(){

@Override

publicvoidrun(){

try{

deliverEvent(event);

}catch(Throwablee){

thrownewRuntimeException(e.getMessage(),e);

}

}

});

}

Next Time

That’s all for now. I hope you’ve found this application of dynamic proxies useful. Please leave a comment if you have any suggestions, questions, or ideas on dynamic proxies or event busses.

In the next part of this series, we’ll focus on several topics (exception handling, garbage collection, etc.) to help round out the bus’ feature set.

About Dele Taylor

We make Data Pipeline — an embedded, real-time data processing engine. Use it in your applications, services, and batch jobs to process, filter, transform, and aggregate large (and small) volumes of data on-the-fly.
Learn more about it at northconcepts.com.
View all posts by Dele Taylor →