介绍

观察者模式的本质是“定义对象之间的一对多依赖关系,以便当一个对象改变状态时,其所有依赖关系都会得到通知并自动更新。” GoF。 观察者模式是发布/订阅模式的子集,它允许许多观察者对象查看事件。

可以在不同的情况下使用此模式,但总而言之,可以说观察者模式可以在对象应该能够将消息通知其他对象并且您不希望这些对象紧密耦合时应用。 就我而言,当异步事件应通知一个或多个图形组件时,我使用了这种模式。

java.util.Observer/Observable类来实现此模式。 但是我的项目总是用Spring开发的,无论是Web还是桌面应用 。 因此,在当前文章中,我将解释如何使用Spring实现Observer模式。

手扶

Spring ApplicationContext中的事件处理是通过ApplicationEvent类和ApplicationListener接口提供的。 如果将实现ApplicationListener接口的bean部署到上下文中,则每次将ApplicationEvent发布到容器时, ApplicationListener都会接收到它。
Spring带有内置事件,例如ContextStartedEventContextStoppedEvent

观察者角色, 可观察角色和事件观察者是那些接收事件并且必须实现ApplicationListener类的人。 可观察类负责发布事件,并且必须实现ApplicationEventPublisherAware 。 最后, 事件类必须扩展ApplicationEvent

编码

Observer模式的Wikipedia示例,但是使用Spring Events而不是Observer / Observable Java类。 该示例是一个基本的发布/订阅示例,其中一个String消息从一个模块发送到另一个模块。
让我们创建MessageEvent 。 此事件包含一个String,它表示我们要发送的消息。 这是一个从ApplicationEvent扩展的简单类。

public class MessageEvent extends ApplicationEvent {
 
  **
   * 
   *
  private static final long serialVersionUID = 5743058377815147529L;
 
  private String message;
 
  public MessageEvent(Object source, String message) {
   super(source);
   this.message = message;
  }
 
  @Override
  public String toString() {
   StringBuilder builder = new StringBuilder();
   builder.append('MessageEvent [message=').append(message).append(']');
   return builder.toString();
  }
 
 }

Observable类。 此类必须实现ApplicationEventPublisherAware 。 此接口使用ApplicationEventPublisher作为参数定义了一个setter方法。 此参数用于发布事件。
在当前的实现中,该代码还实现了Runnable接口,因此用户可以从控制台输入中进行创建,

public class EventSource implements Runnable, ApplicationEventPublisherAware {
 
  private ApplicationEventPublisher applicationEventPublisher = null;
 
  public void setApplicationEventPublisher(
    ApplicationEventPublisher applicationEventPublisher) {
   this.applicationEventPublisher = applicationEventPublisher;
  }
 
  public void run() {
   final InputStreamReader isr = new InputStreamReader(System.in);
   final BufferedReader br = new BufferedReader(isr);
   while (true) {
    try {
     String response = br.readLine();
     System.out.println(Thread.currentThread().getName());
     this.applicationEventPublisher.publishEvent(new MessageEvent(this, response));
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
 }

Observer类甚至更简单。 实现ApplicationListener接口。 发布事件时将调用onApplicationEvent方法。 看到它是一个通用接口,因此不需要强制转换。 这不同于java.util.Observer类。

public class ResponseHandler implements ApplicationListener<MessageEvent> {
 
  public void onApplicationEvent(MessageEvent messageEvent) {
 
   System.out.println(Thread.currentThread().getName());
   System.out.println(messageEvent);
  }
 
 }

ApplicationListenerApplicationEventPublisherAware

最后是一个主类来测试系统。 创建一个线程以执行多个异步事件。

public class MyApp {
 
  public static void main(String args[]) {
 
   ApplicationContext applicationContext = new ClassPathXmlApplicationContext('classpath:META-INFspringapp-context.xml');
 
   EventSource eventSource = applicationContext.getBean('eventSource', EventSource.class);
   Thread thread = new Thread(eventSource);
   thread.start();
 
  }
 
 }

因此,启动程序并编写一些内容以进行控制台。 您将看到类似以下内容:
你好 Thread-0 Thread-0 MessageEvent [message = hello]
我输入了“ hello ”消息,并打印了事件发布者的线程名 。 然后发送事件并打印处理程序线程名称 。 最后显示接收到的事件。 有一件事情应该引起您的注意。 发送者( Observable )和接收者( Observer )都在同一线程中执行; 默认情况下,事件侦听器同步接收事件。 这意味着publishEvent()方法将阻塞,直到所有侦听器都已完成对事件的处理为止。 这种方法有很多优点(例如,重用事务上下文等),但是在某些情况下,您希望每个事件都在新线程中执行, Spring也支持此策略。

Spring中 ,负责事件管理的类是SimpleApplicationEventMulticaster

现在,我将解释如何初始化Spring Event Architecture以及如何进行修改。 默认情况下,当ApplicationContext 启动后,它将调用initApplicationEventMulticaster方法。 此方法验证是否存在与类型ApplicationEventMulticaster的ID applicationEventMulticaster的bean。 如果是这样,则使用已定义的ApplicationEventMulticaster ,否则,将创建具有默认配置的新SimpleApplicationEventMulticaster

SimpleApplicationEventMulticaster具有可用于指定哪些java.util.concurrent.Executor将执行事件setTaskExecutor。 因此,如果您希望每个事件在不同的线程中执行,那么一个好的方法是使用ThreadPoolExecutor 。 如上一段所述,现在我们必须显式定义SimpleApplicationEventMulticaster而不是

<beans xmlns='http:www.springframework.orgschemabeans' xmlns:xsi='http:www.w3.org2001XMLSchema-instance' xmlns:context='http:www.springframework.orgschemacontext' xmlns:task='http:www.springframework.orgschematask' xsi:schemaLocation='http:www.springframework.orgschematask http:www.springframework.orgschemataskspring-task-3.0.xsd http:www.springframework.orgschemabeans http:www.springframework.orgschemabeansspring-beans-3.0.xsd http:www.springframework.orgschemacontext http:www.springframework.orgschemacontextspring-context-3.0.xsd'>
   <bean id='eventSource' class='org.asotobu.oo.EventSource' > 
   <bean id='responseHandler' class='org.asotobu.oo.ResponseHandler' > 
   <task:executor id='pool' pool-size='10' > 
   <bean id='applicationEventMulticaster'        class='org.springframework.context.event.SimpleApplicationEventMulticaster'>
       <property name='taskExecutor' ref='pool' > 
   <bean>
 <beans>

SimpleApplicationEventMulticaster定义为ID为applicationEventMulticaster的bean。 然后设置任务池,然后我们重新运行主类。 输出将是:
你好 线程1 池1 MessageEvent [message = hello]
请注意,现在发送方和接收方线程有所不同。

ApplicationEventMulticaster 。 您只需要实现ApplicationEventMulticaster并使用applicationEventMulticaster

希望现在您的Spring桌面应用程序可以充分利用Spring事件来分隔模块。