Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

So What is a Java Event Agent?


Jul02: Java Q&A

Eric is a development analyst with Reuters. He can be contacted at [email protected].


The JavaBean event model is powerful in its simplicity. A source bean sends an event to anonymous listener beans via a method call on an interface. The source does not need to know who's listening to the events — or what happens when the events are received — and the listeners don't need to know the implementation behind these events.

Events are a big part of the JavaBean component architecture. Despite the simplicity of the event model, it does bring along complexities related to component dependencies.

The JavaBean Event Model

The event model is made up of an event source, event interface, event object, and event listener that implements the event interface. The event source component fires an event by invoking a method on the event interface of the event listener.

An event interface extends java.util.EventListener and defines additional methods that make up a related set of events. Each method defined represents a unique event notification. The parameter for each method on the event interface is typically a subclass of java.util.EventObject. This object should be used to encapsulate the details of the event. However, it is not mandatory that you use it — an arbitrary list of parameters is acceptable.

An event source is identified by its implementation of a registration method that accepts instances of a particular event interface. A component can be a source to multiple event interfaces by implementing a registration method for each one it supports. Conversely, a component can listen to more than one event set by implementing each event interface.

To illustrate, I've created a set of JavaBeans to handle changes to a company's stock price (quote). In this quote system (available electronically; see "Resource Center," page 5), I have QuoteSource, QuoteListener, and QuoteObject classes. I have also defined a QuoteChangedListener interface (Example 1) that defines an event for when a quote changes. QuoteListener implements this interface and provides an implementation of the quoteChanged event method. The method's parameter is an event object, QuoteEvent (Example 2). The QuoteEvent class constructor takes the event source as a parameter, which is stored by calling the superclass constructor. The java.util.EventObject interface defines a getSource method that returns a reference to the source object. A listener can call this method to identify the source component when an event is fired. Listing One presents the QuoteSource and QuoteListener code.

Component Dependencies

The complexity begins as multiple components implement multiple event interfaces. This situation gets more complex when a source component is also a listener of another source component. In Figure 1, which illustrates this complex web of components, the quotes system contains a component that manages system state. The StateChangeSource component fires system state change events. The QuoteDisplay component listens to these events and grays out the display if the state changes to "unavailable." The QuoteChangedSource component has a requirement to maintain a history of quote changes, thus needs to retrieve quote history data when the state changes back to "available." The PortfolioValueChangedSource tracks all changes to quotes in the user's portfolio, and all changes to this value are reflected by the QuoteDisplay component.

On the surface, this seems like a reasonable quote system design. However, instead of designing components that simply receive events, I've really designed components that are clients of many other components. The component dependency matrix this creates becomes a huge maintenance problem.

You should not need to know which component is the source to a particular set of events. Another developer deciding to combine two source components into one component that now sources the combined events will affect the listener component's code. Of course the opposite could occur, where one source component is broken out into multiple source components. These are implementation details that, as an event listener, you shouldn't need to know about. At the least, you should not need to modify your code as a result of such changes.

Circular Dependencies

A more complex component relationship arises when two source components are also listeners to one another's events, as in Figure 2. This is a problem because it creates a tight coupling between the two components. It can also lead to a memory leak, as the objects may never release their references to one another.

Event Source Abstraction

One solution is to abstract all source and listener components one more level by placing a Singleton component between them. This component — or agent — exists on behalf of all source components. Each source component can register itself with the agent as the source for an event interface. Other components can register themselves as listeners with this agent, thereby removing the need to know which specific components source each event interface. The agent notifies the source components when listeners arrive.

The Event Agent

The event agent I describe here is a component that provides this level of abstraction. It is only involved in matching up listener components with source components, and does not act as a router of the events themselves. This would otherwise make the event agent a bottleneck. Once a source and a listener component are matched together, the event agent is out of the picture.

In this implementation, the event agent uses the Singleton design pattern to ensure that there is only one instance of it in existence. To achieve this, the constructor is made private and the class contains a private, static, final instance of itself, accessible via a static method named getInstance. Example 3 implements this design pattern, and can be used to make any Java class a Singleton.

The UML class diagram in Figure 3 shows all of the member variables and methods within the EventAgent class. The only publicly accessible methods of this class are getInstance (just described), regSource, regListener, deRegSource, and deRegListener. The event agent lets source and listener components register independent of one another, meaning a source can register before any listener components do, and vice versa.

Registering a Source Component

This implementation of the event agent allows only one source component for each event interface. If multiple source components were allowed per event interface, the choice as to which source component to notify when a listener registers would be ambiguous. Additionally, giving the listener the ability to choose its event source defeats the purpose of the event agent. If there is reason to listen to events from one source compared to the same events from a different source, then the events are truly unique and should be implemented as unique event interfaces.

A source component registers with the event agent by calling the regSource method (see Listing Two), providing a reference to itself and the name of the event interface as a String. The following steps occur when regSource is called (see Figure 4).

1. A check is made to ensure that the source component implements SourceInterface by using the Java instanceof keyword.

2. A reference to the source component is stored in a HashMap with the key being the event interface name. A check is made to ensure that no other source has registered for the given event interface.

3. A check is made to see if any listener components have already registered for this event set. If so, the source component is notified by calling addListener for each listener component. A Vector of objects is used to encapsulate each listener component and the event interface it has registered for. The event interface name for each listener is compared to that of the source component. For each matching listener: The source is notified by calling its addListener method and passing a reference to the listener; the listener is removed from the Vector. All other listener components remain in this Vector until a matching source component registers with the event agent, whereby this process is repeated.

Registering a Listener Component

It is completely acceptable, and likely, that your software will require multiple listener components interested in the same event interface. The event agent entirely supports this.

A listener component registers with the event agent by calling the regListener method (Listing Three), providing a reference to itself and the name of the event interface as a String. The following steps occur when regListener is called (Figure 5):

1. The HashMap of source components is checked for a source that implements the given event interface. If a matching source component is found, the source component is notified by calling its addListener method.

2. If a matching source component is not found, this listener component is added to a Vector of listeners in anticipation that a source component will register at some future time.

Source and Listener Deregistration

Under some conditions, a source component may need to stop accepting listeners. Similarly, a listener component may no longer be able to wait for a matching source component. For these reasons, the event agent supports deregistration for both source and listener components.

A source or listener component can deregister itself by calling deRegSource or deRegListener, respectively. Both methods take the component's object reference and the name of the event interface as parameters. If the component is indeed on the source or listener list with the given event interface name, it will be removed from that list.

Listener and Source Component Collections

The HashMap of source components and the Vector of listener components actually store references to objects of the class EventComponent. This class, embedded within the EventAgent class, simply wraps the source or listener component as an Object, and the String representing the event interface in question. Listing Four implements this class.

The Modified Stock Quote Example

Using the stock quote example, the modifications needed to use the EventAgent are minor and actually quite helpful.

The event agent requires that all source components implement the interface named SourceInterface. This interface contains one method named addListener(). Since all source components need a similar method to allow clients to register, the requirement that this interface be implemented is not unreasonable. As a result, the event agent can call this method when a listener registers for any source component's event interface.

In regard to event listener components and event objects, there are no additional interface requirements. The final modification needed is for the source/listener components to register themselves with the event agent.

I suggest you add the interface name to the listener interface as a static String. Use this variable as the second parameter when calling regSource() or regListener(). This eliminates the potential for a typo that could otherwise result in a hard to find bug.

Listing Five shows the changes made to the QuoteSource component to use the event agent. Three minor changes have been made. The first is the addition of the implements SourceInterface clause to the class declaration. The second change is to get the EventAgent instance reference. The third change is the call to EventAgent's regSource method.

Listing Six shows the changes made to the QuoteListener component to use the event agent. The obsolete code has been commented out. Again, a line is added to get the EventAgent instance reference. The final change is a call to the EventAgent's regListener.

Conclusion

Reducing component coupling and dependencies within your code go a long way toward reducing bugs. In my experience, the event agent helps to accomplish this goal, with little additional code to make it work. In fact, programming events becomes more consistent through the use of the common SourceInterface and the unified method of event registration for both source and listener components.

The complete event agent source code (available electronically) contains logging to help track components and the events they are listening to. In larger software systems, this can be helpful in simplifying component relationships, as well as acting as a learning tool for new developers.

Overall, the event agent should help to simplify your event-handling code as well as further insulate components from implementation changes.

DDJ

Listing One

public class QuoteSource 
{
    protected Vector listeners;
    public QuoteSource() 
    {
    }

    public void addListener(Object listenerObj)
    {
        listeners.add( listenerObj );
    }

    public void synchronized fire_quoteChange(double newQuote)
    {
        QuoteEvent qe = new QuoteEvent( this, newQuote );

        // give each listener in the Vector 
        // this QuoteEvent object

        Vector listenersCopy;
        synchronized ( this )
        {
            listenersCopy = (Vector)listeners.clone();
        }
        
        int cnt = listenersCopy.size();
        for (int i = 0; i < cnt; i++)
        {
            QuoteChangedListener client = 
                (QuoteChangedListener)listenersCopy.elementAt(i);

            client.quoteChanged( qe );
        }
    }
}
public class QuoteListener implements QuoteChangedListener
{
    protected QuoteSource quoteSource;
    public QuoteListener() 
    {
        quoteSource = new QuoteSource();
        quoteSource.addListener( this );
    }

    public void quoteChanged(QuoteEvent qe)    
    {
        // do something with the new quote
        ...
    }
}

Back to Article

Listing Two

public synchronized boolean regSource(Object sourceObj, String eventName)
{
   // sourceObj must implement SourceInterface
   if ( ! ( sourceObj instanceof SourceInterface ) )
      return false;
   // Add this source object to our HashMap once only 
   if ( sourceObjects.containsKey( eventName ) == true )
      return false;
   sourceObjects.put( eventName, sourceObj );
   // Since listener and source objects can register at any time
   // check if there are matching listener objects in the Vector
   int i = 0;
   int count = listenerObjects.size();
   while ( i < count )
   {
      EventComponent listener = (EventComponent)listenerObjects.elementAt(i);
      // Check the listener's event interface name
      if ( eventName.equals( listener.getEventName() ) )
      {
         notifySource( sourceObj, listener.getObject() );
         // Remove the listener. All remaining elements will 
         // shift left so don't increment vector index i
         listenerObjects.remove( i );
         count--; // one less item in the list now
      }
      else
      {
         i++; // Check the next listener
      }
   }
  return true;
}

Back to Article

Listing Three

public synchronized boolean regListener(Object listenerObj, String eventName)
{
   // Search for the Matching Source in the HashMap
   EventComponent source = (EventComponent)sourceObjects.get( eventName );
   if ( source != null )
   {
      notifySource( source.getObject(), listenerObj );
   }
   else
   {
      // Didn't find the matching Source. Add the listener to the
      // list in anticipation that the source will register later
      EventComponent listener = new EventComponent( listenerObj, eventName );
      listenerObjects.addElement( listener );
   } 
   return true;
} 

Back to Article

Listing Four

class EventComponent
{
    protected Object theObject = null;
    protected String eventType = null;
    public EventComponent(Object obj, String s)
    {
        theObject = obj;
        eventType = s;
    }
    public Object getObject()
    {
        return theObject;
    }
    public String getEventType()
    {
        return eventType;
    }
}

Back to Article

Listing Five

public interface QuoteChangedListener extends java.util.EventListener 
{
    public static final String eventName = "QuoteChangedListener";

    // called when the stock quote changes
    public void quoteChanged(QuoteEvent qe);
}

public class QuoteSource implements SourceInterface
{
    protected Vector listeners;
    protected EventAgent eventAgent = EventAgent.getInstance();

    public QuoteSource() 
    {
        eventAgent.regSource( this, QuoteChangedListener.eventName);
    }

    public void addListener(Object listenerObj)
    {
        listeners.add( listenerObj );
    }

    public void fire_quoteChange(double newQuote)
    {
        ...
    }
}

Back to Article

Listing Six

public class QuoteListener implements QuoteChangedListener
{
    // NOTE: no longer need to know the source component
    // OBSOLETE: protected QuoteSource quoteSource;

    protected EventAgent eventAgent = EventAgent.getInstance();

    public QuoteListener() 
    {
        eventAgent.regListener( this, QuoteChangedListener.eventName );
        
        // OBSOLETE: quoteSource = new QuoteSource();
        // OBSOLETE: quoteSource.addListener( this );
    }

    public void quoteChanged(QuoteEvent qe)    
    {
        ...
    }
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.