【Spring源码】Spring Event事件

目录

1、前言

2、什么是Spring Event?

3、基本使用

3.1、定义事件

3.2、发布事件

3.3、监听事件

3.3.1、继承ApplicationListener

3.3.2、使用@EventListener注解

4、Spring Event是同步还是异步?

4.1、源码实现

4.2、如何实现异步

4.2.1、使用@Async注解

4.2.2、手动实现异步线程池

4.2.3、自定义ApplicationEventMulticaster

5、@TransactionalEventListener

5.1、基本使用


1、前言

事件发布/订阅机制在实际项目中很经常用到,一方面可以很容易让我们的代码进行解耦,另一方面可以很方便的进行一对一或一对多的消息通信,是一种常见的观察者设计模式,具有很好的扩展性。今天就来讲一下Spring的事件机制。

2、什么是Spring Event?

Spring框架中的事件是一种观察者设计模式的实现,用于在应用程序中处理各种状态变化。事件驱动编程是一种流行的编程范式,其中组件之间的通信是通过事件(或消息)进行的。Spring的事件机制允许对象在状态发生变化时发布事件,其他对象则可以订阅这些事件并在事件发生时执行特定的操作。

3、基本使用

Spring Event的使用基本有以下几个步骤:定义事件,发布事件,监听事件。

3.1、定义事件

先定义一个事件Event,继承Spring的ApplicationEvent,声明构造函数将需要传递的事件信息包装为业务事件类。如:

/*** 这里定义事件DamIllegalDataEvent。*/
public class DamIllegalDataEvent extends ApplicationEvent {// 声明构造函数,接收DamIllegalDataDto集合传递到事件中public DamIllegalDataEvent(List<DamIllegalDataDto> list) {super(list);}
}

3.2、发布事件

发布事件时可以注入ApplicationEventPublisher,也可以获取到ApplicationContext,然后调用publisherEvent()方法推送事件。

@RestController
@RequestMapping("anno/dam")
public class DamTestController {@Autowiredprivate ApplicationEventPublisher applicationPushBuilder;@GetMapping("test_audit")public String test_audit(){DamIllegalDataDto build = DamIllegalDataDto.builder().illegalData("11111").source("2222").functionDesc("数据清理中错误了").functionName("333").build();// 注入applicationPushBuilderapplicationPushBuilder.publishEvent(new DamIllegalDataEvent(Collections.singletonList(build)));// 这里也可以直接使用hutool工具类直接发布SpringUtil.publishEvent(new DamIllegalDataEvent(Collections.singletonList(build)));return "ok";}
}

3.3、监听事件

监听事件也可称为订阅事件,即当事件发布了之后,需要监听该事件并进行消费。Spring里面提供了两种事件订阅的方式:

  • 继承ApplicationListener,并实现onApplicationEvent方法。
  • 使用@EventListener注解方法。

3.3.1、继承ApplicationListener

创建一个监听器DamIllegalDataEventListener继承ApplicationListener,通过泛型指定需要监听的事件类。如:

@Slf4j
@Component
public class DamIllegalDataEventListener implements ApplicationListener<DamIllegalDataEvent> {@Autowiredprivate DamIllegalDataAuditService damIllegalDataAuditService;@Overridepublic void onApplicationEvent(DamIllegalDataEvent event) {LOGGER.info("异常数据审计事件开始执行...");List<DamIllegalDataDto> damIllegalDataDtos = (List<DamIllegalDataDto>) event.getSource();// todo......doSomething();}
}

3.3.2、使用@EventListener注解

使用@EventListener注解方法,将其包装为事件处理器。它适用于:1. 不想为每个事件处理都创建一个ApplicationListener实现类;2. 希望支持更复杂的事件条件过滤。@EventListener的classes属性可以过滤事件类型,而condition属性可以根据事件对象是否满足条件表达式来过滤事件。

@Slf4j
@Component
public class DamIllegalDataEventListener {/*** EventListener注解定义事件处理器,并指定监听事件为DamIllegalDataEvent。* condition声明只有事件的code==200时,才进入该事件*/@EventListener(classes = {DamIllegalDataEvent.class}, condition="#event.code==200")public void onApplicationEvent(DamIllegalDataEvent event) {LOGGER.info("异常数据审计事件开始执行...");List<DamIllegalDataDto> damIllegalDataDtos = (List<DamIllegalDataDto>) event.getSource();// todo......doSomething();}
}

4、Spring Event是同步还是异步?

默认情况下 Spring Event是同步执行的。你怎么这么确定?我们先来演示下上面的demo。先实现一个测试接口,该接口发布了一个事件,发布完后打印一行日志:

@GetMapping("test_audit")
public String test_audit(){DamIllegalDataDto build = DamIllegalDataDto.builder().illegalData("11111").source("2222").functionDesc("数据清理中错误了").functionName("333").build();SpringUtil.publishEvent(new DamIllegalDataEvent(Collections.singletonList(build)));System.out.println("接口请求完成......");return "ok";
}

事件监听中打印一行日志,并睡眠5s:

@Slf4j
@Component
public class DamIllegalDataEventListener implements ApplicationListener<DamIllegalDataEvent> {@Overridepublic void onApplicationEvent(DamIllegalDataEvent event) {LOGGER.info("异常数据审计事件开始执行...");ThreadUtil.sleep(5000);}
}

执行查看结果,可以发现不管如何请求,日志打印总是按顺序执行,并且会间隔5S。

4.1、源码实现

如果还是不信?那我们来看源码:org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object),断点跟进到org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {// 包装ApplicationEventApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;}else {applicationEvent = new PayloadApplicationEvent<>(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// 考虑到部分事件在Listener注册之前就发布了,因此先保存起来if (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);}else {// 重点是这里// 铜通过getApplicationEventMulticaster()获取事件发布器;// 调用multicastEvent方法发布事件getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// 同时给父容器发布事件if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);}else {this.parent.publishEvent(event);}}
}

跟进multicastEvent()方法,org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType):

@Override
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);}}
}

4.2、如何实现异步

实现异步方式,可以有3中实现:

  • 使用@Async 注解
  • 手动实现异步线程池
  • 自定义ApplicationEventMulticaster

4.2.1、使用@Async注解

使用这个很简单,只要在事件监听方法上添加@Async注解即可,springboot的启动器需要开启异步@EnableAsync。

@Async
@Override
public void onApplicationEvent(DamIllegalDataEvent event) {LOGGER.info("异常数据审计事件开始执行...");ThreadUtil.sleep(5000);
}

注意:

使用@Async时,最好自己配置相应的线程池核心数以及延迟队列等等。由于Spring中使用@Async异步线程每次都会创建一个新线程执行,如果滥用 它,可能会有内存问题。

4.2.2、手动实现异步线程池

顾名思义就是手动创建一个线程池执行,与@Async类似。

@Slf4j
@Component
public class DamIllegalDataEventListener implements ApplicationListener<DamIllegalDataEvent> {@Overridepublic void onApplicationEvent(DamIllegalDataEvent event) {ThreadUtil.execAsync(() -> {LOGGER.info("异常数据审计事件开始执行...");ThreadUtil.sleep(5000);});}
}

4.2.3、自定义ApplicationEventMulticaster

由于Spring容器会优先使用beanName为applicationEventMulticater 的bean作为事件转发处理器,如果不存在则默认使用SimpleApplicationEventMulticaster作为事件转发处理器,它默认是同步执行的。但它支持设置Executor,那么我们可以将自定义的线程池处理器作为Executor,以此来支持异步执行。

@Configuration
public class DamEventConfig {@Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)public SimpleApplicationEventMulticaster eventMulticaster(){SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster();simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor());return simpleApplicationEventMulticaster;}/*** 目前服务器为8c,默认给他4个,一般事件推送的情况不会多。如果多的话,请检查一下业务使用* @return*/@Beanpublic TaskExecutor taskExecutor(){ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(4);return executor;}
}

配置完之后,事件监听那边都无需修改。

注意:

这种方式的配置是全局性的,一旦配置了之后,所有的事件都是异步的形式处理。如果需要个别业务是同步的,那么此种方式要特别注意。

5、@TransactionalEventListener

提到事件,这里再提一个注解@TransactionalEventListener,也即感知事务,基于事件形式与事务的某个阶段进行绑定。比如在事务提交之前或之后进行一些业务的处理,如短信提醒等等。@TransactionEventListener允许事件处理方法感知事务。它的phase属性,表示希望在事务的哪个阶段执行事件处理。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {/*** Phase to bind the handling of an event to.* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.* <p>If no transaction is in progress, the event is not processed at* all unless {@link #fallbackExecution} has been enabled explicitly.*/TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;/*** Whether the event should be handled if no transaction is running.*/boolean fallbackExecution() default false;/*** Alias for {@link #classes}.*/@AliasFor(annotation = EventListener.class, attribute = "classes")Class<?>[] value() default {};/*** The event classes that this listener handles.* <p>If this attribute is specified with a single value, the annotated* method may optionally accept a single parameter. However, if this* attribute is specified with multiple values, the annotated method* must <em>not</em> declare any parameters.*/@AliasFor(annotation = EventListener.class, attribute = "classes")Class<?>[] classes() default {};/*** Spring Expression Language (SpEL) attribute used for making the event* handling conditional.* <p>The default is {@code ""}, meaning the event is always handled.* @see EventListener#condition*/@AliasFor(annotation = EventListener.class, attribute = "condition")String condition() default "";/*** An optional identifier for the listener, defaulting to the fully-qualified* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").* @since 5.3* @see EventListener#id* @see TransactionalApplicationListener#getListenerId()*/@AliasFor(annotation = EventListener.class, attribute = "id")String id() default "";}

TransactionPhase枚举声明了事务提交的各个阶段:

public enum TransactionPhase {/*** Handle the event before transaction commit.* @see TransactionSynchronization#beforeCommit(boolean)*/BEFORE_COMMIT,/*** Handle the event after the commit has completed successfully.* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore* executes in the same sequence of events as {@code AFTER_COMPLETION}* (and not in {@link TransactionSynchronization#afterCommit()}).* <p>Interactions with the underlying transactional resource will not be* committed in this phase. See* {@link TransactionSynchronization#afterCompletion(int)} for details.* @see TransactionSynchronization#afterCompletion(int)* @see TransactionSynchronization#STATUS_COMMITTED*/AFTER_COMMIT,/*** Handle the event if the transaction has rolled back.* <p>Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore* executes in the same sequence of events as {@code AFTER_COMPLETION}.* <p>Interactions with the underlying transactional resource will not be* committed in this phase. See* {@link TransactionSynchronization#afterCompletion(int)} for details.* @see TransactionSynchronization#afterCompletion(int)* @see TransactionSynchronization#STATUS_ROLLED_BACK*/AFTER_ROLLBACK,/*** Handle the event after the transaction has completed.* <p>For more fine-grained events, use {@link #AFTER_COMMIT} or* {@link #AFTER_ROLLBACK} to intercept transaction commit* or rollback, respectively.* <p>Interactions with the underlying transactional resource will not be* committed in this phase. See* {@link TransactionSynchronization#afterCompletion(int)} for details.* @see TransactionSynchronization#afterCompletion(int)*/AFTER_COMPLETION
}

5.1、基本使用

在含有事务的方法里发布事件:

@Transactional(rollbackFor = Exception.class)
public void test(){DamIllegalDataAudit audit = new DamIllegalDataAudit();audit.setId("1726931543097610240");audit.setRemark("xxx");this.baseMapper.updateById(audit);DamIllegalDataDto build = DamIllegalDataDto.builder().illegalData("11111").source("2222").functionDesc("数据清理中错误了").functionName("333").build();applicationEventPublisher.publishEvent(new DamIllegalDataEvent(Collections.singletonList(build)));
}

定义感知事务监听:

@Component
public class TransactionalEventProcess {@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void afterCommit(DamIllegalDataEvent event){System.out.println("事务提交后事件处理");}@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)public void afterRollback(DamIllegalDataEvent event){System.out.println("事务回滚后事件处理");}
}

当执行事务方法时候,可以发现:

注意:

如果事件自定义了ApplicationEventMulticaster,让事件变成异步,那么该感知事务会失效。

但是如果使用@Async或手动定义了 异步线程池ThreadUtil.execAsync还是可以生效的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171658.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis与Mysql的数据强一致性方案

目的 Redis和Msql来保持数据同步&#xff0c;并且强一致&#xff0c;以此来提高对应接口的响应速度&#xff0c;刚开始考虑是用mybatis的二级缓存&#xff0c;发现坑不少&#xff0c;于是决定自己搞 要关注的问题点 操作数据必须是唯一索引 如果更新数据不是唯一索引&#…

Vue3进阶5个小知识点 附带源码

1.在Vue3中&#xff0c;可以使用<script setup>标签来简化组件的编写。在这个标签中&#xff0c;我们可以将数据、方法和其他属性直接放在setup()函数内部&#xff0c;而不需要使用data、methods等选项。这样可以让代码更加简洁和易于阅读。 <template><div>…

5种主流API网关技术选型,yyds!

API网关是微服务项目的重要组成部分&#xff0c;今天来聊聊API网关的技术选型&#xff0c;有理论&#xff0c;有实战。 不 BB&#xff0c;上文章目录&#xff1a; 1 API网关基础 1.1 什么是API网关 API网关是一个服务器&#xff0c;是系统的唯一入口。 从面向对象设计的角度…

QLabel(“Find What:“) 是在创建 QLabel 对象时,使用 ““ 符号来指定标签的助记键(Mnemonic Key)、访问键

QLabel("&Find What:") 是在创建 QLabel 对象时&#xff0c;使用 “&” 符号来指定标签的助记键&#xff08;Mnemonic Key&#xff09;。 在 Qt 中&#xff0c;助记键用于提供快捷键访问标签或小部件的功能。通过在标签文本中使用 “&” 符号&#xff0…

docker介绍、部署与常用命令

一、docker 介绍 1、容器&#xff08;Container&#xff09;&#xff1a; (1) 概念&#xff1a; 容器是一种用于运行和部署应用程序的技术。它将应用程序及其所有依赖项&#xff08;例如代码、运行时、系统工具、系统库等&#xff09;打包在一个独立的、可移植的运行环境中&…

Facebook的特点优势

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也是最受欢迎的社交网站之一&#xff0c;Facebook具有许多独特的特点和优势。本文小编将说一些关于Facebook的特点及优势。 1、全球化 Facebook拥有数十亿的全球用户&#xff0c;覆盖了几乎所有国家和地区。这使得人们…

【深度学习笔记】05 线性回归

线性回归 线性回归基于几个简单的假设&#xff1a; 首先&#xff0c;假设自变量 x \mathbf{x} x和因变量 y y y之间的关系是线性的&#xff0c; 即 y y y可以表示为 x \mathbf{x} x中元素的加权和&#xff0c;这里通常允许包含观测值的一些噪声&#xff1b; 其次&#xff0c;我…

MQTT客户端MQTT.fx 1.7.1下载、安装和界面介绍

MQTT.fx是一款基于Eclipse Paho&#xff0c;使用Java语言编写的MQTT客户端工具。支持通过Topic订阅和发布消息&#xff0c;用来前期和物理云平台调试非常方便。 1.下载 1.1.访问官方下载地址下载&#xff0c;但是下载不到1.7.1版本 1.2.在连接网页末尾点击立即下载&#xff0c;…

代码随想录 452. 用最少数量的箭引爆气球

题目 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射…

R语言如何实现多元线性回归

输入数据 先把数据用excel保存为csv格式放在”我的文档”文件夹 打开R软件,不用新建,直接写 回归计算 求三个平方和 置信区间(95%)

STL: 容器适配器stack 与 queue

目录 1.容器适配器 1.1 STL标准库中stack和queue的底层结构 1.2 deque的简单介绍(了解) 1.2.1 deque的原理介绍 1.2.2 deque的缺陷 1.2.3 为什么选择deque作为stack和queue的底层默认容器 2. stack的介绍和使用 2.1 stack的介绍 2.2 stack的使用 2.3 利用deque模拟实现…

【算法】FFT-1(递归实现)(不包括IFFT)

FFT 多项式多项式乘法复数及运算导数泰勒公式及展开式欧拉公式单位根 FFTCode IFFT 多项式 我们从课本中可以知道&#xff0c;一个 n − 1 n-1 n−1 次的多项式可以写成 a 0 a 1 x a 2 x 2 a 3 x 3 ⋯ a n − 1 x n − 1 a_{0}a_{1}xa_{2}x^2a_{3}x^3\dotsa_{n-1}x^{n-…

基于Hadoop架构的多重分布式BP神经网络的短期负荷预测方法

点我完整下载&#xff1a;基于Hadoop架构的多重分布式BP神经网络的短期负荷预测方法.docx 基于Hadoop架构的多重分布式BP神经网络的短期负荷预测方法 "A Short-term Load Forecasting Method based on Multi-distributed BP Neural Network Architecture with Hadoop Fram…

【挑战业余一周拿证】二、在云中计算 - 第 2 节 - Amazon EC2 实例类型

第 2 节 - Amazon EC2 实例类型 如果我们想让企业尽可能高效地运作&#xff0c;那就一定要确保员工的技能组合适合他们的角色&#xff0c;就 像我们的咖啡店有不同类型的员工一样&#xff0c;亚马逊云科技也有不同类型的 EC2 实例。每种实例类型 都归属于一个实例系列&#x…

BUUCTF刷题之路-web-[GXYCTF2019]Ping Ping Ping1

启动环境后&#xff0c;是一个简简单单的页面&#xff1a; 看样子是能够触发远程执行漏洞的。尝试下ping 127.0.0.1&#xff0c;如果有回显说明我们的想法是对的。 最近才学习的nc反弹shell。想着是否能用nc反弹shell的办法。控制服务器然后输出flag呢&#xff1f;于是我测试下…

如何通过nginx进行服务的负载均衡

简单介绍 随着互联网的发展&#xff0c;业务流量越来越大并且业务逻辑也越来越复杂&#xff0c;单台服务器的性能及单点故障问题就凸显出来了&#xff0c;因此需要多台服务器组成应用集群&#xff0c;进行性能的水平扩展以及避免单点故障的出现。应用集群是将同一应用部署到多台…

mysql命令行连接数据库

有时项目连接不上数据库&#xff0c;报错鉴权失败&#xff0c;先用mysql工具连接下&#xff0c;容易发现问题。 直接输入mysql看是否已安装&#xff0c;如果没有就安装下。 # 注&#xff1a;直接mysql就行&#xff0c;不用-cli也不用-client&#xff0c;也不用-server&#xf…

NAS非接入层协议学习(二)

在无线通信网络中 NAS (Non-Access Stratum)做为非接入层是演进分组系统(或5G核心网)中的一组协议。NAS用于在用户设备(UE)和移动管理实体(MME/AMF)之间传送非无线电信令&#xff0c;以实现NR/LTE/E-UTRAN接入。 NAS在协议栈中是控制面的最高层。 NAS协议分组中可以将其分为两…

美化wordpress复制文章内容弹出版权提示框的源码代码

通过SweetAlert美化的提示框 将下面代码添加到当前主题模板函数functions.php文件最后即可&#xff1a; function zm_copyright_tips() { echo <link rel"stylesheet" type"text/css" rel"external nofollow" target"_blank" href…

hive杂谈

数据仓库是一个面向主题的、集成的、非易失的、随时间变化的&#xff0c;用来支持管理人员决策的数据集合&#xff0c;数据仓库中包含了粒度化的企业数据。 数据仓库的主要特征是&#xff1a;主题性、集成性、非易失性、时变性。 数据仓库的体系结构通常包含4个层次&#xff…