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

  1. package com.meimeixia.test;
  2. import org.junit.Test;
  3. import org.springframework.context.ApplicationEvent;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import com.meimeixia.ext.ExtConfig;
  6. public class IOCTest_Ext {
  7. @Test
  8. public void test01() {
  9. AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ExtConfig.class);
  10. // 发布一个事件
  11. applicationContext.publishEvent(new ApplicationEvent(new String("我发布的事件")) {
  12. });
  13. // 关闭容器
  14. applicationContext.close();
  15. }
  16. }

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

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

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

创建容器并且刷新

首先,我们在自己编写的监听器(例如MyApplicationListener)内的onApplicationEvent方法处打上一个断点,如下图所示。
image.png
然后,以debug的方式运行IOCTest_Ext测试类中的test01方法,如下图所示,程序现在停到了咱们自己编写的监听器的onApplicationEvent方法中。
image.png
很明显,现在我们看到的是收到的第一个事件,即ContextRefreshedEvent事件。那么问题来了,我们是怎么收到的该事件呢?我们不妨从IOCTest_Ext测试类中的test01方法开始,来梳理一遍整个流程。

鼠标单击Eclipse左上角方法调用栈中的IOCTest_Ext.test01() line:13,这时程序来到了IOCTest_Ext测试类的test01方法中,如下图所示。
image.png
可以看到第一步是要来创建IOC容器的。继续跟进代码,可以看到在创建容器的过程中,还会调用一个refresh方法来刷新容器,刷新容器其实就是创建容器里面的所有bean。
image.png
继续跟进代码,看这个refresh方法里面具体都做了些啥,如下图所示,可以看到它里面调用了如下一个finishRefresh方法,顾名思义,该方法就是来完成容器的刷新工作的。
image.png

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

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

publishEvent发布事件

image.png
接下来,我们就来看看事件的发布流程。继续跟进代码,可以看到程序来到了如下图所示的地方。
image.png

getApplicationEventMulticaster

我们继续跟进代码,可以看到程序来到了如下图所示的这行代码处。
image.png
可以看到先是调用一个getApplicationEventMulticaster方法,从该方法的名字中就可以看出,它是来获取事件多播器的,不过也有人叫事件派发器。接下来,我们就可以说说ContextRefreshedEvent事件的发布流程了。

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

multicastEvent

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

继续跟进代码,来好好看看multicastEvent方法是怎么写的,如下图所示。
image.png
可以看到,一开始就有一个for循环,在这个for循环中,有一个getApplicationListeners方法,它是来拿到所有的ApplicationListener的,拿到之后就会来挨个遍历再来拿到每一个ApplicationListener。

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

细心一点的同学,可以点进去Executor里面去看一看,你会发现它是一个接口,并且Spring提供了一个叫TaskExecutor的子接口来继承它。在该子接口下,Spring又提供了一个SyncTaskExecutor类来实现它,以及一个AsyncTaskExecutor接口来继承它,如下图所示。
image.png
不用我说,大家都应该知道,SyncTaskExecutor支持以同步的方式来执行某一任务,AsyncTaskExecutor支持以异步的方式来执行某一任务。
也就是说,我们可以在自定义事件派发器的时候(这个后面就会讲到),给它传递这两种类型的TaskExecutor,让它支持以同步或者异步的方式来派发事件。

invokeListener

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

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

回调方法onApplicationEvent

继续跟进代码,可以看到程序来到了如下图所示的这行代码处。看到这儿,你差不多应该知道了这样一个结论,即遍历拿到每一个ApplicationListener之后,会回调它的onApplicationEvent方法
image.png
继续跟进代码,这时,程序就会来到我们自己编写的监听器(例如MyApplicationListener)中,继而来回调它其中的onApplicationEvent方法。
image.png
以上就是ContextRefreshedEvent事件的发布流程。

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

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

容器刷新完成,发布我们自定义的事件

按下F8快捷键让程序运行到下一个断点,如下图所示,这时是来到了我们自己编写的监听器(例如MyApplicationListener)里面的onApplicationEvent方法中。
image.png
这里,我们要明白一点,这儿是我们自己发布的事件,就是调用容器的publishEvent方法发布出去的事件,这可以从test01方法的如下这行代码处看出。
image.png
接下来,我们就要来看一下咱们自己发布的事件的发布流程了。

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

继续跟进代码,可以看到程序来到了如下图所示的地方,这不是还是再调用publishEvent方法吗?
image.png
我们继续跟进代码,可以看到程序来到了如下图所示的这行代码处。
image.png
可以看到,还是先获取到事件多播器,然后再调用事件多播器的multicastEvent方法向各个监听器派发事件。
继续跟进代码,可以看到multicastEvent方法是像下面这样写的。
image.png
依然还是拿到所有的ApplicationListener,然后再遍历拿到每一个ApplicationListener,接着来挨个执行每一个ApplicationListener的方法。怎么来执行呢?
如果是异步模式,那么就使用异步的方式来执行,否则便使用同步的方式直接执行。

继续跟进代码,可以看到程序来到了如下图所示的地方。这时,invokeListener方法里面调用了一个叫doInvokeListener的方法。
image.png
继续跟进代码,可以看到程序来到了如下图所示的这行代码处。依旧能看到,这是遍历拿到每一个ApplicationListener之后,再来回调它的onApplicationEvent方法。
image.png
以上就是咱们自己发布的事件的发布流程。
最后,我做一下小结,不管是容器发布的事件,还是咱们自己发布的事件,都会走以上这个事件发布流程,即先拿到事件多播器,然后再拿到所有的监听器,接着再挨个回调它的方法

容器最后关闭,发布ContextClosedEvent事件

接下来,可想而知,就应该是要轮到最后一个事件了,即容器关闭事件。
我们按下F8快捷键让程序运行到下一个断点,如下图所示,可以看到Eclipse控制台打印出了收到的第二个事件,即我们自己发布的事件。
image.png
而且,从上图中也能看到,这时程序来到了我们自己编写的监听器(例如MyApplicationListener)里面的onApplicationEvent方法中。

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

image.png
以上这行代码说的就是来关闭容器,那么容器是怎么关闭的呢?我们继续跟进代码,发现关闭容器的close方法里面又调用了一个doClose方法,如下图所示。
image.png
继续跟进代码,如下图所示,可以看到doClose方法里面又调用了一个publishEvent方法,而且传递进该方法的参数是new出来的一个ContextClosedEvent对象。这一切都在说明着,关闭容器,会发布一个ContextClosedEvent事件
当然不管怎么发布,ContextClosedEvent事件所遵循的发布流程和上面讲述的一模一样。