SpringBoot事件与监听机制

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

SpringBoot事件与监听机制

发现SpringBoot事件

通常我们启动应用就使用这么一条命令SpringApplication.run(XXXX.class,args);然后我们的项目就启动了。我们跟踪进去,就会来到下图的run方法。
在这里插入图片描述
从图中代码可知:

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

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

SpringApplicationRunListeners的构造

在这里插入图片描述
从代码我们可以知道,它构造了SpringApplicationRunListeners对象。调用该对象的构造函数时传进了两个参数。我们先看SpringApplicationRunListeners构造函数的内容:
在这里插入图片描述
其实就是传入了日志器以及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的事件都是扩展于spring的ApplicationEvent。

监听者

现在已经知道springboot有什么事件和事件发布者是谁了,那么监听者又是从何而来?
我们暂时回到较早前的代码片段:
在这里插入图片描述
前文描述的事件发布平台、事件发布者都是在构造了SpringApplication对象后调用它的run方法里出现的。而监听者是在构造SpringApplication对象的过程中出现的。
在这里插入图片描述那么发布事件发布者与监听者是怎么开成关联呢?其实就在构造EventPublishingRunListener的时候就产生关联关系了:
在这里插入图片描述但监听者不是直接成为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各方法的代码片段:
在这里插入图片描述在这里插入图片描述在这里插入图片描述其中不难发现,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的角度来探讨它的作用,在此我们先对他有个简单了解。

在这里插入图片描述

通过UML可知道,SimpleApplicationEventMulticaster继承于AbstractApplicationEventMulticaster,AbstractApplicationEventMulticaster实现了ApplicationEventMulticaster接口和BeanClassLoaderAware, BeanFactoryAware两个Spring机制内的接口,以及定义两个内部类。ApplicationEventMulticaster接口声明了关联Listener的方法(add, remove 开头的方法),以及发布事件的方法(multicastEvent)。通知阅读代码,其实与Listener产生关联的方法都在AbstractApplicationEventMulticaster实现,SimpleApplicationEventMulticaster主要实现了发布事件的方法。而AbstractApplicationEventMulticaster并不是简单地声明一个Listeners集合实现Listener的关联,它是与内部类一起管理着Listener,下面我们一起看看它内部是怎么样的。
在这里插入图片描述在这里插入图片描述
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<ListenerCacheKey, ListenerRetriever>类型的retrieverCache。因为ListenerRetriever与Listner关联,所以不难想象,ListenerCacheKey起到对Listner分组的作用。
在这里插入图片描述
从代码来看,就是通过事件类型以及事件源类型来作为Listener分组的key。那么,retrieverCache的数据来自哪里呢?这跟另一个属性defaultRetriever有关。
前文提到,AbstractApplicationEventMulticaster实现了ApplicationEventMulticaster接口的与Listener关联的方法,如下图:
在这里插入图片描述在这里插入图片描述
奇怪的是,添加Listener时仅一个劲地往defaultRetriever的applicationListeners集合属性或者applicationListenerBeans集合属性添加数据,并没有往retrieverCache添加数据,而且还清空retrieverCache的内容;删除Listener时逻辑也相似。这是什么意思呢?什么时候才会为retrieverCache添加数据呢?
在protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType)方法里可以找到添加数据的代码:
在这里插入图片描述
value部分的retriever是如何与Listener产生关联呢?其实是由上一行代码retrieveApplicationListeners(eventType, sourceType, retriever)准备好的。我们继续看这函数的代码:
在这里插入图片描述也就是说,retriever所关联的Listener来自于AbstractApplicationEventMulticaster的defaultRetriever集合属性,而且会经过事件类型和事件源类型的过滤(supportsEvent函数)。此时,我们回头思考getApplicationListeners函数的作用,就是从defaultRetriever集合属性关联的全量的Listener里抽出一部分,抽取的依据就是函数入参的事件类型以及事件源类型,并将抽出来的这部分Listener形成一组,以函数入参的事件类型以及事件源类型为key,存入retrieverCache属性里,并且返回符合事件类型以及事件源类型的Listener。虽然完整的getApplicationListeners函数并不仅这样,其他的逻辑分支是不存入retrieverCache属性里,但它就是唯一给retrieverCache添加数据的地方。retrieverCache的作用就是下次能够快速返回符合事件类型以及事件源类型的Listener。
而getApplicationListeners函数的调用者是SimpleApplicationEventMulticaster的multicastEvent方法。该方法就是前文提到的EventPublishingRunListener在发布事件时的调用的,以ApplicationStartingEvent事件为例:
在这里插入图片描述
现在终于将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<ApplicationListener<?>> 轮循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属性以是什么?随后的文章将慢慢探讨。

猜你喜欢

转载自blog.csdn.net/yyb_gz/article/details/107704641