Listeners Dependency Injection

Mike Ayzatsky

Mike Aizatsky/ Fabrique Project Manager / JetBrains

Mike has been a lead developer at JetBrains since the early days of IntelliJ IDEA. Now he manages Fabrique — a Rapid Application Development environment for web applications. His main areas of expertise include programming, classical piano, kayaking and art photography.
Contact Mike at: mike.aizatsky at jetbrains.com

Listeners and events are widely used in modern applications. This article finds a way to explicitly define listener-based component dependencies in generic IoCcontainer. The method leads to simplifying listeners code, separation of event delivery logic, as well as defines listener dependencies in analyzable way. These benefits are used to solve common problems with listeners: the order of event notifications and the problem of exponential growth of events in a system with many components.

We've recently started to apply IoC ( Inversion of Control) ideas to our code base in several projects. While it turned out to be quite beneficial I won't advocate for the usage of any container (PicoContainer) or for the IoC idea itself. I will just describe the set of ideas about listeners, which came to my mind while introducing IoCto our code.

I would like to get any feed back about these ideas you might have. I also invite you to discuss the article and all listeners-related problems in the accompanying forum.

All the code in this article is written without the use of JDK 1.5 generics. Though generics would simplify the idea a bit there're plenty of projects not using the generics yet.

The Problem

It's quite common for components in our projects to produce certain kinds of events and to add listeners to each other. For example:


    public MyComponent implements ResourceListener {
    private ResourceManager _manager;
    public MyComponent(ResourceManager mgr) {
    _manager = mgr;
    }

    public void start() {_manager.addResourceListener(this); }
    public void stop() {_manager.removeResourceListener(this); }
    }

    public interface ResourceManager {
    void addResourceListener(ResourceListener l);
    void removeResourceListener(ResourceListener l);
    }

    public class ResourceManagerImpl implements ResourceManager {
    //all the usual events stuff
    private List myListeners = new ArrayList();

    public void addResourceListener(ResourceListener l) {  }
    public void removeResourceListener(ResourceListener l) {  }
    private void fireEvent1(Event e) {  }
    private void fireEvent2(Event e) {  }
    }

So, what's wrong with this code?

  1. The code has more dependencies than needed. MyComponentdoesn't need to know that this kind of events is actually coming out of ResourceManager. The situation is even worse. We often have situation when ResourceManager's implementation is quite complex, and it's split into several other components. To let them fire events on behalf of the ResourceManager, the fire*methods should be made public!
  2. Every event source has quite a few lines of code devoted to listeners management and event firing. It's nice to factor it out somehow.

The Solution. Client part

Let's try to simplify the client code the MyComponentimplementation. Since we don't want to know anything about actual event source, we would prefer to write the code like:


    public interface ResourceEventSource {
    void addListener(ResourceListener l);
    void removeListener(ResourceListener l);
    }

    public MyComponent implements ResourceListener {
    public MyComponent(ResourceEventSource eventSource) {
    _eventSource = evtSource;
    }

    public void start() {_eventSource.addListener(this); }
    public void stop() {_eventSource.removeListener(this); }
    }

Now we have created the client, which might be initialized by IoC container. It's not a big deal yet. Let's think a bit more about it. Why should we bother with all that start/stop methods? We'd surely like just to express our intention to listen some kind of events. Let's simply write:

    public MyComponent implements ResourceListener {
    public MyComponent() {
    }
    }

and let the container spot the fact we're implementing the ResourceListener! How will the container know we'd like to listen for events? Several possible ways can be immediately proposed:

  • 1. Combination of reflection and naming scheme. Let the container analyze all components interfaces and look for *Listenerinterfaces and *EventSourceparameters.
  • 2. Combination of reflection and explicit registration. There's nothing wrong in having this code (xml configuration) somewhere:
    container.registerListenerClass(ResourceListener.class,
    ResourceEventSource.class);
  • 3. Annotation scheme. Can be used by both lucky JDK 1.5 guys and mere mortals with XDoclet:
    /**
    * @listener class= ResourceListener
    */

The Solution. Event source part

Event sources might be greatly simplified with ease too. We don't want to manage all the listeners and write all the fire methods. Let the container provide us with one event sink and let it manage all the stuff itself!


    public class ResourceManagerImpl {
    private ResourceListener _eventSink;

    public ResourceManagerImpl(ResourceListener eventSink) {
    _eventSink = eventSink;
    }

    private void foo() {
    
    //fire the event
    _eventSink.resourceAdded(new ResourceEvent());
    
    }
    }

In this code, instead of managing the listeners you've got the eventSink it’s the event broadcaster, which serves as a single point for you and broadcasts all events to all listeners. And instead of fire* method the corresponding listener method is invoked.

The container logic

The container itself would create the event proxy mechanism for every listener in the system. The proxy should gather all the events from different event sources and reroute them to appropriate clients. The wiring mechanism shouldn't be difficult to implement in any IoCcontainer. In fact I already have the implementation prototype for PicoContainer, which I'm going to publish soon.

Benefits

Let's analyze the proposed solution and examine benefits.

  1. Event sources are much simpler now.
  2. It's possible to have multiple event sources for a single kind of event without exposing this fact to the client and compromising methods visibility.
  3. Clients are totally isolated from the event sources. They deal with events only. In fact, with this scheme it's even possible to move the event source into a remotely running application without a single change in the client code.
  4. Event-based dependencies are stated in an analyzable way both at run & compile time. This fact will be heavily used in the following discussion.
  5. Due to the fact that every event is going through a single event broadcaster, the implementation of event distributing algorithm might be changed easily. You can introduce timers, postpone updates, add prioritized queues etc. The order of client notification might be also easily changed.
  6. This solution is also well suited for containers that support dynamic component removal/addition the container itself might dynamically register listeners. Clients no longer need to listen for container events to register/remove listeners when components appear/disappear.

Another Problem: Listeners and dependent components

Diagram of interdependent components with listener-based dependency

The conventional listener mechanisms often fail in the case of interdependent components. Consider the diagram on the right. In many cases this scheme doesn't cause any trouble, until B starts to change its own internal state significantly after receiving the event from C. The common example of this behavior is caching the data and accessing caches only on later data requests.

Let's first look at the good processing order:

 Sequence diagram of right event dispatch order

In this case B has received the event first and was able to update its own data. That's why the later request for data from A doesn't cause any trouble.

The case with wrong event processing order is straightforward too:

Sequence diagram of wrong event dispatch order

In this case A receives the event first and immediately accesses B for the piece of information needed for A to perform its job. Apparently, this piece of information can't be served because the internal B state is not synchronous with C 's yet. It will become so only after B will process the corresponding C event.

Sample dependency diagram of the listeners in the IDEA core

Though the use-case might seem a bit artificial, we have plenty of examples of these sorts of problems in IntelliJ IDEA. E.g. we have the VirtualFileSystemcomponent, which hides all the differences between file systems on different platforms, jars and even http protocol. We also have some kind of repository, which caches high-level info about java files. It holds the list of classes they contain, methods, etc. The project view displays the information about the project and accesses both the file system, for forming the project tree, and the repository for displaying classes and methods. (The latter request is not done directly, but through the use of other components. I'm simplifying matters a bit.) Apparently the project view should listen for events from file system in order to update its UI. The problem fits exactly into the class of problems I've described.

I've seen several ways of dealing with these problems in different projects: introducing priorities for listeners, using invokeLaterseveral times before accessing other components in event listener, and even forbidding the access of other components while events are still being propagated. None of them seems to be easy enough to use and maintain.

It's not difficult to see now, how explicit event dependency mechanisms can resolve such problems without introducing overhead into project maintenance. Since we have full, easy-to-analyze information about all kinds of project dependencies we can easily sort listeners and fire events in order, which doesn't cause this trouble. We can even detect the dangerous situation when both A and B depend on each other.

Dependency problem which is not solved by reordering of event notifications

In fact, simply sorting the listeners' order is not really enough for complex component systems. To see why it's true take a look at another dependency diagram. In this case the event propagation system will probably need to temporarily freeze the event propagation from C to B while all events from E are served, since B is dependant from both C and D and might want to access D before D has got the event from E and form the same erroneous situation we are analyzing.

Performance Problems

Performance-problematic dependency There's another set of problems arising from listeners: the exponential growth of events propagated. Take a look once more at the already familiar dependency diagram. In cases when a component listens for an event from more than 1 source (B in our case), it’s quite likely for it to produce several events, corresponding to the one that came from a lower-level component (E). While it doesn't seem to be criminal enough to bother about it, it becomes quite a problem when you have hundreds of components. I heard about situations, when thousands of actually duplicated events were generated after a single mouse click.

While there seems to be no fit-into-all-projects solution to this problem, there are still some ideas, how the described mechanism of explicit listeners might help. All of them are trying to identify duplicate events and eliminate them from event propagation cycle.

In some projects it's quite easy to detect duplicate events automatically. To do this, the event propagation system should collect all events B fires while processing events from C& D . After event collection, B's events should be analyzed for duplicates. In simple cases it might be enough to notice, that all events have the same type and the same data. In more complex cases other heuristics, probably applicable to specific project only, should be introduced.

The other way to go is to move all the burden of duplication analysis to event consumers. To do so you need to modify all your listeners to receive the list of events, instead of receiving them one by one. I.e. the listener

    public interface SomeListener {
    void someEventOccured1(Event1 e);
    void someEventOccured2(Event2 e);
    }

should become

    public interface SomeListener {
    void someEventsOccured(AbstractEvent[]events);
    }

Having listeners in this form makes it possible to collect all the events from B and simply deliver them to A without writing complex event duplication detection logic in the event processing system. Please note that due to the fact we're declaring all our listener dependencies explicitly, instead of registering all of them in code, it's even possible to migrate some clients gradually to this scheme, since components container might be aware of this listener form.

Summary

It turned that a simple idea of making component container aware of listener pattern results not only in simpler code. It also solves serious, hard-to-debug problems with listeners in large systems. The proposed solution is not yet implemented by us, but we are actively thinking about adopting it in Fabrique project.

Mike Aizatsky / JetBrains