Listeners

The Listeners library allows you to geatly simplify the code you add to support listeners in your code.

It contains listener collections that provide a special proxy object. Invoking a method on the proxy automatically invokes the method on all contained listeners. As a result, there is no longer need to implement 'fire' methods, your code is simplified, and its correctness is guaranteed.

The code is provided under the Apache license.

What you win

The library allows you to delegate the proper handling of listeners to specialized code and to concentrate on more important matters. Basically you win in the following areas:

  • Much simpler code. As shown on the example below, a lot of repetitive actions are eliminated, and the code becomes easier to handle.
  • Better performance. The implementation using Java code generation performs better than the code normally used for managing listeners.
  • Proper synchronization. Synchronization is often easy to forget and that frequently causes problems with concurrent access to your objects. The provided managers automatically perform synchronization where needed, and you do not need to remember to use the 'synchronized' keyword as a result.

[Listeners library] [Javassist library web] [Listeners source] [Listeners javadoc zip]
[Listeners library for Java 5] [Listeners source for Java 5] [Listeners javadoc for Java 5 zip]

Example

Using the provided library, a class can support listeners of a given type by simply defining the container where the listeners will be kept and by defining a 'proxy' object of the same type as the listeners (this is optional, but simplifies the listener invocation code). For example:

    private IListenerManager _keyLMgr = new GenListenerManager(KeyListener.class);
    private KeyListener _keyLProxy = (KeyListener) _keyLMgr.getProxy();
    
    public void addListener(KeyListener lst) { _keyLMgr.addListener(lst); }
    public void removeListener(KeyListener lst) { _keyLMgr.removeListener(lst); }
	
Once this is done, the listeners can be invoked whenever necessary by simply calling the corresponding function on the proxy object, for example:
        _keyLProxy.keyPressed(event);
        _keyLProxy.keyReleased(event);
        _keyLProxy.keyTyped(event);
	
This will automatically invoke the corresponding functions of all listeners contained by the ListenerManager.

 


In contrast, here is the code one typically writes to handle listeners. First define the listener container and its associated methods:

    private List _arrListeners = new ArrayList();
    
    public synchronized void addListener(KeyListener objListener) {
    	// perhaps make sure there are no duplicates here
        if (!_arrListeners.contains(objListener))
            _arrListeners.add(objListener);
    }

    public synchronized void removeListener(KeyListener objListener) {
        _arrListeners.remove(objListener);
    }
  
Then define a 'fire' method for each method of the interface:
    private synchronized void fireKeyPressed(KeyEvent event) {
        for (Iterator it = _arrListeners.iterator(); it.hasNext();) {
        	KeyListener listener = (KeyListener) it.next();
        	listener.keyPressed(event);
        }
    }
    
    private synchronized void fireKeyReleased(KeyEvent event) {
        for (Iterator it = _arrListeners.iterator(); it.hasNext();) {
        	KeyListener listener = (KeyListener) it.next();
        	listener.keyReleased(event);
        }
    }
    
    private synchronized void fireKeyTyped(KeyEvent event) {
        for (Iterator it = _arrListeners.iterator(); it.hasNext();) {
        	KeyListener listener = (KeyListener) it.next();
        	listener.keyTyped(event);
        }
    }
	
The listeners can then be invoked by calling the corresponding 'fire' method:
    	fireKeyPressed(event);
    	fireKeyReleased(event);
    	fireKeyTyped(event);
	

Performance

The ListenerManager behaviour is defined in the IListenerManager interface. There are two implementations of that interface provided -- GenListenerManager that uses the Javassist library to generate the necessary proxy class in runtime via byte-code manipulation, and RefListenerManager that uses the built in Java reflection facilities to achieve the same task. Both implementations behave identically, but differ in performance and in the required libraries (GenListenerManager requires Javassist, RefListenerManager does not require additional libraries)

Here is a comparison of the invocation speeds different solutions offer. The evaluation is time for 50 000 000 invocations, faster is better:

Speed Method
515ms Listeners in a Java array
Keeping the listeners in a Java array is clearly the most efficient method to manage and invoke them, but unfortunately it is also a rather elaborate one.
It essentially requires the user class to implement a lot of the ArrayList functionality by itself, such as growing and collapsing the array when new listeners are added and removed. The complexity makes the code much longer than usual, opens a lot of possibilities for making an error, and requires a lot of tuning.
938ms GenListenerManager
The GenListenerManager class generates a proxy in runtime via byte code manipulation using Javassist. The proxy can thus be very efficient and performs better than the usual listener management methods based on ArrayList.
1453ms Listeners in an ArrayList using get()
The listeners are kept in an ArrayList and they are accessed using the get(i) method.
3828ms Listeners in an ArrayList using Iterator
Same as above, but the listeners are enumerated using an Iterator.
9859ms RefListenerManager
The RefListenerManager class generates a proxy using Java reflection and that clearly affects its performance. Nevertheless, this class does not depend on external libraries and if the listeners perform serious work, the invocation cost will not matter any more.

To summarize, GenListenerManager performs better than the typical ways in which listeners are handled. RefListenerManager is much slower, but it is a good fit for cases where additional libraries are not wanted and performance is not that critical.