Spring事件监听机制观察者模式详解

目录
  • 前言
  • 观察者模式
    • 观察者的角色定义
  • Java中的事件机制
  • Spring中的事件机制
  • Spring事件监听案例
  • 小结

前言

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容器中,通过ApplicationEventApplicationListener接口来实现事件监听机制。每次Event事件被发布到Spring容器中,都会通知对应的Listener。默认情况下,Spring的事件监听机制是同步的。

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

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

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

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的事件机制。这也就是所谓的融会贯通。

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

以上就是Spring事件监听机制观察者模式详解的详细内容,更多关于Spring事件监听观察者模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring Event观察者模式事件监听详解

    目录 Spring Event事件监听 Spring Event同步使用 自定义事件 定义监听器 定义发布者 测试执行 Debug执行流程 Spring Event 异步使用 自定义事件 定义监听器 定义发布者 开启异步支持 Spring Event事件监听 Spring Event(Application Event)其实就是一个观察者设计模式,一个 Bean 处理完成任务后希望通知其它 Bean 或者说一个 Bean 想观察监听另一个Bean 的行为.在开发中我们经常就会遇到修改一个bean

  • Spring事件监听器之@EventListener原理分析

    目录 Spring事件监听器之@EventListener原理 一.解析@EventListener前的准备工作 二.开始解析@EventListener EventListener.Factory EventListener.Factory监听网络请求全过程 问题是如何将这些数据回传回来呢 Spring事件监听器之@EventListener原理 Spring为我们提供的一个事件监听.订阅的实现,内部实现原理是观察者设计模式:为的就是业务系统逻辑的解耦,提高可扩展性以及可维护性.事件发布者并不

  • SpringBoot利用切面注解及反射实现事件监听功能

    目录 前言 效果图 监听原理 核心源码 源码地址 前言 当某个事件需要被监听的时候,我们需要去做其他的事前,最简单的方式就是将自己的业务 方法追加到该事件之后. 但是当有N多个这样的需求的时候我们都这样一个个去添加修改事件的源码吗? 这篇文章将告诉你如何用一个注解,就可以将你的业务代码通过切面的方式添加到事件的前后,而不需要修改事件的代码 效果图 如下图所示,add方法内并没有调用其他的方法,但是其他方法仍然被执行了. 只要给监听方法加@AddEventListener()注解就可以让它在事件前

  • java和Spring中观察者模式的应用详解

    目录 一.观察者模式基本概况 1.概念 2.作用 3.实现方式 二.java实现两种观察者模式 1.Observer接口和Observable类 2.EventObject和EventListener 三.Spring事件监听实战及原理 1.Spring如何使用EventObject和EventListener实现观察者? 2.先实战-要先会用 3.会原理-搞清楚为什么会这样 四.最后一张图总结 一.观察者模式基本概况 1.概念 观察者模式(Observer Design Pattern)也被称

  • springboot 事件监听器的案例详解

    目录 前言 引导案例 一.通过实现ApplicationListener接口实现步骤 1.自定义一个事件类(对象),继承ApplicationEvent 2.自定义业务类实现ApplicationListener 接口 3.主线业务发布事件 二.通过添加 @EventListener 注解来实现 三.使用异步 前言 在spring框架中,提供了很多动态灵活且可扩展的机制,开发者可以利用这些机制完成一些巧妙的业务,实现一些业务中的解耦, 引导案例 下面看一个简单的案例, @Configuratio

  • Spring事件监听机制观察者模式详解

    目录 前言 观察者模式 观察者的角色定义 Java中的事件机制 Spring中的事件机制 Spring事件监听案例 小结 前言 Spring中提供了一套默认的事件监听机制,在容器初始化时便使用了这套机制.同时,Spring也提供了事件监听机制的接口扩展能力,开发者基于此可快速实现自定义的事件监听功能. Spring的事件监听机制是在JDK事件监听的基础上进行的扩展,也是在典型观察者模式上的进一步抽象和改进.所以,结合Spring的事件监听机制与观察者模式来学习,可以达到理论与实践的完美融合. 本

  • Spring的事件监听机制示例详解

    前言 最近公司在重构广告系统,其中核心的打包功由广告系统调用,即对apk打包的调用和打包完成之后的回调,需要提供相应的接口给广告系统.因此,为了将apk打包的核心流程和对接广告系统的业务解耦,利用了spring的事件监听特性来满足需求.以下说明spring的事件机制的相关内容. 首先spring事件分为事件发布者(EventPublisher).事件监听者(EventListener),还包括一个事件广播者(这个是spring实现相关,这一节不讨论).使用spring事件机制,需要自定义事件发布

  • jQuery-mobile事件监听与用法详解

    本文实例讲述了jQuery-mobile事件监听与用法.分享给大家供大家参考,具体如下: 触摸事件 - 当用户触摸屏幕时触发(敲击和滑动) 滚动事件 - 当上下滚动时触发 方向事件 - 当设备垂直或水平旋转时触发 页面事件 - 当页面被显示.隐藏.创建.加载以及/或卸载时触发 一.初始化事件 1. ready 事件 页面加载完成 $(document).ready(function(){ //your code here... }); 2. 页面加载完成事件二 pageinit $(docume

  • Spring事件监听详解

    一.观察者模式 先来看下观察者模式,举个例子 警察和军人是观察者,犯罪嫌疑人是被观察者 代码实现: 定义被观察者接口: 定义观察者接口 定义坏人 定义好人: 定义好人2: 测试: 或者用JDK自带的观察者模式 定义坏人: 定义好人: 测试: 结果: 最后来总结一下,看下spring的事件 二.spring事件 下面来看下源码 1 初始化事件广播器 可以看到如果没有自定义的事件广播器,默认是使用SimpleApplicationEventMulticaster的 三.注册监听器 其实就是把监听器添

  • Android 滑动监听的实例详解

    Android 滑动监听的实例详解 摘要: ScollBy,ScollTo是对内容的移动,view.ScollyBy是对view的内容的移动 view,ScollTo是对内容的移动(移动到指定位置),view.ScollyBy是对view的内容的移动(移动距离) 在次activity中,当手指点击TextView ,此时是ViewGroup 响应还是TextView响应呢? 代码实践: 在activity中重写onTouchEvent(): public boolean onTouchEvent

  • Pygame实现监听鼠标示例详解

    目录 初始化参数 鼠标移动 鼠标点击位置 输出鼠标位置及其对用的按钮 完整代码  pygame如何捕捉鼠标的活动 初始化参数 import pygame, sys from pygame.locals import * def print_text(font, x, y, text, color=(0, 0, 0)): """打印字体函数""" img_text = font.render(text, True, color) screen.bl

  • Java Spring 事件监听详情解析

    目录 前言 需求背景 事件概念 定义 组成 事件实现 时序图 前言 前段时间因为工作的需要用到Spring事件,翻翻文档将功能实现了,但是存在少许理解不畅的地方,今天有空来梳理梳理. 需求背景 叶子同学在新入职公司,老大让他实现登陆功能,叶子随手写完,上线无bug,一切安好 //登陆伪代码 public void login(....){ userLogin(....); } 几天之后,老大说为维护用户的粘度,每天登陆送积分.叶子同学,二话不说,一顿操作后,上线无bug,一切安好 //登陆伪代码

  • 详谈Java中的事件监听机制

    鼠标事件监听机制的三个方面: 1.事件源对象: 事件源对象就是能够产生动作的对象.在Java语言中所有的容器组件和元素组件都是事件监听中的事件源对象.Java中根据事件的动作来区分不同的事件源对象,动作发生在哪个组件上,那么该组件就是事件源对象 2.事件监听方法: addMouseListener(MouseListener ml) ;该方法主要用来捕获鼠标的释放,按下,点击,进入和离开的动作:捕获到相应的动作后,交由事件处理类(实现MouseListener接口)进行处理. addAction

  • .NET事件监听机制的局限与扩展分析

    本文实例分析了.NET事件监听机制的局限与扩展.分享给大家供大家参考.具体分析如下: .NET中把"事件"看作一个基本的编程概念,并提供了非常优美的语法支持,对比如下C#和Java代码可以看出两种语言设计思想之间的差异. 复制代码 代码如下: // C# someButton.Click += OnSomeButtonClick; 复制代码 代码如下: // Java someButton.addActionListener(     new ActionListener(){    

  • Vue之监听方法案例详解

    vue中的监听方法 watch 注意 名字 你想监听哪个属性,就要和他起一样的名字 1.作用 用来监听vue实例中的数据变化 可以随时修改状态的变化 2.触发条件 当你监听的属性发生变化时,会自动调用对应的监听方法 3.使用场景 用于异步处理,开销比较大的运算 4.示例 watch:{ name(newvalue,oldvalue){ //计算属性可以接受两个参数,第一个参数是新的属性值,第二参数是老的属性值 // this.age // console.log('name属性发生变化了') c

随机推荐