• Spring事件ApplicationEvent源码浅读



    Spring事件ApplicationEvent源码浅读 - Java技术债务

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

    Spring 中提供了以下的事件

    Event描述
    ContextRefreshedEventApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生
    ContextStartedEvent当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序
    ContextStoppedEvent当使用 ConfigurableApplicationContext 接口中的 stop() 方法停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作
    ContextClosedEvent使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启
    RequestHandledEvent这是一个 web-specific 事件,告诉所有 bean HTTP 请求已经被服务
    ServletRequestHandledEventRequestHandledEvent的一个子类,用于添加特定于Servlet的上下文信息。

    demo应用

    具体的详情可以访问:https://cuizb.top/myblog/static/resource/Untitled-1697189238408.png
    这里只是个demo例子

    实现

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

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

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

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

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

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

    4、 客户端调用publisher;

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

    输出结果:

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

    基于注解

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

    重写DemoListener 类如下:

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

    事件过滤

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

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

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

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

    异步事件监听

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

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

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

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

    • 如果异步事件抛出异常,则不会将其传播到调用方。
    • 异步事件监听方法无法通过返回值来发布后续事件,如果需要作为处理结果发布另一个事件,请插入 ApplicationEventPublisher 以手动发布事件

    源码解读

    ApplicationEvent事件机制流程:

    1. ApplicationEventPublisher是Spring的事件发布接口,事件源通过该接口的pulishEvent方法发布事件;
    2. ApplicationEventMulticaster就是Spring事件机制中的事件广播器,它默认提供一个SimpleApplicationEventMulticaster实现,如果用户没有自定义广播器,则使用默认的它通过父类AbstractApplicationEventMulticastergetApplicationListeners方法从事件注册表(事件-监听器关系保存)中获取事件监听器,并且通过invokeListener方法执行监听器的具体逻辑;
    3. ApplicationListener就是Spring的事件监听器接口,所有的监听器都实现该接口,本图中列出了典型的几个子类其中RestartApplicationListnenerSpringBoot的启动框架中就有使用;
    4. 在Spring中通常是ApplicationContext本身担任监听器注册表的角色,在其子类AbstractApplicationContext中就聚合了事件广播器ApplicationEventMulticaster和事件监听器ApplicationListnener,并且提供注册监听器的addApplicationListnener方法;

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

    来到ApplicationEventPublisher 的 publishEvent 方法内部

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

    多播事件multicastEvent方法

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

    在准备执行监听者方法时,会先获取容器中是否有默认的异步线程池,如果在容器启动时,声明了一个异步线程池,getTaskExecutor方法一定不为null,然后异步调用执行listener的业务方法,否则会同步调用执行listener。

    此时如果你使用注解@TransactionalEventListener监听,注解会失效。

    具体请看:https://cuizb.top/myblog/article/detail/1684739163,

    invokeListener方法

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

    doInvokeListener方法

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

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

    Spring事件ApplicationEvent源码浅读 - Java技术债务

    总结

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

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

    1. 定义一个继承ApplicationEvent的事件;
    2. 定义一个实现ApplicationListener的监听器或者使用@EventListener监听事件;
    3. 定义一个发送者,调用ApplicationContext直接发布或者使用ApplicationEventPublisher来发布自定义事件;

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

    Spring事件ApplicationEvent源码浅读 - Java技术债务


    --------------------------------------------------------------欢迎叨扰此地址---------------------------------------------------------------

    本文作者:Java技术债务
    原文链接:https://cuizb.top/myblog/article/detail/1697189495
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。

  • 相关阅读:
    学习笔记12--路径-速度分解法之局部路径搜索
    【机器学习】岭回归和LASSO回归详解以及相关计算实例-加利福尼亚的房价数据集、红酒数据集
    [Linux](5)gcc编译、gdb调试、make/makefile项目自动化构建工具、进度条小程序
    中级工程师职称评审中业绩材料具体有哪些呢?甘建二告诉你
    成考本科学位英语考试有固定合格分数线吗?
    基于Java+SpringBoot+MyBatis+Vue前后端分离宠物领养设计与实现
    算法基础:离散化及模板详解
    Tomcat启动后的日志输出为乱码
    【JavaWeb 之mybatis】一篇文字带你快速入门Mybatis
    MyBatis的缓存问题
  • 原文地址:https://blog.csdn.net/qq_40124555/article/details/133891324