he Spring Framework introduced many people to the idea of dependency injection, the notion that instead of looking up objects you need to collaborate with, you simply have them injected
into your class via JavaBean-style getters and setters. This article introduces a new, complimentary form of dependency injection that is based not on properties but on the idea of channels
and callback listeners
. Much like classic dependency injection (which I consider property-based), this listener-based injection has the ability to reduce boilerplate code, promote looser coupling, and help make code more testable and reusable.
In the same way that the JavaBean introspection used in dependency injection can infer properties by the presence of methods such as setPropertyName/getPropertyName, the listener introspection used in listener injection can infer communication channels on an object from methods such as addChannelName/removeChannelName, as the method signature defines the listener or callback interface. This article demonstrates how to use listener injection to clearly communicate relationships between POJOs using the Spring framework.
Spring-Based Injection Example
Suppose you have an application to which you want to introduce file-based input. So you need something that will poll a directory for files being added. Enter JPoller, an open source project that has all the capabilities you need. You decide to add it to your application using Spring, which makes it easy to define an instance of your poller as well as your listener class.
JPoller defines a PollManager callback interface. You provide an instance of this interface to receive the "file found" events. The basic configuration in Spring might look as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<property name="autoMove" value="true" />
<property name="pollInterval" value="2000" />
<bean id="listener" class="example1.Callback" />
With an instance of your poller and your listener class defined, the only remaining issue is how you register your listener bean with the poller. Somewhere, your code has to call the ManagedDirectoryPoller.addPollManager() method to associate your listener with the polling object. Standard Spring effectively offers only three options for where to place this code:
- You could subclass ManagedDirectoryPoller and add an init or setter method that can be called from Spring for registration.
- You could add the code to your listener class and pass it a reference to the poller bean so that it could add itself.
- You could define a third class that knows about both the poller and the listener and adds them.
All three of these approaches requires writing code, and none of them would convey exactly where the addPollManager() method is invoked just from looking at the configuration.
Enter Listener-Based dependency injection
With listener-based dependency injection you can state this relationship explicitly. Listener injection introspects the communication channels on ManagedDirectoryPoller and identifies the addPollManager(PollManager) method as a defining messaging channel (named PollManager). Just as with JavaBeans introspection, you can then register your bean with this channel by providing an appropriate listener.
I've used Spring's namespace extensions to extend the Spring XML to support syntax for injecting listeners by channel name. The example application can now fully define the relationship between poller and callback listener (see Listing 1).
Notice the line in Listing 1 containing the <orj:listener> tag. This injects the listener bean into the poller bean's PollManager channel. The process is analogous to injecting a property. The beauty of this is that it communicates the relationship between the objects clearly (you're also not forced to find a class off of which to hang the call to addPollManager()).
This is the basic usage of listener injection, which is enough for most people to immediately see the benefit, but there really is much, much more.