这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战
介绍
spring事件监听机制实际上就是一个典型的观察者模式,在观察者模式的基础之上进行的抽象和处理。使得开发者可以根据自己的业务特点依附于spring容器进行事件的注册、发布、处理。
简单使用
1.创建一个类继承于顶层事件类ApplicationEvent,主要需要创建一个处理业务参数的属性值和这个属性值的构造函数。
public class SendEvent extends ApplicationEvent {
private SendVo sendVo;
public SendEvent(SendVo sendVo) {
this.sendVo = sendVo;
}
}
2.监听到对应的事件后的业务处理。可以通过SendNotificationEvent对象获取到发起监听时设置的参数,然后再执行具体的监听器业务逻辑。
@Component
public class EventListenerHandler {
@EventListener
public void sendEventListener(SendEvent event) {
// 业务逻辑
}
}
3.调用点发送具体的事件。调用点通过调用Spring的ApplicationContext对象进行事件的发布,从而进入Spring的监听机制。
public static void main(String[] args) {
SendVo sendVo = SendVo.builder()
.id("123")
.name("tom").build();
SendEvent event = new SendEvent(sendVo);
applicationContext.publishEvent(event);
}
注意:以上就是最简单的spring事件监听的使用。在具体的应用场景中,并不会这么简单的使用,因为若在业务逻辑上需要解耦,大部分还是希望是异步的方式进行事件的处理,然而在默认的情况下,这种模式是同步机制,也就是说待到具体的事件监听处理完成之后,才会继续执行调用点的业务逻辑。
异步方式
1.广播器异步
在spring的事件监听机制中已经考虑到异步的情况,所以在事件发送器发送事件时,会判断是否存在广播器,当存在广播器时,会将具体的监听执行逻辑转移到广播器对应的线程池中。来跟踪一下源码。实际上只有一个接口publishEvent,默认接口中仅是将事件类型都转换为Object对象,由子类进行具体的实现。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
// 子类实现接口
void publishEvent(Object event);
}
publishEvent的具体实现在AbstractApplicationContext中,核心逻辑是获取广播器后发送对应的事件。
protected void publishEvent(Object event, ResolvableType eventType) {
// 省略代码
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}else {
// 获取广播器, 并调用广播器对应的发送事件处理
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
}
默认仅有一个广播器的实现SimpleApplicationEventMulticaster,核心处理在这,先通过扩展接口getTaskExecutor()获取对应的的广播器,再获取这个event对应类型的监听器列表,然后进行监听任务的发布,默认是使用同步的方式,可以通过配置对应的广播器来使用线程池方式进行监听任务的发布。
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 若是配置了线程池, 将监听任务转移到线程池执行
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}else {
// 没有配置线程池, 同步方式执行执行监听任务
invokeListener(listener, event);
}
}
}
通过doInvokeListener执行具体的事件监听逻辑,然后通过listener.onApplicationEvent来进行具体的触发逻辑,所以invokeListener方法最后的逻辑是去调用onApplicationEvent方法。
刚刚上面介绍的最简单的使用方式中采用的EventListener的方式来标记监听器的位置,实际上在初始化这个bean对象时,扫描到EventListener后会将这个对应的方式转化为ApplicationListenerMethodAdapter适配器,该类中包含了对象名称、类名称、监听处理方法名称等等,待到接收到事件时,通过反射调用对应的监听处理方法。
2.@Async注解异步
虽然在事件发送器中内置了广播器线程池,但是若不进行配置,则它还是同步的方式执行,在它同步执行的基础上,若是利用spring的异步机制,也可以达到异步的效果。
@Component
public class EventListenerHandler {
@Async
@EventListener
public void sendEventListener(SendEvent event) {
// 业务处理
}
}
在这种情况下,在spring容器初始化时,扫描到这个bean对象并进行初始化时,会为这个bean创建一个代理类,由这个代理类来执行相应的异步逻辑。
事务
以上看似已经解决的异步的问题,但是在实际的使用过程中又发现如果事件发送点存在事务管理,就会导致事件中获取不到事件发送点的某些数据。(由于事件监听处理触发时,事件发送点还未提交事务。)
伪代码, 在事件监听的处理中, 通过id=123可能存在获取不到这条数据的情况
public void send() {
SendVo sendVo = SendVo.builder()
.id("123")
.name("tom").build();
mapper.insert(sendVo);
SendnEvent event = new SendEvent(sendVo);
applicationContext.publishEvent(event);
}
但是、但是、但是这种情况spring也考虑到了,spring监听机制中通过使用TransactionalEventListener来解决这个问题。TransactionalEventListener它的元注解为EventListener,所以本质上也是个EventListener注解。
- phase:事件触发阶段: 比如事务提交之前、事务提交之后等, 默认是在事务提交之后
- fallbackExecution: 若调用点无事务管理也触发, 默认情况下若调用点无事务接管, 该监听处理不会触发
TransactionPhase phase();
boolean fallbackExecution();
刚刚上面说到在spring扫描到对应的监听器处理bean时,从bean的class对象中找出含有@EventListener注解的方法, 存在map中,同时@TransactionListener方法也会被匹配, 因为它的元注解是@EventListener,所以是根据方法上标记的注解将监听器转换为对应的处理类。根据不同的两个注解@TransactionalEventListener和EventListener对应两个不同的生成监听类工厂DefaultEventListenerFactory和TransactionalEventListenerFactory,由它们来创建具体的监听处理类。
获取监听工厂, 这里有两个工厂:DefaultEventListenerFactory和TransactionalEventListenerFactory。然后判断这个被标记的方法适配哪个工厂,使用工厂创建对应的监听器对象来处理。
private void processBean(final String beanName, final Class<?> targetType) {
Map<Method, EventListener> annotatedMethods = null;
if (CollectionUtils.isEmpty(annotatedMethods)) {
// 省略代码
}else {
ConfigurableApplicationContext context = this.applicationContext;
List<EventListenerFactory> factories = this.eventListenerFactories;
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
而这两个工厂生成出来的监听类,实际上是两个适配器,ApplicationListenerMethodAdapter和ApplicationListenerMethodTransactionalAdapter,由这两个适配器来执行相应的处理逻辑。这里要感叹下spring设计的精妙,一环扣一环,扩展性极强。
这里分析下ApplicationListenerMethodTransactionalAdapter中对应的监听触发方法onApplicationEvent,publish事件时: 创建一个TransactionSynchronization对象, 这个对象持有event,创建TransactionSynchronizationEventAdapter事件适配器,然后注册到事务管理器中。
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 通过事务管理器方式执行
}else if (this.annotation.fallbackExecution()) {
// 反射进行事件的处理
}
}
本以为采用这种方式之后,就解决了对应的异步+调用点事务的问题。在测试中发现:若采用广播器实现异步,极大可能获取不到调用点事务内数据;而采用@Async实现异步百分百可以获取到调用点事务内数据。
简单跟踪发现:
- 广播器方式实现异步,是将
onApplicationEvent方法的触发丢入线程池。 @Async方式实现异步,走下方else逻辑,在事件发送器中走同步逻辑,是直接执行onApplicationEvent。
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
// invokeListener()返回最后的逻辑是去调用ApplicationListener.onApplicationEvent()
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
这里的是否对onApplicationEvent方法执行执行起到了关键性的作用,因为在事务监听处理器适配器中会判断是否是否存在事务。第一种情况,由线程池内线程来执行该方法,这时事务是绑定在原线程上,所以会导致这个判断结果为false。第二种情况,由事件发送线程执行该方法,这时与事务在同一线程,则这个判断的结果为true,将对应的事件处理方法注册到事务管理器中,待到执行改事件监听处理方法时,是异步进行处理的。
总结
整体使用下来,发现其中的道道还是很多的,这需要对所有的组合情况、问题情况、原理都掌握的情况下,否则随意组合,可能在某一场景下能达到需要的效果,但是就像是埋下了定时炸弹。当然了spring的事件监听机制毕竟只是基于内存,若对应的生产环境并没有升级停机钩子处理,或者是金丝雀升级等方式,需停机升级,有可能会导致部分监听未执行的情况,所以建议生产环境还是通过一些mq组件进行发布监听事件的处理。