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

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

@EventListener注解的用法

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

  1. package com.meimeixia.ext;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class UserService {
  5. }

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

  1. package com.meimeixia.ext;
  2. import org.springframework.context.ApplicationEvent;
  3. import org.springframework.context.event.EventListener;
  4. import org.springframework.stereotype.Service;
  5. @Service
  6. public class UserService {
  7. // 一些其他的方法...
  8. @EventListener(classes=ApplicationEvent.class)
  9. public void listen() {
  10. System.out.println("UserService...");
  11. }
  12. }

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

  1. package com.meimeixia.ext;
  2. import org.springframework.context.ApplicationEvent;
  3. import org.springframework.context.event.EventListener;
  4. import org.springframework.stereotype.Service;
  5. @Service
  6. public class UserService {
  7. // 一些其他的方法...
  8. // @EventListener(classes=ApplicationEvent.class)
  9. @EventListener(classes={ApplicationEvent.class})
  10. public void listen() {
  11. System.out.println("UserService...");
  12. }
  13. }

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

  1. package com.meimeixia.ext;
  2. import org.springframework.context.ApplicationEvent;
  3. import org.springframework.context.event.EventListener;
  4. import org.springframework.stereotype.Service;
  5. @Service
  6. public class UserService {
  7. // 一些其他的方法...
  8. // @EventListener(classes=ApplicationEvent.class)
  9. @EventListener(classes={ApplicationEvent.class})
  10. public void listen(ApplicationEvent event) {
  11. System.out.println("UserService...监听到的事件:" + event);
  12. }
  13. }

以上就是我们自己编写的一个普通的业务逻辑组件,该组件就能监听事件,这跟实现ApplicationListener接口的效果是一模一样的。
然后,我们就要来进行测试了,就是运行一下以下IOCTest_Ext测试类中的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控制台打印出了如下内容,可以清晰地看到,不仅我们之前编写的监听器(例如MyApplicationListener)收到了事件,而且UserService组件也收到了事件。也就是说,每一个都能正确地收到事件。
image.png
这里我得说一嘴,以后咱们对@EventListener这个注解的使用会比较多,因为它使用起来非常方便。
接下来,我们就得说说这个注解背后的原理了。

@EventListener注解的原理

我们可以点进去@EventListener这个注解里面去看一看,如下图所示,可以看到这个注解上面有一大堆的描述,从描述中我们是否可以猜到这个注解的内部工作原理呢?答案是可以的。
image.png
描述中有一个醒目的字眼,即参考EventListenerMethodProcessor。意思可能是说,如果你想搞清楚@EventListener注解的内部工作原理,那么可以参考EventListenerMethodProcessor这个类。

EventListenerMethodProcessor

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

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

SmartInitializingSingleton

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

afterSingletonsInstantiated

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

源码追踪

仔细看一下SmartInitializingSingleton接口中afterSingletonsInstantiated方法上面的描述信息,不难看出该方法是在所有的单实例bean已经全部被创建完了以后才会被执行。
其实,在介绍SmartInitializingSingleton接口的时候,我们也能从描述信息中知道,在所有的单实例bean已经全部被创建完成以后才会触发该接口。紧接着下面一段的描述还说了,该接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口。也就是说,这个时候容器已经创建完了。
image.png
可以看到第一步是要来创建IOC容器的。继续跟进代码,可以看到在创建容器的过程中,还会调用一个refresh方法来刷新容器,刷新容器其实就是创建容器里面的所有bean。
image.png

finishBeanFactoryInitialization

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

image.png
对于以上这个方法,我相信大家都不会陌生,因为我们之前就看过好多遍了,它其实就是来初始化所有剩下的那些单实例bean的。也就是说,如果还有一些单实例bean还没被初始化,即还没创建对象,那么便会在这一步进行(初始化)。继续跟进代码,如下图所示,可以看到在finishBeanFactoryInitialization方法里面执行了如下一行代码,依旧还是来初始化所有剩下的单实例bean。

image.pngpreInstantiateSingletons

继续跟进代码,如下图所示,可以看到现在程序停留在了如下这行代码处。
image.png
这不就是我们要讲的afterSingletonsInstantiated方法吗?它原来是在这儿调用的啊!接下来,咱们就得好好看看在调用该方法之前,具体都做了哪些事。

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

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

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

// Trigger post-initialization callback for all applicable beans…

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

afterSingletonsInstantiated

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

finishBeanFactoryInitialization()方法执行结束

最后,我还得说一嘴。如果所有的单实例bean都已经创建完了,也就是说下面这一步都执行完了,那么说明IOC容器已经创建完成了。
image.png
那么,紧接着便会来调用finishRefresh方法,容器已经创建完了,此时就会来发布容器已经刷新完成的事件。
这就呼应了开头的那句话,即SmartInitializingSingleton接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口

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