转载自TheServerSide网站,介绍使用Spring来创建Observer模式。
http://www.theserverside.com/articles/article.tss?l=SpringLoadedObserverPattern
This article describes an easy process of implementing the observer pattern in the Spring framework (Spring Core). Also discussed in this article are a few of the Spring Core classes as well as an easy way to start the Spring Framework in any project. Finally, this article shows developers and designers that the Spring framework is a great reason to continue design pattern advocacy in your projects.
Recently, it seems when developers use the Spring framework to improve their projects they focus only on simple object oriented design techniques. Unfortunately some of the more brilliantly researched patterns are forgotten in place of a brilliant framework (Spring). Although the Factory Pattern and the Singleton Pattern are built into Spring, other patterns such as the Decorator Pattern, the Adapter Pattern, and the Observer Pattern are often forgotten because of the new ideas Spring has to offer. Fortunately, design patterns and the Spring framework can exist in the same application. In this article I show how the commonly used Observer Pattern fits nicely in the Spring Framework.
Observer Pattern
The Observer Pattern is also known as a publisher and subscriber design pattern. The pattern is useful when you have one publisher and many subscribers (one-to-many) that are interested in the publisher's state or messages. Additionally, interested subscribers have the ability to register and unregister as they please. Lastly, subscribers are notified of the publisher's messages automatically (that is, by no effort of their own). Figure 1 is an example of a typical observer pattern.
Figure 1. Observer Pattern
I chose to use a more widely accepted diagram to describe the Observer Pattern so you will notice that the aforementioned publisher is actually the Subject in this diagram. The subscriber is the Observer in the diagram. The intimate details of the Observer Pattern are far outside of the scope of this article, but a note worthy topic is how the Spring framework can be used to leverage good object oriented design techniques while creating the concrete classes of this pattern.
A normal concreteObserver class is required to have code similar to this constructor (or a similar “setter” method to achieve the registering of the Observer with the Subject):
public concreteObserver(Subject s) {
s.addListener(this);
}
Below you will see how the Spring framework wires the two concrete classes together with XML and not with code inside the classes. Ultimately, this allows the developer to avoid any unnecessary coupling of the concrete classes.
Spring Considerations
Since this article covers only the most simple implementation of the observer pattern, I utilize only the required Spring framework jars. At a minimum you need to have the spring-core.jar, the spring-context.jar, and the spring-beans.jar from the Spring framework distribution. Also to avoid any run time errors you need the commons-logging.jar from the Apache Commons project in your class path.
Each of these jars provide a specific role that make using the Spring framework possible. First is the spring-core.jar; this jar is required for all Spring applications. It includes Spring's dependency injection classes as well as other classes that are used to create Spring beans. The spring-context.jar contains the ApplicationContext interface. This interface is used to start the Spring framework in my included example project.
The last Spring jar is the spring-beans.jar. It contains the DesposibleBean interface which the FileSystemXmlApplicationContext bean sub-interfaces. I do not directly use the DesposibleBean interface but I use the FileSystemXmlApplicationContext bean to located the XML file used to configure the Spring framework. The code that implements these classes is shown in Listing 6.
Wiring The Observer Pattern with Spring
To illustrate the Observer Pattern concretely, I chose to create a Town Crier class that sends messages to any registered Town Resident class. To keep this example simple, I developed the same interfaces shown in Figure 1, but the concrete classes are TownCrier and TownResident. All four of these classes are shown in Listings 1 through 4.
After I created the TownCrier (Listing 3) and two TownResident (Listing 4) classes I created an incomplete version the ObserverContext.xml file (Listing 5). This file contains the Spring definitions of the concrete implementation beans. Since this example is simple, I chose not to use any of the more complex attributes of the bean tag.
Typical Bean tags for the shown classes:
<bean id="townCrier" class="springobserver.TownCrier"/>
<bean id="townResident1" class="springobserver.TownResident"/>
<bean id="townResident1" class="springobserver.TownResident2"/>
At this point, I was able to run my ExampleRun class (Listing 6), but nothing eventful actually happened. This is because the TownResident classes were not “wired” into the TownCrier class.
To perform the wiring of the Observer Pattern I chose to use Spring's MethodInvokingFactoryBean class. This process is a very simple way of calling a method on a class and ultimately passing a parameter into method. In this example, the parameter is the bean definition of a townResident. A snapshot of this bean definition is:
<bean id="registerTownResident1"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject"><ref local="townCrier"/></property>
<property name="targetMethod"><value>addListener</value></property>
<property name="arguments">
<list>
<ref bean="townResident1"/>
</list>
</property>
</bean>
As you can see, the targetObject is the townCrier bean, the targetMethod is the addListener method and the argument is the townResident1 bean. This configuration is the only code needed to compose the concrete implementations of the TownCrier with TownResident class.
Now that I have the beans wired together using the MethodInvokingFactoryBean class, I can run my ExampleRun class and see that my TownResident classes are receiving messages from the TownCrier class. Results shown in Example 1.
Conclusion
A few lessons learned in this article include a simple way to start the Spring framework, how to use the MethodInvokingFactoryBean, and an efficient implementation the Observer Pattern in the Spring framework. Since this is a minimal approach to the Spring framework, I was able to show the relationship between the ApplicationContext and it's implementation FileSystemXmlApplicationContext class. This process for starting Spring applications is a very easy way to leverage an incredible framework.
Part of this framework is the MethodInvokingFactoryBean. When using it you are free to employ any parameter available to you such as an Integer, a String, or in our case, another Spring bean. By allowing you to expose methods in your context xml files you can be as flexible as you can dream. This article has covered the addListener() method of the Observer Pattern. I would like to extend a challenge to you to figure out how to implement the removeListener() method using strictly the Spring framework.
Lastly, the Observer Pattern is a common and very useful pattern. The practices shown in this article provide an example of how the concrete implementation of the Observer interface can be developed with no additional coupling to the concrete implementation of the Subject interface. This feature of Spring encourages good object oriented design techniques. As a final note, there is really no reason developers and designers can not find ways to marry proven design patterns with beautifully developed frameworks.
Listing 1. The Observer Interface
package springobserver;
public interface Observer {
public void update(String messageText);
}
Listing 2. The Subject Interface
package springobserver;
public interface Subject {
public void addListener(Observer o);
public void removeListener(Observer o);
public void notifyListeners();
}
Listing 3. The Town Crier
package springobserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TownCrier implements Subject {
private List townResident = new ArrayList();
private String messageText;
// this message is added so I can give
// this class a reason to call notifyListener.
public void setMessage(String message){
System.out.println("I'm the Town Crier and " +
"I've got a message: " + message);
this.messageText = message;
this.notifyListeners();
}
public void addListener(Observer o) {
this.townResident.add(o);
}
public void removeListener(Observer o) {
if (this.townResident.contains(o)){
this.townResident.remove(o);
}
}
// call the update method on
// each observer (town resident)
public void notifyListeners() {
for (Iterator iter = townResident.iterator(); iter.hasNext();) {
Observer listener = (Observer) iter.next();
listener.update(messageText);
}
}
}
Listing 4. The Town Residents
package springobserver;
public class TownResident implements Observer {
public void update(String messageText) {
System.out.println("Greetings my name is: " + this);
System.out.println("I heard: " + messageText);
}
-------- new class --------
package springobserver;
public class TownResident2 implements Observer {
public void update(String messageText) {
System.out.println("Greetings my name is: " + this);
System.out.println("I heard: " + messageText);
}
}
Listing 5. The Application Context XML (ObserverContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- This bean is the town crier.
He's responsible for notifying all town residents that are interested in his message -->
<bean id="townCrier" class="springobserver.TownCrier"/>
<!-- this bean is a town resident interested in the town criers messages -->
<bean id="townResident1" class="springobserver.TownResident"/>
<!-- this bean is another town resident interested in the town criers messages -->
<bean id="townResident2" class="springobserver.TownResident2"/>
<!-- this is a method invoking bean that registers the first town resident with
with the town crier -->
<bean id="registerTownResident1"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject"><ref local="townCrier"/></property>
<property name="targetMethod"><value>addListener</value></property>
<property name="arguments">
<list>
<ref bean="townResident1"/>
</list>
</property>
</bean>
<!-- this is a method invoking bean that registers the second town
resident with the town crier -->
<bean id="registerTownResident2"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject"><ref local="townCrier"/></property>
<property name="targetMethod"><value>addListener</value></property>
<property name="arguments">
<list>
<ref bean="townResident2"/>
</list>
</property>
</bean>
</beans>
Listing 6. Example Run
package springobserver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class ExampleRun {
public static void main(String[] args) {
// launch the spring frame work.
ApplicationContext ctx = new FileSystemXmlApplicationContext(
"/config/ObserverContext.xml");
// grab the Town Crier out of the spring
// framework and send a message too all observers
TownCrier crier = (TownCrier) ctx.getBean("townCrier");
crier.setMessage("It is 1 O'Clock and all is well!");
}
}
Example 1. System Output
I'm the Town Crier and I've got a message: It is 1 O'Clock and all is well!
Greetings my name is: springobserver.TownResident@80fa6f
I heard: It is 1 O'Clock and all is well!
Greetings my name is: springobserver.TownResident2@1b9ce4b
I heard: It is 1 O'Clock and all is well!