当前位置: 首页 > news >正文

使用上 Spring 的事件机制

本文主要是简单的讲述了Spring的事件机制,基本概念,讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制,应用的场景,搭配@Async注解实现异步的操作等等。希望对大家有所帮助。

Spring的事件机制的基本概念

Spring的事件机制是Spring框架中的一个重要特性,基于观察者模式实现,它可以实现应用程序中的解耦,提高代码的可维护性和可扩展性。Spring的事件机制包括事件、事件发布、事件监听器等几个基本概念。其中,事件是一个抽象的概念,它代表着应用程序中的某个动作或状态的发生。事件发布是事件发生的地方,它负责产生事件并通知事件监听器。事件监听器是事件的接收者,它负责处理事件并执行相应的操作。在Spring的事件机制中,事件源和事件监听器之间通过事件进行通信,从而实现了模块之间的解耦。

举个例子:用户修改密码,修改完密码后需要短信通知用户,记录关键性日志,等等其他业务操作。

如下图,就是我们需要调用多个服务来进行实现一个修改密码的功能。

图片

 

使用了事件机制后,我们只需要发布一个事件,无需关心其扩展的逻辑,让我们的事件监听器去处理,从而实现了模块之间的解耦。

图片

 

事件

通过继承ApplicationEvent,实现自定义事件。是对 Java EventObject 的扩展,表示 Spring 的事件,Spring 中的所有事件都要基于其进行扩展。其源码如下。

我们可以获取到timestamp属性指的是发生时间。

图片

 

事件发布

事件发布是事件发生的地方,它负责产生事件并通知事件监听器。ApplicationEventPublisher用于用于发布 ApplicationEvent 事件,发布后 ApplicationListener 才能监听到事件进行处理。源码如下。

需要一个ApplicationEvent,就是我们的事件,来进行发布事件。

图片

 

事件监听器

ApplicationListener 是 Spring 事件的监听器,用来接受事件,所有的监听器都必须实现该接口。该接口源码如下。

图片

 

Spring的事件机制的使用方法

下面会给大家演示如何去使用Spring的事件机制。就拿修改密码作为演示。

如何定义一个事件

新增一个类,继承我们的ApplicationEvent。

如下面代码,继承后定义了一个userId,有一个UserChangePasswordEvent方法。这里就定义我们监听器需要的业务参数,监听器需要那些参数,我们这里就定义那些参数。

/*** @Author JiaQIng* @Description 修改密码事件* @ClassName UserChangePasswordEvent* @Date 2023/3/26 13:55**/
@Getter
@Setter
public class UserChangePasswordEvent extends ApplicationEvent {private String userId;public UserChangePasswordEvent(String userId) {super(new Object());this.userId = userId;}
}

如何监听事件

实现监听器有两种方法

1、 新建一个类实现ApplicationListener接口,并且重写onApplicationEvent方法注入到Spring容器中,交给Spring管理如下代码新建了一个发送短信监听器,收到事件后执行业务操作****;

/*** @Author JiaQIng* @Description 发送短信监听器* @ClassName MessageListener* @Date 2023/3/26 14:16**/
@Component
public class MessageListener implements ApplicationListener<UserChangePasswordEvent> {@Overridepublic void onApplicationEvent(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());}
}

1、 使用@EventListener注解标注处理事件的方法,此时Spring将创建一个ApplicationListenerbean对象,使用给定的方法处理事件源码如下参数可以给指定的事件这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了

图片

代码如下。新建一个事件监听器,注入到Spring容器中,交给Spring管理。在指定方法上添加@EventListener参数为监听的事件。方法为业务代码。使用 @EventListener 注解的好处是一个类可以写很多监听器,定向监听不同的事件,或者同一个事件。

/*** @Author JiaQIng* @Description 事件监听器* @ClassName LogListener* @Date 2023/3/26 14:22**/
@Component
public class ListenerEvent {@EventListener({ UserChangePasswordEvent.class })public void LogListener(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());}@EventListener({ UserChangePasswordEvent.class })public void messageListener(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());}
}

1、 @TransactionalEventListener来定义一个监听器,他与@EventListener不同的就是@EventListener标记一个方法作为监听器,他默认是同步执行,如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交我们就可以使用该注解来标识注意此注解需要spring-tx的依赖

注解源码如下:主要是看一下注释内容。

// 在这个注解上面有一个注解:@EventListener,所以表明其实这个注解也是个事件监听器。 
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {/*** 这个注解取值有:BEFORE_COMMIT(指定目标方法在事务commit之前执行)、AFTER_COMMIT(指定目标方法在事务commit之后执行)、* AFTER_ROLLBACK(指定目标方法在事务rollback之后执行)、AFTER_COMPLETION(指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了)* 各个值都代表什么意思表达什么功能,非常清晰,* 需要注意的是:AFTER_COMMIT + AFTER_COMPLETION是可以同时生效的* AFTER_ROLLBACK + AFTER_COMPLETION是可以同时生效的*/TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;/*** 表明若没有事务的时候,对应的event是否需要执行,默认值为false表示,没事务就不执行了。*/boolean fallbackExecution() default false;/***  这里巧妙的用到了@AliasFor的能力,放到了@EventListener身上*  注意:一般建议都需要指定此值,否则默认可以处理所有类型的事件,范围太广了。*/@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 "";}

使用方式如下。phase事务类型,value指定事件。

/*** @Author JiaQIng* @Description 事件监听器* @ClassName LogListener* @Date 2023/3/26 14:22**/
@Component
public class ListenerEvent {@EventListener({ UserChangePasswordEvent.class })public void logListener(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());}@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,value = { UserChangePasswordEvent.class })public void messageListener(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作给用户发送短信。用户userId为:" + event.getUserId());}
}

如何发布一个事件

1、 使用ApplicationContext进行发布,由于ApplicationContext已经继承了ApplicationEventPublisher,因此可以直接使用发布事件源码如下;

图片

 

1、 直接注入我们的ApplicationEventPublisher,使用@Autowired注入一下;

三种发布事件的方法,我给大家演示一下@Autowired注入的方式发布我们的事件。

@SpringBootTest
class SpirngEventApplicationTests {@AutowiredApplicationEventPublisher appEventPublisher;@Testvoid contextLoads() {appEventPublisher.publishEvent(new UserChangePasswordEvent("1111111"));}}

我们执行一下看一下接口。

图片

 

测试成功。

搭配@Async注解实现异步操作

监听器默认是同步执行的,如果我们想实现异步执行,可以搭配@Async注解使用,但是前提条件是你真的懂@Async注解,使用不当会出现问题的。 后续我会出一篇有关@Async注解使用的文章。这里就不给大家详细的解释了。有想了解的同学可以去网上学习一下有关@Async注解使用。

使用@Async时,需要配置线程池,否则用的还是默认的线程池也就是主线程池,线程池使用不当会浪费资源,严重的会出现OOM事故。

下图是阿里巴巴开发手册的强制要求。

图片

 

简单的演示一下:这里声明一下俺没有使用线程池,只是简单的演示一下。

1、 在我们的启动类上添加@EnableAsync开启异步执行配置;

@EnableAsync
@SpringBootApplication
public class SpirngEventApplication {public static void main(String[] args) {SpringApplication.run(SpirngEventApplication.class, args);}}

1、 在我们想要异步执行的监听器上添加@Async注解;

/*** @Author JiaQIng* @Description 事件监听器* @ClassName LogListener* @Date 2023/3/26 14:22**/
@Component
public class ListenerEvent {@Async@EventListener({ UserChangePasswordEvent.class })public void logListener(UserChangePasswordEvent event) {System.out.println("收到事件:" + event);System.out.println("开始执行业务操作生成关键日志。用户userId为:" + event.getUserId());}
}

这样我们的异步执行监听器的业务操作就完成了。

Spring的事件机制的应用场景

1、 告警操作,比喻钉钉告警,异常告警,可以通过事件机制进行解耦;
2、 关键性日志记录和业务埋点,比喻说我们的关键日志需要入库,记录一下操作时间,操作人,变更内容等等,可以通过事件机制进行解耦;
3、 性能监控,比喻说一些接口的时长,性能方便的埋点等可以通过事件机制进行解耦;
4、 .......一切与主业务无关的操作都可以通过这种方式进行解耦,常用的场景大概就上述提到的,而且很多架构的源码都有使用这种机制,如GateWay,Spring等等;

Spring的事件机制的注意事项

1、 对于同一个事件,有多个监听器的时候,注意可以通过@Order注解指定顺序,Order的value值越小,执行的优先级就越高
2、 如果发布事件的方法处于事务中,那么事务会在监听器方法执行完毕之后才提交事件发布之后就由监听器去处理,而不要影响原有的事务,也就是说希望事务及时提交我们就可以@TransactionalEventListener来定义一个监听器;
3、 监听器默认是同步执行的,如果我们想实现异步执行,可以搭配@Async注解使用,但是前提条件是你真的懂@Async注解,使用不当会出现问题的
4、 对于同一个事件,有多个监听器的时候,如果出现了异常,后续的监听器就失效了,因为他是把同一个事件的监听器add在一个集合里面循环执行,如果出现异常,需要注意捕获异常处理异常

相关文章:

使用上 Spring 的事件机制

本文主要是简单的讲述了Spring的事件机制&#xff0c;基本概念&#xff0c;讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制&#xff0c;应用的场景&#xff0c;搭配Async注解实现异步的操作等等。希望对大家有所帮助。 Spring的事件机制的基本概念 …...

Linux安装QT

//进入qt安装包路径 cd qt安装包路径 //修改权限 sudo chmod x qt-opensource-linux-x64-5.14.2.run //运行qt安装包 sudo ./qt-opensource-linux-x64-5.14.2.run //安装编译器 sudo apt-get install gcc g //安装编译工具 sudo apt-get install build-essential //安装Op…...

如何用arduino uno主板播放自己想要的曲子。《我爱你中国》单片机版本。

目录 一.效果展示 二.基本原理 三.电路图 四.代码 一.效果展示 arduino播放《我爱你中国》 二.基本原理 利用arduino uno单片机实现对蜂鸣器振动频率的调节&#xff0c;基于PWM控制系统通过代码实现控制。 三.电路图 四.代码 //main.uno #define Buzzer 2int PotBuffer …...

redis入门2-命令

Redis的基本数据类型 redis的基本数据类型&#xff08;value&#xff09;: string,普通字符串 hash&#xff08;哈希&#xff09;,适合存储对象 list(列表),按照插入顺序排序&#xff0c;可以由重复的元素 set(无序集合)&#xff0c;没有重复的元素 sorted set(有序集合)&…...

Typescript 枚举类型

枚举是用来表示一组明确的可选值列表 // enum是枚举类型的关键字 //枚举如果不设置值&#xff0c;默认从0开始 enum Direction {Up, // 0 Down, // 1 Left, // 2Right // 3} //如果给第一个值赋值为100&#xff0c;则第二、第三第四个都会在第一个的基础上1 分别是101,102…...

docker小记-容器中启动映射端口号但访问不到

在docker容器中是每一个容器隔离分开的。 每个容器视为一个独立的环境&#xff0c;当在外部环境访问不到的时候就是说明端口号还是没映射到。 之前使用的映射说白了就是将docker中的独立的ip地址端口号映射到主机的ip地址和端口号。这一步没有成功。 docker inspect 容器名 …...

Java中的Map常见使用案例代码

以下是一些Java中Map的常见使用案例和具体代码实现&#xff1a; Map的遍历 Map<String, Integer> map new HashMap<>(); map.put(“apple”, 10); map.put(“banana”, 20); map.put(“orange”, 30); // 遍历方式一&#xff1a;使用entrySet()方法遍历 for (M…...

计算机视觉实验:图像处理综合-路沿检测

目录 实验步骤与过程 1. 路沿检测方法设计 2. 路沿检测方法实现 2.1 视频图像提取 2.2 图像预处理 2.3 兴趣区域提取 2.4 边缘检测 ​​​​​​​2.5 Hough变换 ​​​​​​​2.6 线条过滤与图像输出 3. 路沿检测结果展示 4. 其他路沿检测方法 实验结论或体会 实…...

Linux环境下VS code的python与C++调试环境的安装

Linux环境下VS code的python与C调试环境的安装 文章目录 Linux环境下VS code的python与C调试环境的安装前言一、云服务器的环境二、VS code相关信息三、python 开发环境配置四、C开发环境配置1.测试main.cpp2.进行debug3.进行debug程序4.运行main.cpp程序步骤 前言 最近写的 C&…...

AlexNet卷积神经网络-笔记

AlexNet卷积神经网络-笔记 AlexNet卷积神经网络2012年提出 测试结果为&#xff1a; 通过运行结果可以发现&#xff0c; 在眼疾筛查数据集iChallenge-PM上使用AlexNet&#xff0c;loss能有效下降&#xff0c; 经过5个epoch的训练&#xff0c;在验证集上的准确率可以达到94%左右…...

剑指 Offer 53 - I. !!在排序数组中查找数字 I (考查二分法)

剑指 Offer 53 - I. 在排序数组中查找数字 I 统计一个数字在排序数组中出现的次数。 示例 1: 输入: nums [5,7,7,8,8,10], target 8 输出: 2 示例 2: 输入: nums [5,7,7,8,8,10], target 6 输出: 0 提示&#xff1a; 0 < nums.length < 105 -109 < nums[i] &l…...

RANSAC算法在Python中的实现与应用探索:线性拟合与平面拟合示例

第一部分:RANSAC算法与其应用 在我们的日常生活和多个领域中,如机器学习,计算机视觉,模式识别等,处理数据是一个非常重要的任务。尤其是当我们需要从嘈杂的数据中获取信息或拟合模型时。有时候,数据可能包含异常值或噪声,这可能会对我们的结果产生重大影响。为了解决这…...

PHP接口自动化测试框架实现

我们来看一个简单的PHP实现的超简单的接口。 ...//报名验证 private function apply_verify() {$raid $this->input->get_post(raid);$mid $this->input->get_post(mid);if (!$raid || !$mid) {$this->ret_json(10021, 参数错误);}$this->load->model(…...

VLAN原理+配置

目录 一&#xff0c; 以太网二层交换机 二&#xff0c;三层架构&#xff1a; 三&#xff0c;VLAN配置思路 1.创建vlan 2.接口划入vlan 3.trunk干道 4.vlan间路由器 5.DHCP池塘配置 四&#xff0c;华为VLAN部分的接口模式讲解&#xff1a; 五&#xff0c;华为VLAN部分的…...

MongoDB文档-基础使用-在客户端(dos窗口)/可视化工具中使用MongoDB基础语句

阿丹&#xff1a; 本文章将描述以及研究mongodb在客户端的基础应用以及在spring-boot中整合使用mongodb来完成基本的数据增删改查。 先放官方的文章 MongoDB CRUD操作 - MongoDB-CN-Manual 本文章分为&#xff1a; 在客户端&#xff08;dos窗口&#xff09;/可视化工具中使用…...

“RISC-V成长日记” blog发布,第一个运行在RISC-V服务器上的blog?

尽管推特、公众号、微博、抖音等社交平台风靡一时&#xff0c;但blog&#xff08;博客&#xff09;在全世界依然经久不衰&#xff0c;尤其是在技术领域。对于博主而言&#xff0c;博客是他们独立创作的天地&#xff0c;可以随时更新内容和故事&#xff0c;确保素材的时效性。此…...

gitlab配置webhook

一.前言 当需要做jenkins的自动化触发构建时&#xff0c;就需要配置gitlab的webhook功能&#xff0c;以下来展示以下如何配置gitlab的webhook&#xff0c;jenkins的配置就不在这里展示了&#xff0c;可以去看我devops文章的完整配置 二.配置 在新版本的gitlab中&#xff0c…...

编译安装Linux内核实践与踩坑

编译安装Linux内核实践与踩坑 1. 参考方案 先留个坑 1. 参考方案 编译安装linux内核ShawnZhong的仓库make mrproper make oldconfig scripts/config --set-str SYSTEM_TRUSTED_KEYS "" KBUILD_BUILD_TIMESTAMP make CC"ccache gcc" -jnproc LOCALVERSION-…...

郑州https数字证书

很多注重隐私的网站都注重网站信息的安全&#xff0c;比如购物网站就需要对客户的账户信息以及支付信息进行安全保护&#xff0c;否则信息泄露&#xff0c;客户与网站都有损失&#xff0c;网站也会因此流失大量客户。而网站使用https证书为客户端与服务器之间传输的信息加了一个…...

第125天:内网安全-隧道技术SMBICMP正反向连接防火墙出入规则上线

知识点 #知识点&#xff1a; 1、入站规则不出网上线方案 2、出站规则不出网上线方案 3、规则-隧道技术-SMB&ICMP-隧道技术&#xff1a;解决不出网协议上线的问题&#xff08;利用出网协议进行封装出网&#xff09; -代理技术&#xff1a;解决网络通讯不通的问题&#xff0…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...