'

Catching the bus in Java

Lee Chuk-Munn delves into bus-based architectures in this week's Java TechGuide.

Ever since I was introduced to Infobus from the early days of Java, I have been a big fan of bus-based architecture.

What is bus-based architecture? Let me explain this by way of an example. In a "traditional" Java or Swing-based application, events are wired by directly attaching the event publisher with the event consumer. The following two simple code snippets illustrate this.

//JButton event consumer class

public class MyEventConsumer implements ActionListener {

...

//Implement ActionListener methods

public void actionPerformed(ActionEvent aEvt) {

//Do something with the event

...

//Event producer

JButton button = new JButton("OK");

MyEventComsumer eventConsumer = new MyEventConsumer();

button.addActionListener(eventConsumer);

For MyEventConsumer class to listen to events cast by JButton, we have to directly add the consumer to the event producer, a JButton. Although this method is simple to work with, it does not scale well to larger applications because:

  1. We need to create artificial methods to add consumer to producers; if button above is in a separate class, then we would have to create our addActionListener() method in the encapsulating class to allow any arbitrary consumer to subscribe to the button pressed event.
  2. If you have more than one event producer, then you will have to create unique methods to specify which event we are interested in; for example addOKEvent(ActionListener l), addCancelEvent(ActionListener l), etc.
  3. Listeners that are not removed from the event producer may also cause memory leaks.
  4. Finally, the tight coupling between producers and consumers ultimately make a reasonably sized application difficult to maintain.
An alternative approach is to use a bus-based system. Here is how bus works: events are broadcast on to the bus. Consumers indicate to the bus which events they are interested in by subscribing to these events. In this loosely coupled architecture, producers and consumers are isolated from each other via the bus. And more importantly, changes in either the consumer or the producer does not affect the other.

Using EventBus
I have been using EventBus in two of my projects and have found it to be easy to use and sufficient for my needs. I've used an earlier implementation of a bus architecture library called Infobus which is based on JavaBeans. Infobus, however, has been 'end-of-lifed' (a.k.a. EOLed). EventBus is a publish/subscribe event routing library for applications running on a single Java Virtual Machine (JVM). You can, however, extend EventBus to broadcast to remote JVMs.

Let us look at how we can use EventBus; there are two libraries to support pre and post Java Generics. I will be using the latter in the examples that follow, so some familiarity with Generics is assumed. Here is what you have to do to write an event consumer.

  1. The event consumer implements the EventSubscriber<T> interface indicating the event object type in T.
  2. Now write the onEvent(T object) where T is the object type declared above.
  3. The consumer now subscribes to the bus, EventBus.subscribe(T obj, EventSubscriber<T> sub), passing in the event object class and the EventSubscriber implementation respectively.
Here is a full listing of the MyEventConsumer using EventBus.
import org.bushe.swing.event.*;

 

public class MyEventConsumer implements EventSubscriber {

 

//Setup - subscribe to interested events

private void setup() {

EventBus.subscribe(ActionEvent.class, this);

}

 

//This method wil be called when the publisher
published ActionEvent public void onEvent(ActionEvent aEvt) { //Do something with it ... }
Publishing an event is really simple; all we have to do is call publish() with the appropriate event object.
import org.bushe.swing.event.*;

 

public class MyEventProducer implements ActionListener {

 

private JButton button;

 

public void setup() {

button = new JButton("OK");

//Add a listener to catch all button events

button.addActionListener(this);

}

 

//Listener called when button is pressed

public void actionPerformed(ActionEvent aEvt) {

//Now we publish it once

EventBus.publish(aEvt);

}
The onEvent() method will be called for all T and subclass of T; this means that the onEvent() method will be called for ActionEvent and all its subclass. If you are interested in only a particular class, use subscribeExactly() instead of subscribe() like so:
EventBus.subscribeExactly(ActionEvent.class, this);

Topics
In our previous example, when an ActionEvent is published, all subscribers will be notified. EventBus supports event classification by creating associating topics to the event objects. You can think of topics as queue names. Events published to a particular topic will only be accessible to those that subscribe to that particular topic.

A consumer will implement EventTopicSubscriber instead of EventSubscriber. Let's modify what we have so far to use topics.

public class MyEventConsumer implements EventTopisSubscriber {

 

//Setup - subscribe to interested events

private void setup() {

//Subscribe to "OK" topic

EventBus.subscribe("OK", this);

}

 

//This method wil be called when the publisher publishes an

//event object on the topic that we have subscribed to

public void onEvent(String topic, Object obj) {

//Do something with it

...

}
We can also subscribe to topics based on regular expression like so:
private void setup() {

//Subscribe to any topics that looks like an "OKEvent"

EventBus.subscribe(new Pattern("*OKEvent*"), this);

}
The producer now publishes the event on a particular topic in the following manner:
//Listener called when button is pressed

public void actionPerformed(ActionEvent aEvt) {

String cmd = aEvt.getActionCommand();

//Now we publish it based on different button types

if (cmd.equals("OK"))

EventBus.publish("OKEvent", aEvt);

else if (cmd.equals("Cancel")

One problem that I find with bus systems is debugging; sometimes an event may cause an exception in the consumer. Tracking down the source of the event may be a problem in large systems, especially if it is an unsolicited event based on some external event like networking. However, with proper tools and logging, it is usually quite easy to track it down or you can get the consumer to "fail-fast".

Although EventBus is designed with Swing applications in mind, it has some specific classes for integrating with Swing, and I find it sufficiently general to be used in any Java application. EventBus is simple to use; the library, just one, is small (46K) and supports Generics for type safety.

Lee Chuk-Munn has been programming in the Java language since 1996, when he first joined Sun Microsystems in Hong Kong. He currently works as a senior developer consultant and technology evangelist for Technology Outreach at Sun in Singapore. Chuk's focus is in Java APIs, Java EE, Java SE, and Java ME. Chuk graduated in 1987 from the Royal Melbourne Institute of Technology in Melbourne, Australia, where his favorite subject was compiler theory.