Spring监听的使用、原理、源码分析
一、原理分析
Spring监听的核心原理就是观察者模式。本文将对应观察者模式分析Spring监听的使用和原理。文章较长,跳转记得使用文章右边的目录。
1.1,观察者模式的组成
1,Subject:目标,即被观察者
维护了观察者列表;
提供观察者添加、删除方法;
通知观察者方法;
目标发生变化的发放,具体变化业务由子类实现(相当于又使用了抽象模版设计模式)
2,ConcreteSubject:具体目标,即被观察者子类
实现目标具体变化业务
3,Observer:观察者
根据目标变化,做出相应的反应。实际业务由子类实现
4,ConcreteObserver:具体观察者
实现观察者的实际变化业务
1.2,观察者模式原理
当被观察者发生变化时,根据维护的观察者列表,在通知观察者方法中,遍历观察者列表依次通知,调用观察者的反应方法。
2.1,Spring监听组成
1,Event:事件,相当于被观察者。非Spring Bean
Spring提供了事件基类:ApplicationEvent extends EventObject,用户自定义事件只需继承基类实现自己的数据变化业务即可;
监听列表(观察者列表)由Spring容器管理;
2,Listener:监听器,相当于观察者。Spring Bean
Spring提供了监听器基类:
ApplicationListenner<E extends ApplicationEvent> extends EventListener ,用户自定义监听器需指明监听事件,并实现自己的事件反应业务;
3,Publisher:事件发布器。Spring独有,Spring Bean
观察者模式中,当目标发生变化后,需要由修改目标变化的线程主动调用目标的通知观察者方法,以此触发观察者的执行;
Spring监听在事件发生后,由修改事件变化的线程调用Spring容器的事件发布方法,以此触发监听的执行。
2.2,Spring监听原理
Spring容器维护了事件-监听的容器:Map<Event, Set<Listener>>。当事件发布后,通过维护的事件-监听容器,找到对应的监听,通过反射,调用监听方法。
由于事件非Spring Bean,故事件发布前,容器中并不存在事件-监听的关系。由于监听器在声明时已经指明了监听的事件,故在事件发布时,通过Listener Bean与事件作匹配对比,维护在Map容器中。
二、使用方式
构造了一个最基础的springboot项目用于底座,实现了一个事件多个监听器的Demo,项目结构如下:
1,定义事件
package com.example.simplespringlistener.events;
import org.springframework.context.ApplicationEvent;
public class MyEvent extends ApplicationEvent {
private String priceChangedStr;
public MyEvent(Object source, String priceChangedStr) {
super(source);
this.priceChangedStr = priceChangedStr;
}
public String getPriceChangedStr() {
return priceChangedStr;
}
public void setPriceChangedStr(String priceChangedStr) {
this.priceChangedStr = priceChangedStr;
}
}
2,定义监听器
定义监听器共有三种方式:
2.1,实现基类:ApplicationListner<E extends ApplicationEvent>
package com.example.simplespringlistener.listeners;
import com.example.simplespringlistener.events.MyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;
@Service
public class MyListener1 implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
}
}
实现接口时,指明监听的事件,用于发布时找到事件对应的监听器。
2.2,使用注解:@EventListener
package com.example.simplespringlistener.listeners;
import com.example.simplespringlistener.events.MyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
@Service
public class MyListener2 {
@EventListener(MyEvent.class)
public void handlerEvent(MyEvent event) {
System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
}
}
使用注解时,指明监听的事件,用于发布时找到事件对应的监听器。如果不指定监听的事件,该监听器将会监听所有事件。Spring在启动时,发布了框架所需要的一批事件,故自定义事件需要与框架定义的事件区分开来。
2.3,使用注解:@TransactionalEventListener
package com.example.simplespringlistener.listeners;
import com.example.simplespringlistener.events.MyEvent;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
@Service
public class MyListener3 {
// 使用默认执行机制时,phase属性可省略
@TransactionalEventListener(value = MyEvent.class, phase = TransactionPhase.AFTER_COMPLETION)
public void handlerEvent(MyEvent event) {
System.out.println(this.getClass().getName() + " : 监听到事件发生改变。具体事件:" + event.getPriceChangedStr());
}
}
使用注解时,指明监听的事件,用于发布时找到事件对应的监听器。该注解需要依赖
<artifactId>spring-tx</artifactId>。与@EventListener的区别在于:@EventListner无法指明监听器的执行时机。当主线程发布事件后,不管主线程发布后的事务是否提交,@EventListener监听器就开始执行。而@TransactionalEventListener默认在主线程提交事务后执行,还可以通过注解的phase属性调整监听器的执行时机,phase的可选值分别有:
public enum TransactionPhase {
/**
* 事务提交前
*/
BEFORE_COMMIT,
/**
* 事务提交后
*/
AFTER_COMMIT,
/**
* 事务回滚后
*/
AFTER_ROLLBACK,
/**
* 事务完成后
*/
AFTER_COMPLETION
3,定义发布器
package com.example.simplespringlistener.publishers;
import com.example.simplespringlistener.events.MyEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
@Service
public class MyPublisher {
@Autowired
private ApplicationContext applicationContext;
public void doPublish(String eventChangeStr) {
applicationContext.publishEvent(new MyEvent(this, eventChangeStr));
}
}
4,测试Spring监听
package com.example.simplespringlistener.controller;
import com.example.simplespringlistener.publishers.MyPublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestListenerController {
@Autowired
private MyPublisher myPublisher;
@PostMapping(value = "/updatePrice")
public void updatePrice(@RequestParam(value = "newPrice") String newPrice) {
myPublisher.doPublish(newPrice);
}
}
在业务需要的地方发布事件即可,执行结果如下:
三、源码分析
根据上面的例子可知,Spring监听的入口在于事件发布,我们就从发布处查看源码
从后面明显能知道:第一个是实际调用类,第二个是接口,第三个是测试用类。我们进第一类:
进入该方法
前面确定广播器类型的方法非核心方法,我们先沿着主流程查看,进入核心事件方法:multicastEvent()
进入该方法后发现,该方法主要逻辑就是找到事件对应的监听器,然后调用监听。这两个分支都很重要,我们先查看“调用监听器” 的分支源码。
1,调用监听器源码
根据上文,监听器的实现有三种方式,每种方式的调用源码路径不同,下面将根据各种方式一一详解。
1.1,实现基类ApplicationListner源码
此时调用listener.onApplicationEvent()方法,该方法已被MyListener1继承并重写,则调用会直接进入MyListener1,执行监听器的方法,结束。
1.2,使用注解@EventListener源码
根据图1.2.1,此处的listener不是MyListener2,而是ApplicationListenerMethodAdapter,使用了适配器设计模式, 在该适配器中,赋值了几个重要属性:监听器Bean(MyListener2)、监听器方法、事件。通过适配器属性,将事件与监听器和具体方法产生关联,再通过反射调用方法。
上图断点接着往里进入方法,如下图,进到了适配器类中:
查看该适配器的属性,如下图:
查看完属性后,接着图1.2.4往方法里进入,如下图:
接着看doInvoke(args)方法,如下图:
获取MyListener2 Bean后,通过反射调用方法,进入到MyListener2的 handlerEvent()
1.3,使用注解@TransactionalEventListener源码
源码调用过程与@EventListener一样
2,获取监听器源码
看完了调用监听器的源码,知道了源码是怎么执行到自定义监听器的方法里了。接下来看源码的另一个分支:如何根据事件,找到对应的监听器?分支起点如下:
这次源码要查看的方法是for循环里的getApplicationListeners(event, type),如下图:
由上图可见,该方法的返回值是监听器集合,整体思路也很简单:
步骤一:从 retrieverCache 中获取结果。retrieverCache 是个Map集合,map-key是类 ListenerCacheKey(该类包含了具体事件属性、发布器属性),map-value是类 CachedListenerRetriever(该类包含了监听器集合、监听器Bean名称集合),如下图:
步骤二:如果map-value存在,就返回CachedListenerRetriever的监听器集合;
步骤三:如果map-value不存在,创建CachedListenerRetriever 作为map-value放入retrieverCache map中,并在下面通过 retrieveApplicationListeners(eventType, sourceType, newRetriever) 方法 为新建的map-value赋予上图两个集合属性。最后返回该map-value即可。
第一次发布事件时,retrieverCache map中没有事件对应的监听器集合,需要执行步骤三,接着查看步骤三方法源码,如下图:
前面第一步原理分析中已经说明,每个监听器都是SpringBean,即for循环中的listeners包含了Spring启动时的所有监听器,如下图:
接着查看源码是如何匹配事件-监听的,进入方法supports(listener, eventType, sourceType),如下图:
又是适配器模式,通过适配器,最终通过this.declaredEventType.isAssignableFrom(eventType)) 判断是否匹配。即,根据监听器声明时指定的事件类型,与当前传入的事件class 做对比,监听器指定的事件如果是当前传入的事件的本类或子类,则表明当前监听器监听了当前事件。
至此,Spring监听的源码已分析完毕,请再回头看看第一节的Spring监听原理分析,整个流程就非常清晰且简单了。