一、事件监听的基本原理
事件监听机制和发布订阅机制是很相似的:发布了一个事件后,监听该类型事件的所有监听器会触发相应的处理逻辑。正如Spring官方文档上所说的,整个上就是一个观察者的模式。那么,我们不妨猜测下,Spring是如何来实现事件发布监听的:盲猜底层使用一个集合存储了所有的监听器,当发布了一个事件后,遍历事件监听器集合,然后过滤符合事件类型的监听器,最后触发相应的事件处理逻辑。
二、Spring中事件监听的实现
1.Spring中事件监听的相关规范
在Spring中,事件监听机制主要涉及到了一下几个关键的规范(抽象类及接口):ApplicationEvent、ApplicationListener、ApplicationEventPublisher,我们来分析一下这几个规范吧:
- ApplicationEvent: 同样,所以说Spring的事件是符合jdk的规范的,这个抽象类继承了jdk内置的事件规范类EventObject(即jdk建议所有的事件都继承EventObject这个类)。ApplicationEvent是Spring家的事件规范。所以我们在自定义事件的时候,可以继承与ApplicationEvent,比如,Spring家自己关于容器上下文事件就又定义了一个容器上下文的时间规范ApplicationContextEvent,它同样是继承于ApplicationEvent的,只不过扩充了获取发出事件容器的方法;今后,我们同样可以在继承于ApplicationEvent的基础上定义自己的事件规范。
- ApplicationListener:这是一个函数式接口,同样时事件监听器的规范,当监听到自己监听的事件类型时就会调用onApplicationEvent方法来执行监听逻辑
- ApplicationEventPublisher:这同样是一个函数式接口,定义了事件发布的规范,任何的事件发布器ApplicationEventPublisher都是通过调用publishEvent来进行事件的发布
在了解了以上的三个规范后,我们再来看一看上面三个规范在Spring底层的一些详细细节点:
- 在Spring的底层事件可以大致分为两种:一种是ApplicationEvent,另一种是PayloadApplicationEvent,虽然PayloadApplicationEvent也是继承于ApplicationEvent,但是我们得把它单独拿出来,因为PayloadApplicationEvent其实就是我们发布一个Object类型的事件后Spring底层会帮我们把其封装成PayloadApplicationEvent,其中Object的事件信息被封装到PayloadApplicationEvent中的payload属性中。
- 这里重点说一下ApplicationListener在Spring不同版本中的演进:
1. Spring Framework 3.0 之前 的ApplicationListener是不支持泛型的,所以每个监听器都会收到所有的事件,如果需要过滤不同类型的事件的话,则需要借助instanceof来进行筛选
2. Spring Framework 3.0 开始,Application支持ApplicationEvent泛型监听,监听具体的事件,无需借助instanceof进行筛选
3. 但是引入泛型会引发另一个问题,如果我想同时监听多个类型的事件怎么办呢?如果继续使用ApplicationListener就又回到了Spring Framework 3.0之前了,为此,Spring Framework 3.0引入了SmartApplicationListener接口;该接口通过supports* 方法过滤需要监听的ApplicationEvent类型和事件源类型,从而达到监听不同类型的ApplicationEvent的目的 - 关于ApplicationEventPublisher我们提一个关键点,那就是SpringIOC容器的顶级接口ApplicationContext是实现了ApplicationEventPublisher接口的,那就意味着Spring容器就是一个事件发布器,并且在AbstractApplicationContext中,对时间发布器中的publishEvent方法进行了逻辑重写,Spring的事件发布逻辑正是在此处。所以,我们拿到Spring的容器,我们就能发布事件了
2.Spring中事件监听的流程
在进行探索整个事件发布的流程之前,我们先编写一个事件发送与监听的代码:
自定义监听器代码:
// 该监听器监听String类型的事件
@Component
public class MyInterfaceListener implements ApplicationListener<PayloadApplicationEvent<String>> {
@Override
public void onApplicationEvent(PayloadApplicationEvent<String> event) {
System.out.println("我就监听到了:" + event.getSource().getClass().getSimpleName() + "发送的事件,事件的内容为:" + event.getPayload());
}
}
发送端的代码:
@Autowired
private ApplicationContext applicationContext;
@GetMapping("/publish")
public void publish() {
applicationContext.publishEvent("我是一个事件");
}
当我请求publish的请求时,控制台会打印如下
我就监听到了:AnnotationConfigServletWebServerApplicationContext发送的事件,事件的内容为:我是一个事件
案例跑完后,我们来看一下publishEvent中整个事件发布的流程,首先我们进入到AbstractApplicationContext中来查看Spring底层对publishEvent的重写逻辑,代码如下:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
在这里我们就看到了,Spring底层对ApplicationEvent事件和PayloadApplicationEvent事件的处理:如果发布的直接就是ApplicationEvent类型的事件,那么就直接转换成ApplicationEvent类型,而如果不是ApplicationEvent类型的事件,那么就是我们所说的Object类型的事件,就会帮我们封装成PayloadApplicationEvent,并将Object类型的事件信息存储到payload属性中。在处理完事件的类型后,执行了下面这行重要的代码:
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
其中getApplicationEventMulticaster方法是拿到容器中的事件广播器,然后通过这个事件广播器来进行事件的广播。那么这个事件多播器我们也没有配置创建,是怎么获取的呢?这就需要看一下Spring容器刷新的9大步当中的initApplicationEventMulticaster初始化事件广播器的方法了,如下:
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
一看代码,还是Spring的老技巧,有了就用你的,没有我就帮你做一个然后用我的:首先,Spring会查看容器中有没有名称为applicationEventMulticaster的bean对象,如果有的话,就返回这个对象;如果没有的话,Spring就会帮我们new一个SimpleApplicationEventMulticaster的事件广播器对象返回。所以,我们默认就是走的SimpleApplicationEventMulticaster中的multicastEvent事件广播逻辑,代码如下:
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
看到这里也就验证了我们刚开始的猜测,Spring正式通过拿到容器中所有符合当前事件的监听器,然后循环遍历挨个调用onApplicationEvent方法。
三、@EventListener的实现
除了实现ApplicationListener接口的方式来实现监听器,在走向注解驱动开发后,Spring同样也为我们提供了@EventListener这个注解来实现监听器,注解使用起来还是十分的方便的:只需要在Spring托管Bean的public方法上加上@EventListener注解就可以监听事件了,想监听的事件类型也可以通过注解的属性来进行配置,例如像这样:就只会监听容器刷新的ContextRefreshedEvent事件。
/**
* 监听ioc 容器刷新完毕事件
* @param event
*/
@EventListener({ContextRefreshedEvent.class})
public void listen(ContextRefreshedEvent event) {
//
System.out.println(String.format(message,"ContextRefreshedEvent",event.getSource()));
}
@EventListener是为了支持注解开发,在Spring Framework 4.2的时候才新增的。我们先不看Spring底层是如何实现的,如果说现在让我们来开发一个注解来实现事件监听的功能,那你会怎么做呢,如果开始我们和Spring家想的一样:只需要在Spring托管Bean的public方法上加上@EventListener注解就可以监听事件了,而我们又知道Spring的底层是循环遍历支持该事件的监听器集合来进行广播事件的,那么现在为了让我们这个注解也能达到监听事件的效果,即也能被广播到,那么我觉得我们可以想到两种方式来实现:
- 新建一个集合,将所有标有@EventListener方法的bean都放到这个集合里面,当进行事件广播的时候,同时也循环遍历这个集合,来触发可以监听这个事件的监听器
- 使用适配器模式,将标有@EventListener注解的方法适配成ApplicationListener(其实,全类名,以及方法名,方法参数都有了,就可以通过反射来执行到适配之前的方法了),然后一起放到Spring储存事件监听器的集合中去
当然,你们可能会想到更好的方法,希望评论区一起讨论啊。
那么,Spring的底层是采用什么的方式来实现@EventListener能够进行监听的呢?其实,Spring家就是使用第二种适配器模式来完成的,既然我们知道了Spring是使用这种方式实现的,那么我们继续再想想需要在Spring Framework 3.0版本的基础上增加哪些组件呢:
- 我们需要找到所有标有@EventListener方法的bean,其实,就这一问题的解决方案有很多,主要看是在每个bean实例化的过程中就行判断还是说等所有的bean都实例化完了,在最后一起就行判断,而Spring的底层是选择了后者,通过新增一个实现了SmartInitializingSingleton接口的EventListenerMethodProcessor,我们知道在容器刷新快结束的时候,也就是所有的bean都实例化完成之后,Spring会挨个调用SmartInitializingSingleton的afterSingletonsInstantiated方法,Spring也正是在此进行处理标有@EventListener的bean的
- 另外,就是得提供一个适配器,来完成@EventListener注解到ApplicationListener的适配,所以,在Spring Framework 4.2的版本中,引入了ApplicationListenerMethodAdapter这个适配类
- 为了方便构造出ApplicationListenerMethodAdapter这个适配器对象,Spring还新增了一个EventListener工厂类DefaultEventListenerFactory来帮助我们构建ApplicationListenerMethodAdapter对象,或许这就是优秀代码人的代码能力体现吧,是我的话,可能就直接自己new了
上面已经说明了新增了哪些组件了,以及需要新增的原因,下面我们看看EventListenerMethodProcessor以及DefaultEventListenerFactory这两个组件是什么时候注入到容器中的,在容器构造函数中,创建AnnotatedBeanDefinitionReader对象的时候调用registerAnnotationConfigProcessors方法进行了注入,代码如下:
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
// ......此处.省略了其他无关代码
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
return beanDefs;
}
看着两个if写在最后,我们就猜得到是后期为了支持注解@EventListener而后加上去的,只要我们自己没有注入名字为org.springframework.context.event.internalEventListenerProcessor以及名字为org.springframework.context.event.internalEventListenerFactory的bean定义信息的时候,Spring就会帮我们注入EventListenerMethodProcessor以及DefaultEventListenerFactory这两个类型的bean定义信息,从而在后期帮忙实现@EventListener监听的功能,下面就来大致看看具体是如何实现的吧:
首先来看看EventListenerMethodProcessor的afterSingletonsInstantiated方法:
```java
public void afterSingletonsInstantiated() {
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
// 拿到容器中所有对象的名字
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
//...此处省略了不必要的代码
try {
// 遍历名字进行处理
processBean(beanName, type);
}
catch (Throwable ex) {
throw new BeanInitializationException("Failed to process @EventListener " +
"annotation on bean with name '" + beanName + "'", ex);
}
}
}
```
processBean的代码逻辑
private void processBean(final String beanName, final Class<?> targetType) {
if (!this.nonAnnotatedClasses.contains(targetType) &&
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
!isSpringContainerClass(targetType)) {
Map<Method, EventListener> annotatedMethods = null;
try {
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
}
else {
// Non-empty set of methods
ConfigurableApplicationContext context = this.applicationContext;
Assert.state(context != null, "No ApplicationContext set");
List<EventListenerFactory> factories = this.eventListenerFactories;
Assert.state(factories != null, "EventListenerFactory List not initialized");
// 遍历所有包含@EventListener注解的方法
for (Method method : annotatedMethods.keySet()) {
// 拿到容器中所有的EventListenerFactory
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 创建出ApplicationListenerMethodAdapter对象
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 将适配后的对象加入到监听器集合中
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
}
根据上面的代码,我们了解了Spring底层的处理逻辑是:首先拿到容器中的所有bena对象的名称,然后遍历没一个对象进行判断它是否有标有@EventListener注解的方法,有的话就存储起来,最后来遍历具有@EventListener注解的方法的bean,通过适配器工厂EventListenerFactory将其适配为ApplicationListener对象,最后放入到监听器集合中。你看是不是和我们前面想的方案二是一样的所以说,当我们掌握了Spring的扩展点后,自己来完成一些新的功能也是没有问题的。
最后
有任何问题,欢迎和小松子一起讨论啊,Spring家族爱好者一枚,希望你也是