3

扒去Spring事件监听机制的外衣,竟然是观察者模式

 1 year ago
source link: https://www.51cto.com/article/722686.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

扒去Spring事件监听机制的外衣,竟然是观察者模式

作者:丑胖侠二师兄 2022-11-15 07:35:50
本篇文章带大家从观察者模式、Java事件机制延伸到Spring的事件监听机制,将三者融合在一起来讲解。通过这个案例,其实我们能够体会到一些经验性的知识,比如看似复杂的Spring事件监听机制实现只不过是观察者模式的一种实现,而其中又集成了Java的事件机制。这也就是所谓的融会贯通。

Spring中提供了一套默认的事件监听机制,在容器初始化时便使用了这套机制。同时,Spring也提供了事件监听机制的接口扩展能力,开发者基于此可快速实现自定义的事件监听功能。

Spring的事件监听机制是在JDK事件监听的基础上进行的扩展,也是在典型观察者模式上的进一步抽象和改进。所以,结合Spring的事件监听机制与观察者模式来学习,可以达到理论与实践的完美融合。

本篇文章就以观察者模式和Spring事件监听机制作为切入点,结合具体的实例来对两者进行系统的学习和实践。

观察者模式

观察者模式(Observer Pattern),也叫作发布-订阅模式(Publish/Subscribe)。

无论是观察者模式,还是Spring的事件监听机制,本质上都是在定义对象间一对多的依赖关系,使得每当一个对象(被观察者/事件)改变状态时,所有依赖于它的对象(观察者/事件监听器)都会得到通知,并被自动更新。

观察者模式的优点在于:观察者和被观察者之间是抽象耦合,不管是新增观察者或是被观察者,都非常容易扩展。这也符合面向对象所倡导的“开闭原则”:对扩展开放,对修改关闭。

观察者模式适用于以下三类场景:

  • 关联行为场景,而且关联是可拆分的。
  • 事件多级触发场景。
  • 跨系统的消息交换场景,比如消息队列的处理机制。

在使用的过程中,也要综合考虑开发效率和运行效率的问题。通常,一个被观察者会对应多个观察者,那么在开发和调试的过程中会有一定的复杂度。

同时,因为被观察者存在关联、多级拆分,也就是会有多个观察者,而Java消息的通知(和Spring的事件监听机制)默认是顺序执行的,如果其中一个观察者执行时间过长或卡死,势必会影响整体的效率。此时,就需要考虑异步处理。

观察者的角色定义

观察者模式是一个典型的发布-订阅模型,其中主要涉及四个角色:

  • 抽象被观察者角色:内部持有所有观察者角色的引用,并对外提供新增、移除观察者角色、通知所有观察者的功能;
  • 具体被观察者角色:当状态变更时,会通知到所有的观察者角色;
  • 抽象观察者角色:抽象具体观察者角色的一些共性方法,如状态变更方法;
  • 具体观察者角色:实现抽象观察者角色的方法;

UML类图展示类观察者模式大体如下:

图片

观察者模式类图

以具体的代码来展示一下观察者模式的实现。

第一,定义抽象观察者。

/**
 * 抽象观察者角色
 * @author sec
 **/
public abstract class AbstractObserver {

 /**
  * 接收消息
  * @param context 消息内容
  */
 public abstract void receiveMsg(String context);

}

第二,定义抽象被观察者。

/**
 * 抽象主题(抽象被观察者角色)
 * @author sec
 **/
public abstract class AbstractSubject {

 /**
  * 持有所有抽象观察者角色的集合引用
  */
 private final List<AbstractObserver> observers = new ArrayList<>();

 /**
  * 添加一个观察者
  * @param observer 观察者
  */
 public void addObserver(AbstractObserver observer){
  observers.add(observer);
 }

 /**
  * 移除一个观察者
  * @param observer 观察者
  */
 public void removeObserver(AbstractObserver observer){
  observers.remove(observer);
 }

 /**
  * 通知所有的观察者,执行观察者更新方法
  * @param context 通知内容
  */
 public void notifyObserver(String context){
  observers.forEach(observer -> observer.receiveMsg(context));
 }
}

第三,定义具体被观察者,实现了抽象被观察者。

/**
 * 具体被观察者
 * @author sec
 **/
public class ConcreteSubject extends AbstractSubject{
 
 /**
  * 被观察者发送消息
  * @param context 消息内容
  */
 public void sendMsg(String context){
  System.out.println("具体被观察者角色发送消息: " + context);
  super.notifyObserver(context);
 }
}

第四,定义具体观察者,实现了抽象观察者。

/**
 * 具体观察者角色实现类
 * @author sec
 **/
public class ConcreteObserver extends AbstractObserver{

 @Override
 public void receiveMsg(String context) {
  System.out.println("具体观察者角色接收消息: " + context);
 }
}

第五,使用演示类。

public class ObserverPatternTest {

 public static void main(String[] args) {
  ConcreteSubject subject = new ConcreteSubject();
  subject.addObserver(new ConcreteObserver());
  subject.sendMsg("Hello World!");
 }
}

执行上述方法,控制台打印日志为:

具体被观察者角色发送消息: Hello World!
具体观察者角色接收消息: Hello World!

在上述代码实现中,被观察者发出消息后,观察者接收到具体的消息,如果添加了多个观察者,它们均会收到消息。也就是前面所说的,每当一个对象(被观察者/事件)改变状态时,所有依赖于它的对象(观察者/事件监听器)都会得到通知,并被自动更新。

Java中的事件机制

前面聊了观察者模式,这里再来看看Java中的事件机制。

在JDK 1.1及以后版本中,事件处理模型采用基于观察者模式的委派事件模型(DelegationEvent Model, DEM),即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。

这并不是说事件模型是基于Observer和Observable的,事件模型与Observer和Observable没有任何关系,Observer和Observable只是观察者模式的一种实现而已。

Java中的事件机制有三个角色参与:

  • Event Source:事件源,发起事件的主体。
  • Event Object:事件状态对象,传递的信息载体,可以是事件源本身,一般作为参数存在于listerner的方法之中。所有事件状态对象都将从Java中的EventObject派生而来;
  • Event Listener:事件监听器,当监听到EventObject产生时,调用相应的方法进行处理。所有事件侦听器接口必须扩展EventListener接口;

UML类图展示类事件模式大体如下:

图片

在上面的UML图中,EventObject一般作为Listener处理方法的参数传入,而EventSource是事件的触发者,通过此对象注册相关的Listener,然后向Listener触发事件。

通过UML图的对比可以看出,事件监听模式和观察者模式大同小异,它们属于同一类型模式,都属于回调机制,主动推送消息,但在使用场景上有所区别。

观察者(Observer)相当于事件监听者(监听器),被观察者(Observable)相当于事件源和事件,事件监听比观察者模式要复杂一些,多了EventSource角色的存在。

以具体的代码来展示一下Java中的事件机制实现。

第一,定义事件对象。

/**
 * 事件对象
 *
 * @author sec
 **/
public class DoorEvent extends EventObject {

 private int state;

 /**
  * Constructs a prototypical Event.
  *
  * @param source The object on which the Event initially occurred.
  * @throws IllegalArgumentException if source is null.
  */
 public DoorEvent(Object source) {
  super(source);
 }

 public DoorEvent(Object source, int state) {
  super(source);
  this.state = state;
 }

 // 省略getter/setter方法 
}

第二,定义事件监听器接口。

/**
 * 事件监听器接口
 *
 * @author sec
 **/
public interface DoorListener extends EventListener {

 /**
  * 门处理事件
  * @param doorEvent 事件
  */
 void doorEvent(DoorEvent doorEvent);
}

第三,定义事件监听器的实现类。

public class CloseDoorListener implements DoorListener{
 @Override
 public void doorEvent(DoorEvent doorEvent) {
  if(doorEvent.getState() == -1){
   System.out.println("门关上了");
  }
 }
}

public class OpenDoorListener implements DoorListener{
 @Override
 public void doorEvent(DoorEvent doorEvent) {
  if(doorEvent.getState() == 1){
   System.out.println("门打开了");
  }
 }
}

这里实现了门的开和关两个事件监听器类。

第四,定义事件源EventSource。

public class EventSource {

 //监听器列表,监听器的注册则加入此列表
 private Vector<DoorListener> listenerList = new Vector<>();

 //注册监听器
 public void addListener(DoorListener eventListener) {
  listenerList.add(eventListener);
 }

 //撤销注册
 public void removeListener(DoorListener eventListener) {
  listenerList.remove(eventListener);
 }

 //接受外部事件
 public void notifyListenerEvents(DoorEvent event) {
  for (DoorListener eventListener : listenerList) {
   eventListener.doorEvent(event);
  }
 }
}

第五,测试类。

public class EventTest {

 public static void main(String[] args) {
  EventSource eventSource = new EventSource();
  eventSource.addListener(new CloseDoorListener());
  eventSource.addListener(new OpenDoorListener());

  eventSource.notifyListenerEvents(new DoorEvent("关门事件", -1));
  eventSource.notifyListenerEvents(new DoorEvent("开门时间", 1));
 }
}

执行测试类,控制台打印:

门关上了
门打开了

事件成功触发。

Spring中的事件机制

在了解了观察者模式和Java的事件机制之后,再来看看Spring中的事件机制。在Spring容器中,通过ApplicationEvent和ApplicationListener接口来实现事件监听机制。每次Event事件被发布到Spring容器中,都会通知对应的Listener。默认情况下,Spring的事件监听机制是同步的。

Spring的事件监听由三部分组成:

  • 事件(ApplicationEvent):该类继承自JDK中的EventObject,负责对应相应的监听器,事件源发生某事件是特定事件监听器被触发的原因;
  • 监听器(ApplicationListener):该类继承自JDK中的EventListener,对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生后的响应逻辑;
  • 事件发布器(ApplicationEventPublisher):对应于观察者模式中的被观察者/主题,负责通知观察者,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。

通过上面的分析可以看出Spring的事件机制不仅是观察者模式的一种实现,也实现了JDK提供的事件接口。同时,除了发布者和监听者之外,还存在一个EventMulticaster的角色,负责把事件转发给监听者。

Spring事件机制的工作流程如下:

图片

Spring事件机制

在上述流程中,发布者调用applicationEventPublisher.publishEvent(msg),将事件发送给EventMultiCaster。EventMultiCaster注册着所有的Listener,它会根据事件类型决定转发给那个Listener。

在Spring中提供了一些标准的事件,比如:ContextRefreshEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent等。

关于Spring事件机制的具体实现和这些标准事件的作用,大家可以通过阅读源码来学习,这里不再详细展开。

下面来看看Spring事件机制涉及到的几个角色的源码及后续基于它们的实践。

第一,事件(ApplicationEvent)。

public abstract class ApplicationEvent extends EventObject {

 /** use serialVersionUID from Spring 1.2 for interoperability. */
 private static final long serialVersionUID = 7099057708183571937L;

 /** System time when the event happened. */
 private final long timestamp;

 /**
  * Create a new {@code ApplicationEvent}.
  * @param source the object on which the event initially occurred or with
  * which the event is associated (never {@code null})
  */
 public ApplicationEvent(Object source) {
  super(source);
  this.timestamp = System.currentTimeMillis();
 }

 /**
  * Return the system time in milliseconds when the event occurred.
  */
 public final long getTimestamp() {
  return this.timestamp;
 }

}

事件可类比观察者中的被观察者实现类的角色,继承自JDK的EventObject。上述Spring中的标准事件都是直接或间接继承自该类。

第二,事件发布器(ApplicationEventPublisher)。

@FunctionalInterface
public interface ApplicationEventPublisher {

 default void publishEvent(ApplicationEvent event) {
  publishEvent((Object) event);
 }

 void publishEvent(Object event);
}

通过实现ApplicationEventPublisher接口,并重写publishEvent()方法,可以自定义事件发布的逻辑。ApplicationContext继承了ApplicationEventPublisher接口。因此,我们可以通过实现ApplicationContextAware接口,注入ApplicationContext,然后通过ApplicationContext的publishEvent()方法来实现事件发布功能。

ApplicationContext容器本身仅仅是对外提供了事件发布的接口publishEvent(),真正的工作委托给了具体容器内部的ApplicationEventMulticaster对象。而ApplicationEventMulticaster对象可类比观察者模式中的抽象被观察者角色,负责持有所有观察者集合的引用、动态添加、移除观察者角色。

第三,事件监听器(ApplicationListener)。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

 /**
  * Handle an application event.
  * @param event the event to respond to
  */
 void onApplicationEvent(E event);

}

事件监听器(ApplicationListener)对应于观察者模式中的具体观察者角色。当事件发布之后,就会执行事件监听器的逻辑。通过实现ApplicationListener接口,并重写onApplicationEvent()方法,就可以监听到事件发布器发布的事件。

Spring事件监听案例

下面以具体的案例代码来说明如何自定义实现Spring事件监听。

第一,自定义定义事件对象,集成自ApplicationEvent。

public class MyEvent extends ApplicationEvent {
 /**
  * Create a new {@code ApplicationEvent}.
  *
  * @param source the object on which the event initially occurred or with
  *               which the event is associated (never {@code null})
  */
 public MyEvent(Object source) {
  super(source);
 }

 private String context;

 public MyEvent(Object source, String context){
  super(source);
  this.context = context;
 }

 public String getContext() {
  return context;
 }

 public void setContext(String context) {
  this.context = context;
 }
}

第二,自定义ApplicationListener事件监听器。

@Component
public class MyApplicationListener implements ApplicationListener<MyEvent> {
 @Override
 public void onApplicationEvent(MyEvent event) {
  // 监听到具体事件,处理对应具体逻辑
  System.out.println("event.getContext() = " + event.getContext());
 }

}

除了上述基于实现ApplicationListener接口的方式外,还可以使用**@EventListener**注解来实现,实现示例如下:

@Component
public class MyApplicationListener{

    // 通过注解实现监听器
    @EventListener
    public void handleMyEvent(MyEvent event){
        // 监听到具体事件,处理对应具体逻辑
    System.out.println("event.getContext() = " + event.getContext());
    }
}

第三,使用及单元测试。

@Slf4j
@SpringBootTest
public class SpringEventTest {

 @Autowired
 private ApplicationEventPublisher eventPublisher;

 @Test
 void testEvent() {
  eventPublisher.publishEvent(new MyEvent("自定义事件", "Hello World!"));
 }
}

执行单元测试,可看到控制台打印对应的事件信息。

通过上述方式我们已经成功实现了基于Spring的事件监听机制,但这其中还有一个问题:同步处理。默认情况下,上述事件是基于同步处理的,如果其中一个监听器阻塞,那么整个线程将处于等待状态。

那么,如何使用异步方式处理监听事件呢?只需两步即可。

第一步,在监听器类或方法上添加@Async注解,例如:

@Component
@Async
public class MyApplicationListener implements ApplicationListener<MyEvent> {
 @Override
 public void onApplicationEvent(MyEvent event) {
  // 监听到具体事件,处理对应具体逻辑
  System.out.println("event.getContext() = " + event.getContext());
 }

}

第二步,在SpringBoot启动类(这里以SpringBoot项目为例)上添加@EnableAsync注解,例如:

@SpringBootApplication
@EnableAsync
public class SpringBootMainApplication {

 public static void main(String[] args) {
  SpringApplication.run(SpringBootMainApplication.class, args);
 }
}

此时,就可以实现异步监听功能了。当然,@Async注解也可以指定我们已经配置好的线程池来处理异步请求,关于线程数的初始化这里就不再演示了。

本篇文章带大家从观察者模式、Java事件机制延伸到Spring的事件监听机制,将三者融合在一起来讲解。通过这个案例,其实我们能够体会到一些经验性的知识,比如看似复杂的Spring事件监听机制实现只不过是观察者模式的一种实现,而其中又集成了Java的事件机制。这也就是所谓的融会贯通。

我们如果单纯的学习某一个设计模式,可能只会运用和识别它的简单实现,而实践中往往会对设计模式进行变种,甚至融合多种设计模式的优点于一体,这便是活学活用。希望通过这边文章你能够更加深入的理解上述三者。

参考文章:

​https://blog.csdn.net/Weixiaohuai/article/details/122367792​

​https://www.cnblogs.com/admol/p/14036564.html​

​https://blog.csdn.net/qq_30364247/article/details/123168800​

​https://cloud.tencent.com/developer/article/1701947​

​https://www.jianshu.com/p/81a8ca9a6ffa​

责任编辑:武晓燕 来源: 程序新视界

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK