Easy Java Bean Event Notification

Unlike C#, Java has no language support for event notification. Instead, Java classes usually follow a set of programming conventions, defined by the Java Beans framework, for defining notifications in terms of listener interfaces and methods for connecting and disconnecting event listeners and event sources.

In the Java Bean conventions, notifications are typed and grouped by "listener" interfaces. Each listener interface extends EventListener and defines a methods for each notification that is delivered through that interface. For example, the ContainerListener interface defines two notifications: componentAdded, announced when a component is added to the container, and componentRemoved, announced when a component is removed. Listener methods do not return results.

An object is declared as source of events of some type by implementing two methods, one to add a new listener to the event source and one to remove a listener from the event source. For example, a source of Sheep events would define the two methods:

An object that wants to receive notifications must implement the listener interface that defines the notifications and then be added as a listener to the source of events. The source of events must maintain a collection of registered listeners. To announce an event it must call the appropriate notification method of all of the listeners that have been registered with it.

As you can see, writing a class that announces Java Bean events is not an insignificant effort. What is a one-line declaration in C# requires a lot of boilerplate code in Java. The Java Bean event conventions and Java's static type system do not make it easy to write notification code in a way that can be reused in many classes for different listener types. As a result, notification is not used as often as it could be in Java code. It is usually easier to pass a callback interface to an object's constructor than it is to define Java Bean events on the class.

The result is that classes are more tightly coupled than necessary. You must pass a listener to the constructor of the source of events even when there is no need to consume the notifications.

Worse, it becomes impossible to create the object and the listeners at different times and then connect them at a later time. This is a form of what I call "Temporal Coupling". The objects appear to be decoupled because they communicate through interfaces but there is an implicit, hidden coupling between the time at which the two objects can do things. In this case, the listener must exist before you create the source of events.

Luckily I have a trick up my sleeve. I have written a cunning class, Announcer, that uses the reflection API and Java 5 generics to implement the Java Bean event model for any listener interface. Although it still requires more than one-line, it makes it much easier to add Bean events to any class. Here's how it's used to make a class a source of Sheep events:

public class Sheep {
    private Announcer<SheepListener> sheepListeners = Announcer.to(SheepListener.class);
    
    ...

    public void addSheepListener(SheepListener  listener) {
        sheepListeners.addListener(listener);
    }

    public void removeSheepListener(SheepListener listener) {
        sheepListeners.removeListener(listener);
    }

    protected void announceSheepDipped() {
        sheepListeners.announce().sheepDipped(this);
    }

    protected void announceSheepSheared() {
        sheepListeners.announce().sheepSheared(this);
    }

    ...
}

Currently the Announcer class is sitting as an example in the jMock project. It's only one class (and a unit test), so I don't think it's worth giving it its own project and JAR file. I usually just copy it into whatever project I'm writing and move it into one of the project's packages. Feel free to do the same.

Copyright © 2007 Nat Pryce. Posted 2007-08-13. Share it.

Comments powered by Disqus