Spring注解驱动开发第39讲——你不知道的ApplicationListener的原理
发布日期:2021-06-30 17:56:24 浏览次数:3 分类:技术文章

本文共 6990 字,大约阅读时间需要 23 分钟。

写在前面

我们不妨先回顾一下上一讲所学习的内容,在上一讲中,我们是编写了一个监听器,即ApplicationListener接口的一个实现类,通过这个监听器,我们可以来监听容器中的事件,只要容器中有事件发布,监听器中的方法就会得到回调。除此之外,我们还可以自己利用IOC容器的publishEvent方法来自定义发布一个事件,也就是说,我们自己也是能够发布一个事件的。

回顾完毕,接下来,咱们就来说说本讲所要阐述的内容,即事件的整个发布和事件监听机制的内部原理。

事件监听机制的源码分析

在研究分析事件的整个发布和事件监听机制的内部原理之前,我们先来运行一下如下单元测试类中的test01方法。

package com.meimeixia.test;import org.junit.Test;import org.springframework.context.ApplicationEvent;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import com.meimeixia.ext.ExtConfig;public class IOCTest_Ext {
@Test public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class); // 发布一个事件 applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
}); // 关闭容器 applicationContext.close(); }}

运行完毕,你会发现Eclipse控制台打印出了如下三个咱们收到的事件。

  • ContextRefreshedEvent事件
  • 我们自己发布的一个事件,即IOCTest_Ext$1[source=我发布的事件]
  • ContextClosedEvent事件

在这里插入图片描述

接下来,我们就要来分析一下以上这三个事件都是怎么收到的?

创建容器并且刷新

首先,我们在自己编写的监听器(例如MyApplicationListener)内的onApplicationEvent方法处打上一个断点,如下图所示。

在这里插入图片描述

然后,以debug的方式运行IOCTest_Ext测试类中的test01方法,如下图所示,程序现在停到了咱们自己编写的监听器的onApplicationEvent方法中。

在这里插入图片描述

很明显,现在我们看到的是收到的第一个事件,即ContextRefreshedEvent事件。那么问题来了,我们是怎么收到的该事件呢?我们不妨从IOCTest_Ext测试类中的test01方法开始,来梳理一遍整个流程。

鼠标单击Eclipse左上角方法调用栈中的IOCTest_Ext.test01() line:13,这时程序来到了IOCTest_Ext测试类的test01方法中,如下图所示。

在这里插入图片描述

可以看到第一步是要来创建IOC容器的。继续跟进代码,可以看到在创建容器的过程中,还会调用一个refresh方法来刷新容器,刷新容器其实就是创建容器里面的所有bean。

在这里插入图片描述

继续跟进代码,看这个refresh方法里面具体都做了些啥,如下图所示,可以看到它里面调用了如下一个finishRefresh方法,顾名思义,该方法就是来完成容器的刷新工作的。

在这里插入图片描述

对于这个refresh方法而言,想必你是再熟悉不过了,它里面做了很多的事情,也就是说,在容器刷新这一步中做了很多的事情,比如执行BeanFactoryPostProcessor组件的方法、给容器中注册后置处理器等等,这些之前我就已经详细讲解过了,故在这儿只是稍微提及一下,并不再过多赘述。

容器刷新完成,发布ContextRefreshedEvent事件

当容器刷新完成时,就会调用finishRefresh方法,那么该方法里面又做了哪些事呢?我们继续跟进代码,如下图所示,发现容器刷新完成时调用的finishRefresh方法里面又调用了一个叫publishEvent的方法,而且传递进该方法的参数是new出来的一个ContextRefreshedEvent对象。这一切都在说明着,容器在刷新完成以后,便会发布一个ContextRefreshedEvent事件

在这里插入图片描述

接下来,我们就来看看ContextRefreshedEvent事件的发布流程。

事件发布流程

当容器刷新完成时,就会来调用一个叫publishEvent的方法,而且会向该方法中传递一个ContextRefreshedEvent对象。这即是发布了一个事件,这个事件呢,正是我们第一个感知到的事件,即容器刷新完成事件。接下来,我们就来看看这个事件到底是怎么发布的。

继续跟进代码,可以看到程序来到了如下图所示的地方。

在这里插入图片描述

我们继续跟进代码,可以看到程序来到了如下图所示的这行代码处。

在这里插入图片描述

可以看到先是调用一个getApplicationEventMulticaster方法,从该方法的名字中就可以看出,它是来获取事件多播器的,不过也有人叫事件派发器。接下来,我们就可以说说ContextRefreshedEvent事件的发布流程了。

首先,调用getApplicationEventMulticaster方法来获取到事件多播器,或者,你叫事件派发器也行。所谓的事件多播器就是指我们要把一个事件发送给多个监听器,让它们同时感知。

然后,调用事件多播器的multicastEvent方法,这个方法就是用来向各个监听器派发事件的。那么,它到底是怎么来派发事件的呢?

继续跟进代码,来好好看看multicastEvent方法是怎么写的,如下图所示。

在这里插入图片描述

可以看到,一开始就有一个for循环,在这个for循环中,有一个getApplicationListeners方法,它是来拿到所有的ApplicationListener的,拿到之后就会来挨个遍历再来拿到每一个ApplicationListener。

很快,你会看到有一个if判断,它会判断getTaskExecutor方法能不能够返回一个Executor对象,如果能够,那么会利用Executor的异步执行功能来使用多线程的方式异步地派发事件;如果不能够,那么就使用同步的方式直接执行ApplicationListener的方法。

细心一点的同学,可以点进去Executor里面去看一看,你会发现它是一个接口,并且Spring提供了一个叫TaskExecutor的子接口来继承它。在该子接口下,Spring又提供了一个SyncTaskExecutor类来实现它,以及一个AsyncTaskExecutor接口来继承它,如下图所示。

在这里插入图片描述

不用我说,大家都应该知道,SyncTaskExecutor支持以同步的方式来执行某一任务,AsyncTaskExecutor支持以异步的方式来执行某一任务。也就是说,我们可以在自定义事件派发器的时候(这个后面就会讲到),给它传递这两种类型的TaskExecutor,让它支持以同步或者异步的方式来派发事件。

现在程序很显然是进入到了else判断语句中,也就是说,现在是使用同步的方式来直接执行ApplicationListener的方法的,相应地,这时是调用了一个叫invokeListener的方法,而且在该方法中传入了当前遍历出来的ApplicationListener。那么问题来了,这个方法的内部又做了哪些事呢?

我们继续跟进代码,可以看到程序来到了如下图所示的地方。这时,invokeListener方法里面调用了一个叫doInvokeListener的方法。

在这里插入图片描述

继续跟进代码,可以看到程序来到了如下图所示的这行代码处。看到这儿,你差不多应该知道了这样一个结论,即遍历拿到每一个ApplicationListener之后,会回调它的onApplicationEvent方法

在这里插入图片描述

继续跟进代码,这时,程序就会来到我们自己编写的监听器(例如MyApplicationListener)中,继而来回调它其中的onApplicationEvent方法。

在这里插入图片描述

以上就是ContextRefreshedEvent事件的发布流程。

写到这里,我来做一下总结,即总结一下一个事件怎么发布的。首先调用一个publishEvent方法,然后获取到事件多播器,接着为我们派发事件。你看,就是这么简单!

在本讲的最开始,从Eclipse控制台打印出的内容中,我们可以知道收到的第一个事件就是ContextRefreshedEvent事件。为了让大家能够更加清晰地看到这一点,我按下F6快捷键让程序继续往下运行,如下图所示,这时Eclipse控制台打印出了收到的第一个事件,即ContextRefreshedEvent事件。

在这里插入图片描述

自己发布的事件

按下F8快捷键让程序运行到下一个断点,如下图所示,这时是来到了我们自己编写的监听器(例如MyApplicationListener)里面的onApplicationEvent方法中。

在这里插入图片描述

这里,我们要明白一点,这儿是我们自己发布的事件,就是调用容器的publishEvent方法发布出去的事件,这可以从test01方法的如下这行代码处看出。

在这里插入图片描述

接下来,我们就要来看一下咱们自己发布的事件的发布流程了。

这里,我要说一嘴,其实,咱们自己发布的事件的发布流程与上面所讲述的ContextRefreshedEvent事件的发布流程是一模一样的,我为什么会这么说呢,这得看接下来的源码分析了。

继续跟进代码,可以看到程序来到了如下图所示的地方,这不是还是再调用publishEvent方法吗?

在这里插入图片描述

我们继续跟进代码,可以看到程序来到了如下图所示的这行代码处。

在这里插入图片描述

可以看到,还是先获取到事件多播器,然后再调用事件多播器的multicastEvent方法向各个监听器派发事件。

继续跟进代码,可以看到multicastEvent方法是像下面这样写的。

在这里插入图片描述

依然还是拿到所有的ApplicationListener,然后再遍历拿到每一个ApplicationListener,接着来挨个执行每一个ApplicationListener的方法。怎么来执行呢?如果是异步模式,那么就使用异步的方式来执行,否则便使用同步的方式直接执行。

继续跟进代码,可以看到程序来到了如下图所示的地方。这时,invokeListener方法里面调用了一个叫doInvokeListener的方法。

在这里插入图片描述

继续跟进代码,可以看到程序来到了如下图所示的这行代码处。依旧能看到,这是遍历拿到每一个ApplicationListener之后,再来回调它的onApplicationEvent方法。

在这里插入图片描述

以上就是咱们自己发布的事件的发布流程。

最后,我做一下小结,不管是容器发布的事件,还是咱们自己发布的事件,都会走以上这个事件发布流程,即先拿到事件多播器,然后再拿到所有的监听器,接着再挨个回调它的方法

容器关闭,发布ContextClosedEvent事件

接下来,可想而知,就应该是要轮到最后一个事件了,即容器关闭事件。我们按下F8快捷键让程序运行到下一个断点,如下图所示,可以看到Eclipse控制台打印出了收到的第二个事件,即我们自己发布的事件。

在这里插入图片描述

而且,从上图中也能看到,这时程序来到了我们自己编写的监听器(例如MyApplicationListener)里面的onApplicationEvent方法中。

下面,我们就来看看容器关闭事件的发布流程。首先,鼠标单击Eclipse左上角方法调用栈中的IOCTest_Ext.test01() line:20,这时程序来到了IOCTest_Ext测试类的test01方法中的最后一行代码处,如下图所示。

在这里插入图片描述

以上这行代码说的就是来关闭容器,那么容器是怎么关闭的呢?我们继续跟进代码,发现关闭容器的close方法里面又调用了一个doClose方法,如下图所示。

在这里插入图片描述

继续跟进代码,如下图所示,可以看到doClose方法里面又调用了一个publishEvent方法,而且传递进该方法的参数是new出来的一个ContextClosedEvent对象。这一切都在说明着,关闭容器,会发布一个ContextClosedEvent事件

在这里插入图片描述

当然不管怎么发布,ContextClosedEvent事件所遵循的发布流程和上面讲述的一模一样。

有关事件多播器,这些是你应该知道的

你知道事件多播器是怎么拿到的吗?

接下来,我们得来说另一点了。你是不是注意到了这一点,在上面我们讲述事件发布的流程时,会通过一个getApplicationEventMulticaster方法来获取事件多播器,我们不妨看一下该方法是怎么写的,如下图所示。

在这里插入图片描述

通过该方法可以获取到事件多播器,很显然,applicationEventMulticaster这么一个玩意代表的就是事件多播器。那么问题来了,我们是从哪获取到的事件多播器的呢?下面我就要为大家揭晓谜团了,给大家说一下它是怎么拿到的。

首先,创建IOC容器。我们知道,在创建容器的过程中,还会调用一个refresh方法来刷新容器,如下图所示。

在这里插入图片描述

然后,我们就要来看看这个refresh方法具体都做了哪些事。该方法我们已经很熟悉了,如下图所示,可以看到在该方法中会调非常多的方法,其中就有一个叫initApplicationEventMulticaster的方法,顾名思义,它就是来初始化ApplicationEventMulticaster的。而且,它还是在初始化创建其他组件之前调用的。

在这里插入图片描述

那么,初始化ApplicationEventMulticaster的逻辑又是怎样的呢?我们也可以来看一看,进入initApplicationEventMulticaster方法里面,如下图所示。

在这里插入图片描述

上述这个方法,你能看得懂吗?其实很简单,它就是先判断IOC容器(也就是BeanFactory)中是否有id等于applicationEventMulticaster的组件,这个我是咋知道的呢?因为APPLICATION_EVENT_MULTICASTER_BEAN_NAME这个常量就是字符串applicationEventMulticaster,如下图所示。

在这里插入图片描述

如果IOC容器中有id等于applicationEventMulticaster的组件,那么就会通过getBean方法直接拿到这个组件;如果没有,那么就重新new一个SimpleApplicationEventMulticaster类型的事件多播器,然后再把这个事件多播器注册到容器中,也就是说,这相当于我们自己给容器中注册了一个事件多播器,这样,以后我们就可以在其他组件要派发事件的时候,自动注入这个事件多播器就行了。其实说白了,在整个事件派发的过程中,我们可以自定义事件多播器

以上就是我们这个事件多播器它是怎么拿到的。

你知道容器是怎么将容器中的监听器注册到事件多播器中去的吗?

还记得我们在分析事件发布流程时,有一个叫getApplicationListeners的方法吗?

在这里插入图片描述

通过该方法就能知道容器中有哪些监听器。

那么问题来了,容器中到底有哪些监听器呢?其实,这个问题的答案很简单,因为我们把监听器早就已经添加到了容器中,所以,容器只需要判断一下哪些组件是监听器就行了。我为什么会这么说呢?这就得分析源码才能得出了。

首先,依旧还是创建IOC容器。我们也知道,在创建容器的过程中,还会调用一个refresh方法来刷新容器,如下图所示。

在这里插入图片描述

然后,我们就要来看看这个refresh方法具体都做了哪些事。该方法我们已经超熟悉了,如下图所示,可以看到在该方法中会调非常多的方法,其中就有一个叫registerListeners的方法,顾名思义,它就是来注册监听器的。

在这里插入图片描述

那到底是怎么来注册监听器的呢?我们可以点进去该方法里面看一看,如下图所示,可以看到它是先从容器中拿到所有的监听器,然后再把它们注册到applicationEventMulticaster当中。

在这里插入图片描述

当然了,第一次调用该方法时,getApplicationListeners方法是获取不到容器中所有的监听器的,因为这些监听器还没注册到容器中,不知道我这样理解的对不对,如果我要是理解的不对,那么还请读者指出,我再来修改,哈哈哈😁

所以,第一次调用该方法时,它会调用getBeanNamesForType方法从容器中拿到所有ApplicationListener类型的组件(即监听器),然后再把这些组件注册到事件派发器中。

在这里插入图片描述

这样,事件派发器里面就有这些监听器了,容器中到底有哪些监听器我们自然也就知道了。

接下来,就不用我说了吧!自然是事件派发器向各个监听器派发事件了。

转载地址:https://liayun.blog.csdn.net/article/details/113830783 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第40讲——你晓得@EventListener这个注解的原理吗?
下一篇:Spring注解驱动开发第38讲——你知道ApplicationListener的用法吗?

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月14日 03时12分08秒