• Spring Event


    • 前言

    • ApplicationEvent 与 ApplicationListener 应用

      • 实现

      • 基于注解

      • 事件过滤

      • 异步事件监听

    • 好处及应用场景

    • 源码阅读

    • 总结

    1前言

    ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现了 ApplicationListener 接口的 bean 部署到容器中,则每次将 ApplicationEvent 发布到ApplicationContext 时,都会通知到该 bean,这简直是典型的观察者模式。设计的初衷就是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

    Spring 中提供了以下的事件

    图片

    2ApplicationEvent 与 ApplicationListener 应用

    实现

    1、自定义事件类,基于 ApplicationEvent 实现扩展

    1. public class DemoEvent extends ApplicationEvent {
    2.     private static final long serialVersionUID = -2753705718295396328L;
    3.     private String msg;
    4.     public DemoEvent(Object source, String msg) {
    5.         super(source);
    6.         this.msg = msg;
    7.     }
    8.     public String getMsg() {
    9.         return msg;
    10.     }
    11.     public void setMsg(String msg) {
    12.         this.msg = msg;
    13.     }
    14. }

    2、定义 Listener 类,实现 ApplicationListener接口,并且注入到 IOC 中。等发布者发布事件时,都会通知到这个bean,从而达到监听的效果。

    1. @Component
    2. public class DemoListener implements ApplicationListener<DemoEvent> {
    3.     @Override
    4.     public void onApplicationEvent(DemoEvent demoEvent) {
    5.         String msg = demoEvent.getMsg();
    6.         System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    7.     }
    8. }

    3、要发布上述自定义的 event,需要调用 ApplicationEventPublisher 的 publishEvent 方法,我们可以定义一个实现 ApplicationEventPublisherAware 的类,并注入 IOC来进行调用。

    1. @Component
    2. public class DemoPublisher implements ApplicationEventPublisherAware {
    3.     private ApplicationEventPublisher applicationEventPublisher;
    4.     @Override
    5.     public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    6.         this.applicationEventPublisher = applicationEventPublisher;
    7.     }
    8.     public void sendMsg(String msg) {
    9.         applicationEventPublisher.publishEvent(new DemoEvent(this, msg));
    10.     }
    11. }

    4、客户端调用 publisher

    1. @RestController
    2. @RequestMapping("/event")
    3. public class DemoClient implements ApplicationContextAware {
    4.     private ApplicationContext applicationContext;
    5.     @Override
    6.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    7.         this.applicationContext = applicationContext;
    8.     }
    9.     @GetMapping("/publish")
    10.     public void publish(){
    11.         DemoPublisher bean = applicationContext.getBean(DemoPublisher.class);
    12.         bean.sendMsg("发布者发送消息......");
    13.     }
    14. }

    输出结果:

    bean-listener 收到了 publisher 发布的消息: 发布者发送消息......

    基于注解

    我们可以不用实现 AppplicationListener 接口 ,在方法上使用 @EventListener 注册事件。如果你的方法应该侦听多个事件,并不使用任何参数来定义,可以在 @EventListener 注解上指定多个事件。

    重写 DemoListener 类如下:

    1. public class DemoListener {
    2.     @EventListener(value = {DemoEvent.class, TestEvent.class})
    3.     public void processApplicationEvent(DemoEvent event) {
    4.         String msg = event.getMsg();
    5.         System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    6.     }
    7. }
    事件过滤

    如果希望通过一定的条件对事件进行过滤,可以使用 @EventListener 的 condition 属性。以下实例中只有 event 的 msg 属性是 my-event 时才会进行调用。

    1. @EventListener(value = {DemoEvent.class, TestEvent.class}, condition = "#event.msg == 'my-event'")
    2. public void processApplicationEvent(DemoEvent event) {
    3.      String msg = event.getMsg();
    4.      System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    5.  }

    此时,发送符合条件的消息,listener 才会侦听到 publisher 发布的消息。

    bean-listener 收到了 publisher 发布的消息: my-event

    异步事件监听

    前面提到的都是同步处理事件,那如果我们希望某个特定的侦听器异步去处理事件,如何做?

    使用 @Async 注解可以实现类内方法的异步调用,这样方法在执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,即可继续其他的操作。

    1. @EventListener
    2. @Async
    3. public void processApplicationEvent(DemoEvent event) {
    4.     String msg = event.getMsg();
    5.     System.out.println("bean-listener 收到了 publisher 发布的消息: " + msg);
    6. }

    使用异步监听时,有两点需要注意:

    • 如果异步事件抛出异常,则不会将其传播到调用方。

    • 异步事件监听方法无法通过返回值来发布后续事件,如果需要作为处理结果发布另一个事件,请插入 ApplicationEventPublisher 以手动发布事件

    3好处及应用场景

    ApplicationContext 在运行期会自动检测到所有实现了 ApplicationListener 的 bean,并将其作为事件接收对象。当我们与 spring 上下文交互触发 publishEvent 方法时,每个实现了 ApplicationListener 的 bean 都会收到 ApplicationEvent 对象,每个 ApplicationListener 可以根据需要只接收自己感兴趣的事件。

    这样做有什么好处呢?

    在传统的项目中,各个业务逻辑之间耦合性比较强,controller 和 service 间都是关联关系,然而,使用 ApplicationEvent 监听 publisher 这种方式,类间关系是什么样的?我们不如画张图来看看。

    DemoPublisher 和 DemoListener 两个类间并没有直接关联,解除了传统业务逻辑两个类间的关联关系,将耦合降到最小。这样在后期更新、维护时难度大大降低了。

    图片

    ApplicationEvent 使用观察者模式实现,那什么时候适合使用观察者模式呢?观察者模式也叫 发布-订阅模式,例如,微博的订阅,我们订阅了某些微博账号,当这些账号发布消息时,我们都会收到通知。

    总结来说,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,从而实现广播的效果。

    4源码阅读

    图片

    Spring中的事件机制流程

    1、ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的pulishEvent方法发布事件

    2、ApplicationEventMulticaster就是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的。它通过父类AbstractApplicationEventMulticastergetApplicationListeners方法从事件注册表(事件-监听器关系保存)中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑

    3、ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口,本图中列出了典型的几个子类。其中RestartApplicationListnener在SpringBoot的启动框架中就有使用

    4、在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法

    ChatGPT中文网站:https://ai.cxyquan.com/   

    通过上图就能较清晰的知道当一个事件源产生事件时,它通过事件发布器ApplicationEventPublisher发布事件,然后事件广播器ApplicationEventMulticaster会去事件注册表ApplicationContext中找到事件监听器ApplicationListnener,并且逐个执行监听器的onApplicationEvent方法,从而完成事件监听器的逻辑。

    来到ApplicationEventPublisher 的 publishEvent 方法内部

    1. protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    2.  if (this.earlyApplicationEvents != null) {
    3.  this.earlyApplicationEvents.add(applicationEvent);
    4.  }
    5.  else {
    6.   // 
    7.   getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    8.  }
    9. }

    多播事件方法

    1. @Override
    2. public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    3.  ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    4.  Executor executor = getTaskExecutor();
    5.  // 遍历所有的监听者
    6.  for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    7.   if (executor != null) {
    8.    // 异步调用监听器
    9.    executor.execute(() -> invokeListener(listener, event));
    10.   }
    11.   else {
    12.    // 同步调用监听器
    13.    invokeListener(listener, event);
    14.   }
    15.  }
    16. }

    invokeListener

    1. protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
    2.  ErrorHandler errorHandler = getErrorHandler();
    3.  if (errorHandler != null) {
    4.   try {
    5.    doInvokeListener(listener, event);
    6.   }
    7.   catch (Throwable err) {
    8.    errorHandler.handleError(err);
    9.   }
    10.  }
    11.  else {
    12.   doInvokeListener(listener, event);
    13.  }
    14. }

    doInvokeListener

    1. private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    2.  try {
    3.   // 这里是事件发生的地方
    4.   listener.onApplicationEvent(event);
    5.  }
    6.  catch (ClassCastException ex) {
    7.   ......
    8.  }
    9. }

    点击 ApplicationListener 接口 onApplicationEvent 方法的实现,可以看到我们重写的方法。

    图片

    5总结

    Spring 使用反射机制,获取了所有继承 ApplicationListener 接口的监听器,在 Spring 初始化时,会把监听器都自动注册到注册表中。

    Spring 的事件发布非常简单,我们来总结一下:

    • 定义一个继承 ApplicationEvent 的事件

    • 定义一个实现 ApplicationListener 的监听器或者使用 @EventListener 监听事件

    • 定义一个发送者,调用 ApplicationContext 直接发布或者使用 ApplicationEventPublisher 来发布自定义事件

    最后,发布-订阅模式可以很好的将业务逻辑进行解耦(上图验证过),大大提高了可维护性、可扩展性。

  • 相关阅读:
    虚拟现实(VR)和增强现实(AR)
    Vue 安装 vue的基本使用 vue的初步使用步骤
    132 分割回文串II
    Django admin 站点管理
    代码随想录算法训练营第三十一天| 455 分发饼干 376 摆动序列 53 最大子数组和
    在 4GB 物理内存的机器上,申请 8G 内存会怎么样?
    「运维有小邓」EventLog Analyzer实时告警通知
    【MyBatis笔记06】MyBatis中的三种关联查询方式(一对一、一对多、多对多)
    OpenCV实现单目相机检测物体尺寸
    家谱文化④:江苏百岁老人多达8375人,健康长寿的秘密都写在家谱
  • 原文地址:https://blog.csdn.net/Ruby_One/article/details/133990973