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:
1 2 3 |
public interface WalkListener extends EventListener { void walkTo(Location location); } |
Then we use WalkListener
to both observer and publisher events:
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 |
public class EventBusExample { public static void main(String[] args) { EventBusExample eventSource = new EventBusExample(); Location store = new Location(); EventBus eventBus = new EventBus(); eventBus.addListener(WalkListener.class, new WalkListener() { public void walkTo(Location location) { System.out.println("walking slowly..."); } }); eventBus.addListener(WalkListener.class, new WalkListener() { public void walkTo(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.
1 2 3 4 5 6 7 8 9 |
public class Event { private final Object eventSource; private final Class<? extends EventListener> eventListenerClass; private final Method method; private final Object[] 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class EventSourceInvocationHandler implements InvocationHandler { private final EventBus eventBus; private final Object eventSource; private final Class<? extends EventListener> eventListenerClass; public EventSourceInvocationHandler(EventBus eventBus, Object eventSource, Class<? extends EventListener> eventListenerClass) { this.eventBus = eventBus; this.eventSource = eventSource; this.eventListenerClass = eventListenerClass; } public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { eventBus.publishEvent(new Event(eventSource, eventListenerClass, method, arguments)); return null; } } |
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:
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 |
public class EventBus { private final Map<Class<? extends EventListener>, Set<? extends EventListener>> eventListeners = new HashMap<Class<? extends EventListener>, Set<? extends EventListener>>(); private final ExecutorService executorService = Executors.newFixedThreadPool(1); public EventBus() { } public <T extends EventListener> void addListener( Class<T> eventListenerClass, T listener) { } public <T extends EventListener> void removeListener( Class<T> eventListenerClass, T listener) { } public void removeListener(EventListener listener) { } public <T extends EventListener> T getPublisher(Object eventSource, Class<T> eventListenerClass) { return null; } protected void publishEvent(Event event) { } protected void deliverEvent(Event event) { } public void shutdown() throws InterruptedException { } } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public <T extends EventListener> T getPublisher(Object eventSource, Class<T> eventListenerClass) { EventSourceInvocationHandler handler = new EventSourceInvocationHandler(this, eventSource, eventListenerClass); return (T) Proxy.newProxyInstance( eventListenerClass.getClassLoader(), new Class[]{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.
1 2 3 4 5 6 7 8 9 10 11 12 |
public <T extends EventListener> void addListener(Class<T> eventListenerClass, T listener) { Set<T> listeners; synchronized (eventListeners) { listeners = (Set<T>) eventListeners.get(eventListenerClass); if (listeners == null) { listeners = new CopyOnWriteArraySet<T>(); eventListeners.put(eventListenerClass, listeners); } } listeners.add(listener); } |
EventBus.deliverEvent()
Event delivery is performed by invoking the original method called by the producer on every registered observer.
1 2 3 4 5 6 7 8 9 10 11 |
protected void deliverEvent(Event event) throws Throwable { Set<? extends EventListener> 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
protected void publishEvent(final Event event) { executorService.execute(new Runnable(){ @Override public void run() { try { deliverEvent(event); } catch (Throwable e) { throw new RuntimeException(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.
Download
The event bus download contains the entire source code (including Eclipse project). The source code is licensed under the terms of the Apache License, Version 2.0.
If you enjoyed this, I’ve got an exception tracking tool for Java coming out. Sign-up at StackHunter.com to be notified when it does.
I wanted to get the rss-FEED but feed site shows me some XML errors…
Fara – can you give me an example of one of the XML errors you’re seeing with https://104.131.41.115/blog/feed/
Pingback: Use dynamic proxies to create a simple, powerful event bus (Part 2) | North Concepts
Hi, Dele,
I don’t see eventSource is used by your code.
What is purpose to have eventSource defined in the code?
Thanks
HI Jim,
Good catch. It isn’t until part 2 of this series that eventSource is used for filtering.
Thanks for taking the time to read this article and go through the code.
Cheers,
Dele
I agree. A powerful bus will be created through these steps and methods posted. Great!
Pingback: How to build a simple GWT event bus using Generators | North Concepts
Really enjoyed that- frankly never considered reflector interceptors (?) before- thanks 🙂