使用上 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在一个集合里面循环执行,如果出现异常,需要注意捕获异常处理异常

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

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

相关文章

xcode 的app工程与ffmpeg 4.4版本的静态库联调,ffmpeg内下的断点无法暂停。

先阐述一下我的业务场景&#xff0c;我有一个iOS的app sdk项目&#xff0c;下面简称 A &#xff0c;以及运行 A 的 app 项目&#xff0c;简称 A demo 。 引用关系为 A demo 引用了 A &#xff0c;而 A 引用了 ffmpeg 的静态库&#xff08;.a文件&#xff09;。此时业务出现了 b…

Linux上安装Keepalived,多台Nginx配置Keepalived(保姆级教程)

目录 一、yum安装 第一步&#xff1a;下载 第二步&#xff1a;编辑Keepalived配置文件&#xff08;第一台&#xff09; 第三步&#xff1a;编辑Keepalived配置文件&#xff08;第二台&#xff09; 第四步&#xff1a;我们在本机利用cmd ping一下 一、yum安装 第一步&…

【数据结构篇】手写双向链表、单向链表(超详细)

文章目录 链表1、基本介绍2、单向链表2.1 带头节点的单向链表测试类&#xff1a;链表实现类&#xff1a; 2.2 不带头节点的单向链表2.3 练习测试类&#xff1a;链表实现类&#xff1a; 3、双向链表测试类&#xff1a;双向链表实现类&#xff1a; 4、单向环形链表**测试类**&…

从零开始实现一个 mini-Retrofit 框架

前言 本篇文章将采用循序渐进的编码方式&#xff0c;从零开始实现一个Retorift框架&#xff0c;在实现过程中不断提出问题并分析实现&#xff0c;最终开发出一个mini版的Retrofit框架 演示一个使用OkHttp的项目Demo 为了更好的演示框架的实现过程&#xff0c;这里我先创建了一…

mongodb-win32-x86_64-2008plus-3.4.24-signed.msi

Microsoft Windows [版本 6.1.7601] 版权所有 (c) 2009 Microsoft Corporation。保留所有权利。C:\Users\Administrator>cd C:\MongoDB\Server\3.4\binC:\MongoDB\Server\3.4\bin>C:\MongoDB\Server\3.4\bin>mongod --help Options:General options:-h [ --help ] …

【MySQL】增删查改基础

文章目录 一、创建操作1.1 单行插入1.2 多行插入1.3 插入否则替换更新1.4 替换replace 二、查询操作2.1 select查询2.2 where条件判断2.3 order by排序2.4 limit筛选分页结果 三、更新操作四、删除操作4.1 删除一列4.2 删除整张表数据 五、插入查询结果 CRUD : Create(创建), R…

CS 144 Lab Four 收尾 -- 网络交互全流程解析

CS 144 Lab Four 收尾 -- 网络交互全流程解析 引言Tun/Tap简介tcp_ipv4.cc文件配置信息初始化cs144实现的fd家族体系基于自定义fd体系进行数据读写的adapter适配器体系自定义socket体系自定义事件循环EventLoop模板类TCPSpongeSocket详解listen_and_accept方法_tcp_main方法_in…

【雕爷学编程】Arduino动手做(188)---0.66寸OLED液晶屏模块

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

Source Insight显示行号

1、默认不显示行号 (1)Source Insight默认是不显示行号的&#xff0c;如下图所示。 2、配置显示行号 2.1、方法1 (1)点击View→Line Numbers。 2.2、方法2 (1)空白处鼠标右键&#xff0c;在点击"Line Numbers"。 (2)配置显示行号后如下图所示。

使用hexo进行博客迁移

本文不会从0开始介绍如何通过hexo去搭建一个github page。因为最近折腾了下&#xff0c;发现这玩意儿确实写个博客很费劲&#xff0c;打算把他拖管到github当作我的知识库网站&#xff0c;我的主要文章还是通过mweb写完一键发布到博客园&#xff0c;然后csdn记录一些杂文和思考…

Liunx环境下git的详细使用(gitee版)

Liunx环境下git的详细使用&#xff08;gitee版&#xff09; 1.git是什么2.git操作2.1在gitee创建一个仓库2.2.gitignore2.3.git 3.git三板斧3.1add3.2 commit3.3push 4.git其他命令4.1查看当前仓库状态4.2查看提交日志4.3修改git里面文件名称4.4删除文件4.5修改远端仓库内容 1.…

低碳 Web 实践指南

现状和问题 2023年7月6日&#xff0c;世界迎来有记录以来最热的一天。气候变化是如今人类面临的最大健康威胁。据世界卫生组织预测2030年至2050年期间&#xff0c;气候变化预计每年将造成约25万人死亡。这是人们可以真切感受到的变化&#xff0c;而背后的主要推手是碳排放。 …

Java分布式微服务2——声明式Http客户端(Feign)与网关(Gateway)

文章目录 Http声明式客户端FeignFeign介绍与使用Feign自定义配置Feign性能优化Feign最佳实践方案 网关Gateway网关Gateway的作用与搭建路由断言工厂Route Predicate Factory路由过滤器GatewayFilter全局过滤器过滤器执行顺序网关的跨域处理 Http声明式客户端Feign Feign介绍与…

Python魔法解析:探索变量类型的丰富多彩世界!

在Python这个魔法般的编程语言中&#xff0c;变量是连接你与计算机世界的神奇桥梁。然而&#xff0c;这些变量并不是单一的&#xff0c;它们有着丰富多彩的类型。无论你是刚刚踏入编程的大门&#xff0c;还是想要深入了解Python的高级特性&#xff0c;本篇博客将带你探索变量的…

c++学习(特殊类设计)[30]

只能在堆上创建对象的类 如果你想要确保对象只能在堆上创建&#xff0c;可以通过将析构函数声明为私有&#xff0c;并提供一个静态成员函数来创建对象。这样&#xff0c;类的实例化只能通过调用静态成员函数来完成&#xff0c;而无法直接在栈上创建对象。 以下是一个示例&…

【PCIE】PCIE的驱动和pcie的端口驱动关系

pice驱动和pcie端口驱动区别 PCIe的端口服务驱动与PCIe驱动之间存在一定的关系&#xff0c;但它们是不同的概念。 PCIe驱动是用于管理和操作PCIe设备的驱动程序。它负责与硬件进行通信&#xff0c;并实现对PCIe设备的配置、数据传输以及其他相关操作。PCIe驱动通常涉及设备的…

【NLP概念源和流】 05-引进LSTM网络(第 5/20 部分)

一、说明 在上一篇博客中,我们讨论了原版RNN架构,也讨论了它的局限性。梯度消失是一个非常重要的缺点,它限制了RNN对较短序列的建模。香草 RNN 在相关输入事件和目标信号之间存在超过 5-10 个离散时间步长的时间滞时无法学习。这基本上限制了香草RNN在许多实际问题上的应用,…

NestJs Debug配置文件

&#xff08;事缓则圆,人缓则安,语迟则贵,虎行似病,鹰立似睡。清俞万春《荡寇志》&#xff09; {"version": "0.2.0","configurations": [{"type": "node","request": "launch","name": &quo…

基于LNMP架构搭建Discuz论坛

LNMP: L---->linux系统&#xff0c;操作系统。 N----->nginx网站服务&#xff08;前端),提供前端的静态页面服务。同时具有代理、转发的作用。&#xff08;转发就是转发后端请求&#xff0c;转发PHP&#xff09;&#xff0c;nginx没有处理动态资源的功能&#xff0c;他有…

IO模型-信号驱动IO

linux内核中存在一个信号SIGIO&#xff0c;这个信号就是用于实现信号驱动IO的。当应用程序中想要以信号驱动IO的模型读写硬件数据时&#xff0c;首先注册一个SIGIO信号的信号处理函数,当硬件数据就绪&#xff0c;硬件会发起一个中断&#xff0c;在硬件的中断处理函数中向当前进…