SpringBoot事件与监听机制

本文通过SpringBoot项目的运行,来探讨SpringBoot事件与监听机制。springboot版本:2.0.2.RELEASE

文章目录

  • SpringBoot事件与监听机制
    • 发现SpringBoot事件
      • SpringApplicationRunListeners的构造
      • SpringApplicationRunListener的构造
      • 事件的发布
        • 事件的类图
    • 监听者
    • 事件发布者
      • 核心的事件发布者

SpringBoot事件与监听机制

发现SpringBoot事件

通常我们启动应用就使用这么一条命令SpringApplication.run(XXXX.class,args);然后我们的项目就启动了。我们跟踪进去,就会来到下图的run方法。
SpringBoot事件与监听机制_第1张图片
从图中代码可知:

  1. 构造SpringApplication对象
  2. 调用该对象的run方法
  3. 该对象的run方法返回了实现ConfigurableApplicationContext接口的对象

与我们主题相关的内容在run方法里。
SpringBoot事件与监听机制_第2张图片
在方法里我们可以看到这条命令:
SpringApplicationRunListeners listeners = getRunListeners(args);然后后续的代码里与listeners相关的命令有这些:
listeners.starting();
prepareEnvironment(listeners, applicationArguments);
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
listeners.started(context);
listeners.running(context);
而随着这些命令的执行,就会发布不同的事件。后面我们将看看各条命令具体做了些什么事情,不过现在首先看看listeners是如何被实例化的。

SpringApplicationRunListeners的构造

SpringBoot事件与监听机制_第3张图片
从代码我们可以知道,它构造了SpringApplicationRunListeners对象。调用该对象的构造函数时传进了两个参数。我们先看SpringApplicationRunListeners构造函数的内容:
SpringBoot事件与监听机制_第4张图片
其实就是传入了日志器以及SpringApplicationRunListener集合,并将这两参数作为自己的属性。请注意,一个是SpringApplicationRunListeners,一个是SpringApplicationRunListener。从字面上看,前者是后者的复数形式。

SpringApplicationRunListener的构造

我们回到getRunListeners方法里,在调用SpringApplicationRunListeners构造函数时,以getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)命令的返回值作为第二个参数值。另外,getSpringFactoriesInstances方法会调用SpringFactoriesLoader.loadFactoryNames方法,该方法从类路径META-INF/spring.factories文件里找到以SpringApplicationRunListener的完整类名为key的属性值。属性值比较简单,就中只有一个类名org.springframework.boot.context.event.EventPublishingRunListener,然后通过反射机制,调用该类的构造函数得到该对象EventPublishingRunListener。
从当前版本的springboot来看,只有spring-boot-2.0.2.RELEASE.jar的spring.factories文件里才有定义。所以EventPublishingRunListener就是唯一的SpringApplicationRunListener了。
我们再回头看各自的定义。SpringApplicationRunListeners是一个类,而SpringApplicationRunListener是一个接口,后者声明的方法前者都有,而且前者方法基本上是轮循调用SpringApplicationRunListener集里的各个元素对应的方法,所以这种形式象极了组合模式。更进一步,我们完全可以象EventPublishingRunListener的声明方式那样,在自已工程里的/META-INF/spring.properties内也配置自定义的SpringApplicationRunListener实现类,这样就可以在SpringApplicationRunListeners的不同方法被调用时,发布自定义的事件了。

事件的发布

因为EventPublishingRunListener是唯一的SpringApplicationRunListener接口的实现类,所以它实现了接口声明的方法。从EventPublishingRunListener的代码不难看出每个方法都会发布不同的事件。现归纳如下:

方法 事件
starting ApplicationStartingEvent
environmentPrepared ApplicationEnvironmentPreparedEvent
contextPrepared
contextLoaded ApplicationPreparedEvent
started ApplicationStartedEvent
running ApplicationReadyEvent
failed ApplicationFailedEvent
事件的类图

SpringBoot事件与监听机制_第5张图片若仔细看的话会发现,Springboot的事件都是扩展于spring的ApplicationEvent。

监听者

现在已经知道springboot有什么事件和事件发布者是谁了,那么监听者又是从何而来?
我们暂时回到较早前的代码片段:
SpringBoot事件与监听机制_第6张图片
前文描述的事件发布平台、事件发布者都是在构造了SpringApplication对象后调用它的run方法里出现的。而监听者是在构造SpringApplication对象的过程中出现的。
SpringBoot事件与监听机制_第7张图片那么发布事件发布者与监听者是怎么开成关联呢?其实就在构造EventPublishingRunListener的时候就产生关联关系了:
SpringBoot事件与监听机制_第8张图片但监听者不是直接成为EventPublishingRunListener的属性,而是被加到它的initialMulticaster属性里面去了。
因为构造EventPublishingRunListener的时侯,是在 return new SpringApplication(primarySources).run(args); 这条命令的run阶段,而listener是在构造SpringApplication阶段产生,所以构造EventPublishingRunListener时能够获取到listener。

事件发布者

经过前文的铺垫,我们相当于已经知道事件的发布者是谁了。以发布ApplicationStartingEvent为例,发布事件的调用链如下:

SpringApplication XXXRunListeners XXXRunListener starting() 轮循调用starting() 发布ApplicationStartingEvent. SpringApplication XXXRunListeners XXXRunListener

所以可以这么粗略地理解,SpringApplicationRunListeners是发布平台,SpringApplicationRunListener是平台上具体的发布者。EventPublishingRunListener目前是唯一的SpringApplicationRunListener接口的实现类。

下面贴上EventPublishingRunListener各方法的代码片段:
SpringBoot事件与监听机制_第9张图片SpringBoot事件与监听机制_第10张图片SpringBoot事件与监听机制_第11张图片其中不难发现,starting和environmentPrepared方法的实现逻辑是一致的,contextLoaded方法在发布事件部分的逻辑也是跟starting和environmentPrepared两个方法一致,started和running方法就完全不一样了,最后的fail方法是在某种情况下发布事件的逻辑和starting和environmentPrepared相似,在另一种情况下与started和running方法的逻辑一样。这种不一致的处理会产生什么效果呢?后续文章将进一步探讨它。

核心的事件发布者

从上面的代码片段可以发现,starting、environmentPrepared、contextLoad和fail方法都有通过initialMulticaster属性对象的multicastEvent方法来发布事件。initialMulticaster的具体类型在EventPublishingRunListener的构造函数里已经告诉我们了,就是SimpleApplicationEventMulticaster。虽然从代码上看EventPublishingRunListener并不是所有方法都直接通过SimpleApplicationEventMulticaster来发布事件,但我们称它为核心的事件发布者是适当的,后面关Spring的事件与发布机制文章会从Spring的角度来探讨它的作用,在此我们先对他有个简单了解。

SpringBoot事件与监听机制_第12张图片

通过UML可知道,SimpleApplicationEventMulticaster继承于AbstractApplicationEventMulticaster,AbstractApplicationEventMulticaster实现了ApplicationEventMulticaster接口和BeanClassLoaderAware, BeanFactoryAware两个Spring机制内的接口,以及定义两个内部类。ApplicationEventMulticaster接口声明了关联Listener的方法(add, remove 开头的方法),以及发布事件的方法(multicastEvent)。通知阅读代码,其实与Listener产生关联的方法都在AbstractApplicationEventMulticaster实现,SimpleApplicationEventMulticaster主要实现了发布事件的方法。而AbstractApplicationEventMulticaster并不是简单地声明一个Listeners集合实现Listener的关联,它是与内部类一起管理着Listener,下面我们一起看看它内部是怎么样的。
SpringBoot事件与监听机制_第13张图片SpringBoot事件与监听机制_第14张图片
ListenerRetriever的定义并不复杂,有两个public的集合类的属性,这两个属性是在构造函数里初始化出空集合。另外还定义了getApplicationListeners的方法。那么很容易让人产生疑问,没有添加具体的Listener当调用getApplicationListeners的方法时怎么会有具体的数据呢?因为它是内部类,而且集合都是public, 所以在getApplicationListeners方法被调用前一定已经被外部直接添加数据了。在后文将会看到添加Listener的地方。这里还要注意下,其中applicationListenerBeans属性是Listener名字的集合,在getApplicationListeners方法里是通过beanFactory获取名字对应的Listener。整个方法是将两个集合的数据整合到一个集合里再返回。之所以在这方法里能访问beanFactory,是因为外部的AbstractApplicationEventMulticaster实现的BeanFactoryAware接口,在Spring的生命周期管理中会注入beanFactory对象。

AbstractApplicationEventMulticaster定义的另一个内部ListenerCacheKey,它实现了Comparable接口,从函数的定义看,它只有构造函数,并重写了equals和hashCode方法,也实现的Comparable接口的compareTo方法,貌似没有自己的业务函数。不过它重写了equals和hasCode方法,基本实现了自己的价值。AbstractApplicationEventMulticaster有两个主要的属性,其中一个是Map类型的retrieverCache。因为ListenerRetriever与Listner关联,所以不难想象,ListenerCacheKey起到对Listner分组的作用。
SpringBoot事件与监听机制_第15张图片
从代码来看,就是通过事件类型以及事件源类型来作为Listener分组的key。那么,retrieverCache的数据来自哪里呢?这跟另一个属性defaultRetriever有关。
前文提到,AbstractApplicationEventMulticaster实现了ApplicationEventMulticaster接口的与Listener关联的方法,如下图:
SpringBoot事件与监听机制_第16张图片SpringBoot事件与监听机制_第17张图片
奇怪的是,添加Listener时仅一个劲地往defaultRetriever的applicationListeners集合属性或者applicationListenerBeans集合属性添加数据,并没有往retrieverCache添加数据,而且还清空retrieverCache的内容;删除Listener时逻辑也相似。这是什么意思呢?什么时候才会为retrieverCache添加数据呢?
在protected Collection> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType)方法里可以找到添加数据的代码:
SpringBoot事件与监听机制_第18张图片
value部分的retriever是如何与Listener产生关联呢?其实是由上一行代码retrieveApplicationListeners(eventType, sourceType, retriever)准备好的。我们继续看这函数的代码:
SpringBoot事件与监听机制_第19张图片也就是说,retriever所关联的Listener来自于AbstractApplicationEventMulticaster的defaultRetriever集合属性,而且会经过事件类型和事件源类型的过滤(supportsEvent函数)。此时,我们回头思考getApplicationListeners函数的作用,就是从defaultRetriever集合属性关联的全量的Listener里抽出一部分,抽取的依据就是函数入参的事件类型以及事件源类型,并将抽出来的这部分Listener形成一组,以函数入参的事件类型以及事件源类型为key,存入retrieverCache属性里,并且返回符合事件类型以及事件源类型的Listener。虽然完整的getApplicationListeners函数并不仅这样,其他的逻辑分支是不存入retrieverCache属性里,但它就是唯一给retrieverCache添加数据的地方。retrieverCache的作用就是下次能够快速返回符合事件类型以及事件源类型的Listener。
而getApplicationListeners函数的调用者是SimpleApplicationEventMulticaster的multicastEvent方法。该方法就是前文提到的EventPublishingRunListener在发布事件时的调用的,以ApplicationStartingEvent事件为例:
SpringBoot事件与监听机制_第20张图片
现在终于将EventPublishingRunListener联系在一起了。当构造EventPublishingRunListener时,将/META-INF/spring.properties内以ApplicationListener完整类名为key的Listener都会加入到SimpleApplicationEventMulticaster对象的父类的defaultRetriever的applicationListeners集合里,当EventPublishingRunListener发布ApplicationStartingEvent事件时,SimpleApplicationEventMulticaster对象会以事件类型以及事件源类型调用父类的getApplicationListeners方法获取对应的Listener,此时会先看retrieverCache是否已经缓存了匹配的Listener,有的话马上返回Listener,如果没有则计算后放进缓存并返回Lisener,最终会调用这些Listener的onApplicationEvent方法发布事件。

EventPublishingRunListener#starting SimpleApplicationEventMulticaster AbstractApplicationEventMulticaster ApplicationListener multicastEvent(ApplicationStartingEvent) getApplicationListeners(event, type) Collection> 轮循onApplicationEvent(ApplicationStartingEvent) EventPublishingRunListener#starting SimpleApplicationEventMulticaster AbstractApplicationEventMulticaster ApplicationListener

最后,我们再看AbstractApplicationEventMulticaster与Listener关联的方法,为什么都会有this.retrieverCache.clear();这条命令。现在似乎比较好回答了,触发retrieverCache产生数据的时机点是EventPublishingRunListener发布消息,再结合随处可见的synchronized (this.retrievalMutex)命令,就可看出,Spring系统要确保发布消息时,当前的与事件类型和事件源类型相匹配的Listener都能够接收到事件,并且这些Listener并不是稳定地存在于AbstractApplicationEventMulticaster的defaultRetriever里。抽象地描述就是当线程A要发布事件X时,线程B在添加或者删除能够接收事件X的Listener时,通过锁机制,将并行的处理调整为串行的处理,从而确保程序和数据的安全。

至此,Springboot的事件、事件发布者、和事件监听者已经找到了,并对事件的发布进行了一定程度的探讨。那么,Springboot的事件机制与Spring的事件机制会不会有关联呢?在查看EventPublishingRunListener发布事件的方法时,会看到不是每个方法都以相同的逻辑来发布,这会有什么效果呢?EventPublishingRunListener的initialMulticaster属性以是什么?随后的文章将慢慢探讨。

你可能感兴趣的:(Java阵营,java)