Spring注解驱动开发第40讲——你晓得@EventListener这个注解的原理吗?
发布日期:2021-06-30 17:56:24 浏览次数:4 分类:技术文章

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

写在前面

在上一讲中,我们讲了一下事件监听机制的内部原理,当然了,在一过程中,我们也看到了事件的整个发布流程。再回顾一下的话,你会发现之前咱们编写的监听器都是来实现ApplicationListener这个接口的,其实,除此之外,还有另外一种方式。因此,这一讲,我们就来着重讲述这种方式。

这里我先提一下这种方式,即使用@EventListener注解,我们就可以让任意方法都能监听事件。这样的话,我们在一个普通的业务逻辑组件中,就可以直接来使用这个注解了,而不是让它去实现ApplicationListener这个接口。

@EventListener注解的用法

首先,编写一个普通的业务逻辑组件,例如UserService,并在该组件上标注一个@Service注解。

package com.meimeixia.ext;import org.springframework.stereotype.Service;@Servicepublic class UserService {
}

在该组件内,我们肯定会写一些很多的方法,但这里就略去了。那么问题来了,如果我们希望该组件能监听到事件,那么该怎么办呢?我们可以在该组件内写一个listen方法,以便让该方法来监听事件。这时,我们只需要简单地给该方法上标注一个@EventListener注解,就可以让它来监听事件了。那么,到底要监听哪些事件呢?我们可以通过@EventListener注解中的classes属性来指定,例如,我们可以让listen方法监听ApplicationEvent及其下面的子事件。

package com.meimeixia.ext;import org.springframework.context.ApplicationEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Service;@Servicepublic class UserService {
// 一些其他的方法... @EventListener(classes=ApplicationEvent.class) public void listen() {
System.out.println("UserService..."); } }

当然了,我们还可以通过@EventListener注解中的classes属性来指定监听多个事件。

package com.meimeixia.ext;import org.springframework.context.ApplicationEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Service;@Servicepublic class UserService {
// 一些其他的方法... // @EventListener(classes=ApplicationEvent.class) @EventListener(classes={
ApplicationEvent.class}) public void listen() {
System.out.println("UserService..."); } }

如果ApplicationEvent及其下面的子事件发生了,那么我们应该怎么办呢?想都不用想,肯定是拿到这个事件,因此我们就要在listen方法的参数位置上写一个ApplicationEvent参数来接收该事件。

package com.meimeixia.ext;import org.springframework.context.ApplicationEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Service;@Servicepublic class UserService {
// 一些其他的方法... // @EventListener(classes=ApplicationEvent.class) @EventListener(classes={
ApplicationEvent.class}) public void listen(ApplicationEvent event) {
System.out.println("UserService...监听到的事件:" + event); } }

以上就是我们自己编写的一个普通的业务逻辑组件,该组件就能监听事件,这跟实现ApplicationListener接口的效果是一模一样的。

然后,我们就要来进行测试了,就是运行一下以下IOCTest_Ext测试类中的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控制台打印出了如下内容,可以清晰地看到,不仅我们之前编写的监听器(例如MyApplicationListener)收到了事件,而且UserService组件也收到了事件。也就是说,每一个都能正确地收到事件。

在这里插入图片描述

这里我得说一嘴,以后咱们对@EventListener这个注解的使用会比较多,因为它使用起来非常方便。

接下来,我们就得说说这个注解背后的原理了。

@EventListener注解的原理

我们可以点进去@EventListener这个注解里面去看一看,如下图所示,可以看到这个注解上面有一大堆的描述,从描述中我们是否可以猜到这个注解的内部工作原理呢?答案是可以的。

在这里插入图片描述

描述中有一个醒目的字眼,即参考EventListenerMethodProcessor。意思可能是说,如果你想搞清楚@EventListener注解的内部工作原理,那么可以参考EventListenerMethodProcessor这个类。

EventListenerMethodProcessor是啥呢?它就是一个处理器,其作用是来解析方法上的@EventListener注解的。这也就是说,Spring会使用EventListenerMethodProcessor这个处理器来解析方法上的@EventListener注解。因此,接下来,我们就要将关注点放在这个处理器上,搞清楚这个处理器是怎样工作的。搞清楚了这个,自然地我们就搞清楚了@EventListener注解的内部工作原理。

我们点进去EventListenerMethodProcessor这个类里面去看一看,如下图所示,发现它实现了一个接口,叫SmartInitializingSingleton。这时,要想搞清楚EventListenerMethodProcessor这个处理器是怎样工作的,那就得先搞清楚SmartInitializingSingleton这个接口的原理了。

在这里插入图片描述

不妨点进去SmartInitializingSingleton这个接口里面去看一看,你会发现它里面定义了一个叫afterSingletonsInstantiated的方法,如下图所示。

在这里插入图片描述

接下来,我们就要搞清楚到底是什么时候开始触发执行afterSingletonsInstantiated方法的。

仔细看一下SmartInitializingSingleton接口中afterSingletonsInstantiated方法上面的描述信息,不难看出该方法是在所有的单实例bean已经全部被创建完了以后才会被执行。

其实,在介绍SmartInitializingSingleton接口的时候,我们也能从描述信息中知道,在所有的单实例bean已经全部被创建完成以后才会触发该接口。紧接着下面一段的描述还说了,该接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口。也就是说,这个时候容器已经创建完了。

好吧,回到主题,我们来看看afterSingletonsInstantiated方法的触发时机。首先,我们得在EventListenerMethodProcessor类里面的afterSingletonsInstantiated方法处打上一个断点,如下图所示。

在这里插入图片描述

然后,以debug的方式运行IOCTest_Ext测试类中的test01方法,这时程序停留在了EventListenerMethodProcessor类里面的afterSingletonsInstantiated方法中,如下图所示。

在这里插入图片描述

此时,你是不是很想知道是什么时候开始触发执行afterSingletonsInstantiated这个方法的呢?我们不妨从IOCTest_Ext测试类中的test01方法开始,来梳理一遍整个流程。

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

对于以上这个方法,我相信大家都不会陌生,因为我们之前就看过好多遍了,它其实就是来初始化所有剩下的那些单实例bean的。也就是说,如果还有一些单实例bean还没被初始化,即还没创建对象,那么便会在这一步进行(初始化)。

继续跟进代码,如下图所示,可以看到在finishBeanFactoryInitialization方法里面执行了如下一行代码,依旧还是来初始化所有剩下的单实例bean。

在这里插入图片描述

继续跟进代码,如下图所示,可以看到现在程序停留在了如下这行代码处。

在这里插入图片描述

这不就是我们要讲的afterSingletonsInstantiated方法吗?它原来是在这儿调用的啊!接下来,咱们就得好好看看在调用该方法之前,具体都做了哪些事。

由于afterSingletonsInstantiated方法位于DefaultListableBeanFactory类的preInstantiateSingletons方法里面,所以我们就得来仔细看看preInstantiateSingletons方法里面具体都做了些啥了。

进入眼帘的首先是一个for循环,在该for循环里面,beanNames里面存储的都是即将要创建的所有bean的名字,紧接着会做一个判断,即判断bean是不是抽象的,是不是单实例的,等等等等。最后,不管怎样,都会调用getBean方法来创建对象。

在这里插入图片描述

总结一下就是,先利用一个for循环拿到所有我们要创建的单实例bean,然后挨个调用getBean方法来创建对象。也即,创建所有的单实例bean。

再来往下翻阅preInstantiateSingletons方法,发现它下面还有一个for循环,在该for循环里面,beanNames里面依旧存储的是即将要创建的所有bean的名字。那么,在该for循环中所做的事情又是什么呢?很显然,在最上面的那个for循环中,所有的单实例bean都已经全部创建完了。因此,在下面这个for循环中,咱们所要做的事就是获取所有创建好的单实例bean,然后判断每一个bean对象是否是SmartInitializingSingleton这个接口类型的,如果是,那么便调用它里面的afterSingletonsInstantiated方法,而该方法就是SmartInitializingSingleton接口中定义的方法。

在这里插入图片描述

至此,你该搞清楚afterSingletonsInstantiated方法是什么时候开始触发执行了吧😁!就是在所有单实例bean全部创建完成以后

最后,我还得说一嘴。如果所有的单实例bean都已经创建完了,也就是说下面这一步都执行完了,那么说明IOC容器已经创建完成了。

在这里插入图片描述

那么,紧接着便会来调用finishRefresh方法,容器已经创建完了,此时就会来发布容器已经刷新完成的事件。这就呼应了开头的那句话,即SmartInitializingSingleton接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口。

以上就是@EventListener注解的内部工作原理,在讲解该原理时,我们顺道说了一下SmartInitializingSingleton接口的原理。

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

上一篇:Spring注解驱动开发第41讲——Spring IOC容器创建源码解析(一)之BeanFactory的创建以及预准备工作
下一篇:Spring注解驱动开发第39讲——你不知道的ApplicationListener的原理

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年05月03日 17时16分37秒