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

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

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容