在上一讲中,我们讲了一下事件监听机制的内部原理,当然了,在一过程中,我们也看到了事件的整个发布流程。再回顾一下的话,你会发现之前咱们编写的监听器都是来实现ApplicationListener这个接口的,其实,除此之外,还有另外一种方式。因此,这一讲,我们就来着重讲述这种方式。
这里我先提一下这种方式,即使用@EventListener注解,我们就可以让任意方法都能监听事件。这样的话,我们在一个普通的业务逻辑组件中,就可以直接来使用这个注解了,而不是让它去实现ApplicationListener这个接口。
@EventListener注解的用法
首先,编写一个普通的业务逻辑组件,例如UserService,并在该组件上标注一个@Service注解。
package com.meimeixia.ext;
import org.springframework.stereotype.Service;
@Service
public 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;
@Service
public 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;
@Service
public 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;
@Service
public 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
EventListenerMethodProcessor是啥呢?它就是一个处理器,其作用是来解析方法上的@EventListener注解的。这也就是说,Spring会使用EventListenerMethodProcessor这个处理器来解析方法上的@EventListener注解。因此,接下来,我们就要将关注点放在这个处理器上,搞清楚这个处理器是怎样工作的。搞清楚了这个,自然地我们就搞清楚了@EventListener注解的内部工作原理。
我们点进去EventListenerMethodProcessor这个类里面去看一看,如下图所示,发现它实现了一个接口,叫SmartInitializingSingleton。这时,要想搞清楚EventListenerMethodProcessor这个处理器是怎样工作的,那就得先搞清楚SmartInitializingSingleton这个接口的原理了。
SmartInitializingSingleton
不妨点进去SmartInitializingSingleton这个接口里面去看一看,你会发现它里面定义了一个叫afterSingletonsInstantiated的方法,如下图所示。
afterSingletonsInstantiated
接下来,我们就要搞清楚到底是什么时候开始触发执行afterSingletonsInstantiated方法的。
源码追踪
仔细看一下SmartInitializingSingleton接口中afterSingletonsInstantiated方法上面的描述信息,不难看出该方法是在所有的单实例bean已经全部被创建完了以后才会被执行。
其实,在介绍SmartInitializingSingleton接口的时候,我们也能从描述信息中知道,在所有的单实例bean已经全部被创建完成以后才会触发该接口。紧接着下面一段的描述还说了,该接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口。也就是说,这个时候容器已经创建完了。
可以看到第一步是要来创建IOC容器的。继续跟进代码,可以看到在创建容器的过程中,还会调用一个refresh方法来刷新容器,刷新容器其实就是创建容器里面的所有bean。
finishBeanFactoryInitialization
继续跟进代码,看这个refresh方法里面具体都做了些啥,如下图所示,可以看到它里面调用了如下一个finishBeanFactoryInitialization方法,顾名思义,该方法就是来完成BeanFactory的初始化工作的。
对于以上这个方法,我相信大家都不会陌生,因为我们之前就看过好多遍了,它其实就是来初始化所有剩下的那些单实例bean的。也就是说,如果还有一些单实例bean还没被初始化,即还没创建对象,那么便会在这一步进行(初始化)。继续跟进代码,如下图所示,可以看到在finishBeanFactoryInitialization方法里面执行了如下一行代码,依旧还是来初始化所有剩下的单实例bean。
preInstantiateSingletons
继续跟进代码,如下图所示,可以看到现在程序停留在了如下这行代码处。
这不就是我们要讲的afterSingletonsInstantiated方法吗?它原来是在这儿调用的啊!接下来,咱们就得好好看看在调用该方法之前,具体都做了哪些事。
由于afterSingletonsInstantiated方法调用位于DefaultListableBeanFactory类的preInstantiateSingletons方法里面,所以我们就得来仔细看看preInstantiateSingletons方法里面具体都做了些啥了。
进入眼帘的首先是一个for循环,在该for循环里面,beanNames里面存储的都是即将要创建的所有bean的名字,紧接着会做一个判断,即判断bean是不是抽象的,是不是单实例的,等等等等。最后,不管怎样,都会调用getBean方法来创建对象。
总结一下就是,先利用一个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接口中定义的方法。
afterSingletonsInstantiated
至此,你该搞清楚afterSingletonsInstantiated方法是什么时候开始触发执行了吧😁!就是在所有单实例bean全部创建完成以后。
finishBeanFactoryInitialization()方法执行结束
最后,我还得说一嘴。如果所有的单实例bean都已经创建完了,也就是说下面这一步都执行完了,那么说明IOC容器已经创建完成了。
那么,紧接着便会来调用finishRefresh方法,容器已经创建完了,此时就会来发布容器已经刷新完成的事件。
这就呼应了开头的那句话,即SmartInitializingSingleton接口的调用时机有点类似于ContextRefreshedEvent事件,即在容器刷新完成以后,便会回调该接口。
以上就是@EventListener注解的内部工作原理,在讲解该原理时,我们顺道说了一下SmartInitializingSingleton接口的原理。