”Publish/subscribe” eller ”Observer” är ett designmönster som används när en ”Observable” vill notifiera många ”Observers” om att något har hänt. Några exempel på detta mönster är de Swing-events som skickas vid ex en knapptryckning till dess olika ActionListeners eller de ApplicationEvents som kan skickas via Springs ApplicationContext exempelvis ContextRefreshedEvent. Detta inlägg visar hur man kan sätta upp detta mönster via Spring genom att bara använda löst kopplade komponenter och kontext.

Nedanstående klass är den så kallade publiceraren eller dispatchern som kommer att publicera events till alla dess lyssnare.

package se.cygni.sample;

import java.util.ArrayList;
import java.util.List;

/**
 * The event dispatcher can dispatch events via the method
 * dispatchEvent to all registered event listeners.
 */
public class EventDispatcher {
    /** A list containing the event listeners. */
    private List listeners = new ArrayList();

    /**
     * Add an event listener to the event dispatcher.
     * @param listener The listener to add.
     */
    public void addEventListener(EventListener listener) {
        listeners.add(listener);
    }

    /**
     * Dispatches an event to all the listeners.
     * @param whatHappened The event to dispatch.
     */
    public void dispatchEvent(String whatHappened) {
        for (EventListener listener : listeners) {
            listener.eventOccured(whatHappened);
        }
    }
}

Lyssnare till events implementerar interfacet EventListener.

package se.cygni.sample;

/**
 * Implemented by anyone that wants to receive
 * events from the MessageDispatcher.
 */
public interface EventListener {
    void eventOccured(String whatHappened);
}

Ett exempel på en mycket enkel lyssnare som helt enkelt ekar ut innehållet i eventet till System.out.

package se.cygni.sample;

/**
 * A simple EventListener that prints the events that are
 * passed from the EventDispatcher to System.out.
 */
public class ConcreteEventListener implements EventListener {
    /** Shared counter for all instances of this class. */
    private static int counter = 0;

    /** The id of the event listener. */
    private int id = counter++;

    public void eventOccured(String whatHappened) {
        System.out.println("listener=" + id + ", event=" + whatHappened);
    }
}

Problemet uppstår när man ska koppla samman lyssnare och dispatcher. Man vill inte ha logik i lyssnarna för att koppla ihop sig med dispatchern och man vill inte att dispatchern ska ha ett hårt beroende till lyssnarna.

Ett enkelt sätt att koppla samman allt är genom följande konstruktion.

<!--
      Hard wiring between the event dispatcher and the
      event listeners.
-->
<bean id="eventDispatcher" class="se.cygni.sample.EventDispatcher">
    <property name="listeners">
        <list>
            <ref bean="eventListener1"/>
            <ref bean="eventListener2"/>
        </list>
    </property>
</bean>

Detta leder dock till att det kontext där eventDispatcher deklareras måste känna till alla lyssnare. Istället kan man använda MethodInvokingFactoryBean som erbjuder ett löst sätt att koppla ihop lyssnare och dispatcher.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="eventDispatcher"
        class="se.cygni.sample.EventDispatcher"/>
    <bean id="eventListener1"
        class="se.cygni.sample.ConcreteEventListener"/>
    <bean id="eventListener2"
        class="se.cygni.sample.ConcreteEventListener"/>

    <!--
      Do the wiring between the listeners and the dispatcher
      via XML
    -->
    <bean class=
    "org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject" ref="eventDispatcher"/>
        <property name="targetMethod" value="addEventListener"/>
        <property name="arguments">
            <list>
                <ref bean="eventListener1"/>
            </list>
        </property>
    </bean>

    <bean class=
    "org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject" ref="eventDispatcher"/>
        <property name="targetMethod" value="addEventListener"/>
        <property name="arguments">
            <list>
                <ref bean="eventListener2"/>
            </list>
        </property>
    </bean>
</beans>

I ovanstående fall så kan lyssnarna läggas till dynamiskt från olika kontext.