美文网首页
Spring学习笔记:Spring事件及源码分析

Spring学习笔记:Spring事件及源码分析

作者: 大力papa | 来源:发表于2021-04-28 15:05 被阅读0次

本文仅供学习交流使用,侵权必删。
不作商业用途,转载请注明出处

1. 概述

在Spring Framework中,ApplicationContext是通过org.springframework.context.ApplicationEventorg.springframework.context.ApplicationListener管理事件。一个Bean如果实现了ApplicationListener接口,这个Bean就会监听指定类型的事件,只要这个指定事件在ApplicationContext中被发布,这个监听器就会被通知。这是一种典型的观察者模式实现

当前使用版本是Spring Framework 5.2.2.RELEASE

1. 1Spring的内建事件

事件类型 说明
ContextRefreshedEvent 当ApplicationContext初始化完成的时候会发布该事件
ContextStartedEvent 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#start方法启动会发布该事件
ContextStoppedEvent 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#stop方法停止容器会发布该事件
ContextClosedEvent 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#close方法或者通过JVM的shutdown hook关闭容器的会发布该事件
RequestHandledEvent web环境事件,当一个http请求完成后发布该事件。注意这个事件只有使用了Spring的org.springframework.web.servlet.DispatcherServlet的web程序下才能发布
ServletRequestHandledEvent RequestHandledEvent的子类,扩展了一下关于Servlet上下文信息

除此之外,还能能够通过继承org.springframework.context.ApplicationEvent实现自定义事件。下面将展示如何在Spring环境中实现事件的监听以及发布自定义事件。

2. 事件监听以及发布的代码实现

org.springframework.context.ApplicationListener监听器注册到应用上下文的方式有多种:

  1. 可以通过常规Spring Bean的方式注册。
  2. 可以构建一个外部对象通过ConfigurableApplicationContext#addApplicationListener注册到应用上下文中。
  3. 另外,从Spring4.2后开始支持以注解的方式注册。

并且监听器的回调方法执行有同步和异步两种方式。下面将展示具体的代码实现。

2.1 通过注册Spring Bean的方式注册监听器

  • 首先定义一个监听器实现类MyApplicationListener,并监听org.springframework.context.event.ContextRefreshedEvent事件。后面的代码实现基本都是继续沿用这个实现类
   public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
          
       @Override
          public void onApplicationEvent(ContextRefreshedEvent event) {
              System.out.println ("MyApplicationListener#onApplicationEvent event:" + event);
          }
      }
  • 编写Main方法启动Spring容器并注册监听器

    public class SpringEventDemo {
         public static void main(String[] args) {
                     AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
    
             //注册监听器
             applicationContext.register (MyApplicationListener.class);
             
             //启动Spring应用上下文
             applicationContext.refresh ();
         
          //关闭Spring应用上下文
             applicationContext.close();
         }
    }
    
  • 这里Main方法执行后这个console会打印

MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]

2.2 通过ConfigurableApplicationContext#addApplicationListener注册监听器

  • 编写Main方法启动Spring容器并注册监听器
 public class SpringEventDemo {
      public static void main(String[] args) {
                  AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
 
          //实例化监听器对象
          MyApplicationListener myApplicationListener = new MyApplicationListener ();
          
          //将监听器对象添加到Spring应用上下文
          applicationContext.addApplicationListener (myApplicationListener);
          
          //启动Spring应用上下文
          applicationContext.refresh ();
      
       //关闭Spring应用上下文
          applicationContext.close();
      }
 }
  • 这里Main方法执行后这个console会打印
MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]

2.3通过注解方式注册&设置监听器异步回调

  • 通过@EventListener标记在方法上设置监听器回调方法
  • 通过@Async和@EnableAsync开启异步执行
    • 开启异步回调监听器除了通过注解的方式,还能获取ApplicationContext的SimpleApplicationEventMulticaster,调用SimpleApplicationEventMulticaster#setTaskExecutor设置线程池开启异步执行。但这种方式要注意我们需要自己通过代码进行线程池关闭,Spring应用上下文不会关闭我们设置进去的线程池。
@EnableAsync
public class AnnotatedAsyncEventListenerDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
        applicationContext.register (AnnotatedAsyncEventListenerDemo.class);
        applicationContext.refresh ();
        applicationContext.publishEvent (new MyApplicationEvent ("AnnotatedAsyncEventListenerDemo#MyApplicationEvent"));
        applicationContext.close ();
    }

    @Async
    @EventListener
  public void onListenerEvent(ContextRefreshedEvent contextRefreshedEvent) {
       System.out.printf ("事件执行当前线程:%s,事件为:%s\n",Thread.currentThread ().getName (),contextRefreshedEvent);

    }
}
  • Main方法执行后这个console会打印,这里可以看到处理事件的线程并不是main线程
事件执行当前线程:SimpleAsyncTaskExecutor-1,事件为:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@5d099f62, started on Mon Apr 26 17:04:34 CST 2021]

2.4 自定义事件

尝试自定义一个事件并进行发布。

  • 定义自定义事件MyApplicationEvent
 public class MyApplicationEvent 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 MyApplicationEvent(String source) {
         super (source);
     }
 
     @Override
     public String getSource() {
         return (String) super.getSource ();
     }
 }
  • 定义监听自定义事件的监听器MyEventListener
    public class MyEventListener implements ApplicationListener<MyApplicationEvent> {
        @Override
        public void onApplicationEvent(MyApplicationEvent event) {
            System.out.printf ("执行事件:[%s]\n",event);
        }
    }
    
  • Main方法编写测试,这里通过org.springframework.context.ApplicationEventPublisherAware回调接口发布事件
  public class CustomizedEventDemo implements ApplicationEventPublisherAware {
  
      public static void main(String[] args) {
          AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
          applicationContext.register (CustomizedEventDemo.class);
          applicationContext.addApplicationListener (new MyEventListener ());
          applicationContext.refresh ();
          applicationContext.close ();
      }
  
      @Override
      public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
          applicationEventPublisher.publishEvent (new MyApplicationEvent ("hello,setApplicationEventPublisher"));
      }
  }

2.5 事件异常处理器

在Spring 3.0之后提供了一个org.springframework.util.ErrorHandler能够处理事件异常的情况,如图2-5-1。下面将展示如何向Spring应用上下文中注册事件异常处理器

图2-5-1
public class ErrorHandlerDemo {

    public static void main(String[] args) {
        GenericApplicationContext applicationContext = new GenericApplicationContext ();
        applicationContext.addApplicationListener (new MyEventListener ());
        applicationContext.refresh ();
        
        ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean (AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
            SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
                    (SimpleApplicationEventMulticaster) applicationEventMulticaster;
            /**
             *  Spring事件错误处理代码
             *  SimpleApplicationEventMulticaster中设置ErrorHandler
             *  {@link org.springframework.util.ErrorHandler}
             */
            simpleApplicationEventMulticaster.setErrorHandler (new ErrorHandler () {
                @Override
                public void handleError(Throwable t) {
                    System.err.println ("ErrorHandler处理事件错误," + t);
                }
            });

            /**
            设置一个主动抛出异常的监听器
            */
            simpleApplicationEventMulticaster.addApplicationListener (new ApplicationListener<MyApplicationEvent> () {
                @Override
                public void onApplicationEvent(MyApplicationEvent event) {
                    throw new RuntimeException ("主动抛出异常,测试事件错误处理");
                }
            });

        }
        // 发布MyApplicationEvent事件触发异步事件
        applicationEventMulticaster.multicastEvent (new MyApplicationEvent ("AsyncEventListenerDemo"));
        System.out.println ("关闭Spring应用上下文");
        applicationContext.close ();
    }
}
  • 这里Main方法会输出
ErrorHandler处理事件错误,java.lang.RuntimeException: 主动抛出异常,测试事件错误处理

以上就是Spring事件相关的操作,此外如果我们需要控制监听器的调用顺序,我们可以通过@Order控制具体的执行顺序。

3.Spring Event源码分析

3.1 容器启动时,事件的相关源码

这里从org.springframework.context.support.AbstractApplicationContext#refresh方法开始入手。首先refresh方法调用的prepareRefresh方法是容器启动前的准备,这里的相关操作是初始化一个名为earlyApplicationEventsSet<ApplicationEvent>,这是用于存放早期事件的一个容器,这个容器的出现了是为了修复Spring之前的一个缺陷,这里切换到Spring Framework 3.0的源码并尝试分析这个缺陷是什么以及到底如何出现的。

这里首先提前说一下,Spring应用上下文拥有事件发布的能力,该能力基于其成员属性org.springframework.context.event.ApplicationEventMulticaster这个接口的实现类实现的

  • 首先先看一下refresh方法的整体流程,如图3-1-1所示
    1. 首先会做启动前准备prepareBeanFactory
    2. 然后调用invokeBeanFactoryPostProcessor回调org.springframework.beans.factory.config.BeanFactoryPostProcessor的方法
    3. 再望后执行initApplicationEventMulticaster,这个方法是实例化org.springframework.context.event.ApplicationEventMulticaster
    4. 最后调用registerListeners,将用户的监听器注册到容器中
图3-1-1
  • 进入AbstractApplicationContext#prepareRefresh,当时并没有初始化早期事件的容器,如图3-1-2

    图3-1-2
  • 导致缺陷发生的场景如下,假设创建一个类是实现了 BeanFactoryPostProcessor以及ApplicationContextAware接口的。ApplicationContext是有发布事件的能力,其能力依赖于ApplicationEventMulticaster。当我们在通过ApplicationContextAware回调获取ApplicaitonContext实例后,然后在BeanFactoryPostProcessor的回调接口中利用返回的ApplicationContext实例发布实现,Spring应用上下文就会抛出异常,因为从上面的调用顺序来看,BeanFactoryPostProcessor的回调时间早于ApplicationEventMulticaster的实例化,这里的ApplicationEventMulticaster还是null的所以会抛出NPE。下面展示一下异常的示例

  • 定义一个调用类BugInvoker

public class BugInvoker implements BeanFactoryPostProcessor, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        applicationContext.publishEvent (new MyApplicationEvent ("hello world"));
    }
}
  • 编写Main方法测试
public class Spring3BugDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext ();
        //注册到容器中
        applicationContext.register (BugInvoker.class);

        applicationContext.addApplicationListener (new ApplicationListener () {
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                System.out.println (event);
            }
        });

        applicationContext.refresh ();
        applicationContext.close ();
    }
}
  • 这里是启动后抛出的具体异常信息,异常信息告诉用户ApplicationEventMulticaster还没有实例化完成
Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@378fd1ac: startup date [Tue Apr 27 14:46:22 CST 2021]; root of context hierarchy
    at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:307)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:294)
    at com.kgyam.event.springEvent.Spring3BugDemo$BugInvoke.postProcessBeanFactory(Spring3BugDemo.java:48)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:624)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:614)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:398)
    at com.kgyam.event.springEvent.Spring3BugDemo.main(Spring3BugDemo.java:31)

  • 而3.0之后加入的早期事件容器就是为了修复这个缺陷,对早于ApplicationEventMulticaster实例化前发布的事件先缓存在这个容器中,等待ApplicationEventMulticaster实例化完成后再对这个容器里的事件进行发布。

3.2 监听器注册

监听器的注册比较简单,通过org.springframework.context.support.AbstractApplicationContext#addApplicationListener作为入口

  • 将监听器添加到ApplicationEventMulticaster中,如图3-2-1


    图3-2-1
  • org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListener中的添加监听器逻辑是排除掉重复的监听器以免发生二次调用,然后就会把这个监听器对象添加到监听器列表中并清理监听器缓存Map,如图3-2-2

    图3-2-2
  • 到这里就基本完成了添加监听器的过程了,接下来看下事件发布相关源码。

3.3事件发布

事件发布通过org.springframework.context.support.AbstractApplicationContext#publishEvent作为入口

  • 首先根据发布的事件对象,如果是非ApplicationEvent类型的就会将其转换为PayloadApplicationEvent对象,如图3-3-1


    图3-3-1
  • 然后就是如果earlyApplicationEvents不为null就会将其存放到早期事件容器中,这里是表示applicationEventMulticaster对象没有初始化完成,将事件缓存到该早期事件容器中。在applicationEventMulticaster初始化完成并发布所有早期事件容器里面的事件后,会将该容器设置为null。
  • 如果applicationEventMulticaster应用事件广播器已经实例化,那么就会调用org.springframework.context.event.ApplicationEventMulticaster#multicastEvent发布事件,如图3-3-2
    图3-3-2
  • 往下进入ApplicationEventMulticaster的实现类org.springframework.context.event.SimpleApplicationEventMulticaster看下具体的事件发布逻辑,如图3-3-3
    图3-3-3
  • 调用getApplicationListeners方法获取对应事件类型对应的监听器列表,这其中的逻辑相对是比较多的。
  1. 首先会根据事件类型和事件源类型生成对应的监听器缓存key对象org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerCacheKey,根据这个key从retrieverCache(Map对象,如图3-3-4)获取监听器列表对象org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerRetriever,这个ListenerRetriever是存放了事件类型相关的监听器列表,
  2. 如果没有从缓存中获取到对应的监听器列表对象,就从defaultRetriever(存放所有监听器的对象,图3-3-5)中找到匹配的监听器生成对应的ListenerRetriever并重新缓存到retrieverCache中,最后返回监听器列表。


    图3-3-4
    图3-3-5
  • 返回监听器列表后就通过invokeListener遍历回调监听器,如图3-3-6。这里try-catch将捕获监听器抛出的异常并交给ErrorHandler处理,如图3-3-7


    图3-3-6
    图3-3-7
  • 到这里事件发布流程基本就结束了。但还有一个地方需要注意,我们回到org.springframework.context.event.ApplicationEventMulticaster#multicastEvent这里,如果应用上下文存在父子关系的话,该事件就会传递到父的应用上下文中,这种情况下注意出现事件被多次处理的情况,如图3-3-8。

    图3-3-8

3.4 @EventListener注册过程

org.springframework.context.event.EventListener的注释中有提及到该注解是通过一个内建bean对象org.springframework.context.event.EventListenerMethodProcessor进行处理的,如图3-4-1所示。

图3-4-1
  • 首先先看下EventListenerMethodProcessor类,如图3-4-2。对于处理EventListener注解的核心方法是在afterSingletonsInstantiated方法(实例化完成后处理接口org.springframework.beans.factory.SmartInitializingSingleton的接口方法)。将断点打在这个方法上作为入口看下对应的处理逻辑。

    图3-4-2
  • 该方法首先会获取容器中所有的beanName并遍历,然后根据beanName在容器中获取其对应的Class类型。

  • 获取到Class类型后找到所有标记了注解@EventListener的方法并放到一个名为annotatedMethods的Map中,如图3-4-3。


    图3-4-3
  • 如果annotatedMethods非空,就遍历这个Map并通过org.springframework.context.event.EventListenerFactory的实现类对其进行处理,如图3-4-5。这里会针对每个标记了注解的Method对象创建一个对应的事件监听适配器并通过应用上下文的addApplicationContext方法将其添加进去。

    图3-4-5
  • EventListenerFactory在Spring的内建实现只有一个org.springframework.context.event.DefaultEventListenerFactory,如图3-4-6。该接口主要有两个方法,第一个是判断是否支持该Method对象,DefaultEventListenerFactory默认返回true。第二个方法是针对该方法创建一个事件监听器,DefaultEventListenerFactory会创建一个事件监听适配器org.springframework.context.event.ApplicationListenerMethodAdapter#ApplicationListenerMethodAdapter

    图3-4-6
  • @EventListener注解的处理基本到这里就结束了。

4.总结

Spring Event使用的是典型的观察者模型,这种情况能够让后监听器更加符合单一职责原则,并且能够提高系统的扩展性。同时我们还知道了Spring应用上下文ApplicationContext是拥有事件发布的能力的,其能力是依赖于org.springframework.context.event.ApplicationEventMulticaster
最后还需要注意的是,如果使用Spring 3.0这种早期版本需要注意事件发布必须晚于事件广播器实例化。

参考文档

Core Technologies (spring.io)

相关文章

  • spring 源码分析及知识点总结

    参考:spring 源码分析及知识点总结Spring源码深度解析》学习笔记——Spring的整体架构与容器的基本实...

  • Spring学习笔记:Spring事件及源码分析

    本文仅供学习交流使用,侵权必删。不作商业用途,转载请注明出处 1. 概述 在Spring Framework中,A...

  • Spring Event 实现原理

    笔记简述本学习笔记主要介绍Spring的事件通知是如何实现的,以及源码分析 Demo 事件定义 继承 Applic...

  • 2021-02-27_ Spring源码学习笔记@AutoWir

    Spring源码学习笔记@AutoWire的propertyValues属性注入分析 1概述 本文基于Spring...

  • 2018-05-26

    spring源码分析(六) 目录五、spring源码分析--5.7、Spring JDBC 设计原理及二次开发--...

  • 2018-05-19

    spring源码分析(五) 目录五、源码分析--5.6、Spring AOP 设计原理及具体实践----5.6.1...

  • Spring Boot 学习笔记

    Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spr...

  • Spring之事件机制初始化流程

    描述 在Spring之事件机制模型中我们了解了spring事件机制的模型以及工作流程,下面通过源码分析spring...

  • Spring源码分析(一)

    Spring源码分析 一 基于Spring 5.1.5 前言:要分析Spring源码,首先得知道Spring是怎么...

  • spring IOC 源码分析(一)

    分析源码是一件很枯燥的事件。 spring boot的源码最后还是落到refresh(),spring这里来。故,...

网友评论

      本文标题:Spring学习笔记:Spring事件及源码分析

      本文链接:https://www.haomeiwen.com/subject/iclprltx.html