SpringBoot 结合 Spring Event 实现事件发布与监听
文章目录
!版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。
系统环境:
- JAVA JDK 版本: openjdk 17
- SpringBoot 版本: 3.3.2
示例地址:
一、Spring Event 是什么
Spring Event 是 Spring 框架提供的一个基于发布-订阅模式 (Publish-Subscribe Pattern) 的事件处理机制。它基于观察者模式实现,允许应用程序中的组件发布事件,并由其它组件订阅这些事件,从而实现不同组件之间进行解耦的通信,提高了代码的可测试性和可维护性。
二、Spring Event 核心组件
Spring Event 主要有三个关键组成部分:
● ApplicationEvent (应用事件) ApplicationEvent 作为所有自定义事件的基类,扮演着事件信息载体的角色。它本质上是一个简单的 POJO,用于封装与事件相关的一切数据细节。自定义事件可以通过继承此基类,来为事件注入更多与特定事件相关的属性。
● ApplicationEventPublisher (事件发布者)
ApplicationEventPublisher 是事件发布的中枢,负责将事件广播至系统中所有建立了关联关系了的监听器。而且任何持有 ApplicationEventPublisher 实例的 Spring Bean 都能够成为事件的发起点,只需调用实例中的 publishEvent 方法,就可以发布特定类型的事件,从而执行一系列预先设定的逻辑,实现了事件生产与消费的无缝对接。
● ApplicationListener (事件监听器)
ApplicationListener 是事件监听机制的基石,负责捕获并响应发布的 ApplicationEvent 事件。通过实现 ApplicationListener 接口中的 onApplicationEvent 方法,或者使用 @EventListener 注解,可以实现对特定类型的事件进行监听,并且执行特定的逻辑。
三、Spring Event 使用方式
以下是使用 Spring Event 实现事件发布与监听的基本流程,这里通过一个直观的流程示意图来说明其执行流程:
1+-------------------+ +--------------------------+ +--------------------+
2| 事件发布者 | | 事件 (CustomEvent) | | 事件监听器 |
3| (MyEventPublisher)| --> | extends ApplicationEvent | --> | (MyEventListener) |
4+-------------------+ +--------------------------+ +--------------------+
5 | - source |
6 | - message |
7 +--------------------------+
使用 Spring Event 大致遵循以下步骤:
- ① 定义自定义事件类: 首先,根据需求创建一个继承了
ApplicationEvent
类的自定义事件类。比如,在本次示例中创建一个自定义事件类CustomEvent
,这个自定义事件类需要继承 ApplicationEvent 类,并且在类中可以添加一些与特定事件相关的属性。 - ② 创建事件发布者: 然后,创建事件发布者。比如,在本次示例中创建一个事件发布者类
MyEventPublisher
,并且在该类中注入ApplicationEventPublisher
的Bean
对象,可以通过调用这个 Bean 对象中的 publishEvent 方法发布自定义事件。 - ③ 创建事件监听器: 最后,创建事件监听器。比如,在本次示例中创建一个事件监听器类
MyEventListener
,并且在类中实现了事件监听方法,还在方法上添加@EventListener
注解,并指定监听的事件类型为自定义的 CustomEvent 事件,从而实现对 CustomEvent 类型的事件进行监听与处理。
3.1 定义自定义事件类
首先,通过继承应用事件类 ApplicationEvent
来创建一个自定义事件类 CustomEvent
。代码如下:
1import lombok.Getter;
2import lombok.Setter;
3import org.springframework.context.ApplicationEvent;
4
5/**
6 * 定义自定义事件类
7 *
8 * @author mydlq
9 */
10@Setter
11@Getter
12public class CustomEvent extends ApplicationEvent {
13 /**
14 * 事件携带的消息内容
15 */
16 private String message;
17
18 /**
19 * 构造函数
20 * @param source 事件的发起对象(即事件源)
21 * @param message 事件携带的具体消息内容
22 */
23 public CustomEvent(Object source, String message) {
24 super(source);
25 this.message = message;
26 }
27
28}
这里之所以需要继承 ApplicationEvent 类,主要是为了使自定义的事件类融入到 Spring 事件驱动框架中,并且将 ApplicationEvent 作为基类,可以使自定义事件类中包含一些 Spring 定义的跟事件相关的信息,比如事件发布时间、事件源等。
3.2 创建事件发布者
然后,创建一个自定义事件发布者类,并且引入应用事件发布者 ApplicationEventPublisher
的 Bean
对象,然后通过该 Bean
对象发布自定义 CustomEvent
事件。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import jakarta.annotation.Resource;
3import org.springframework.context.ApplicationEventPublisher;
4import org.springframework.stereotype.Component;
5
6/**
7 * 事件发布者
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventPublisher {
13
14 @Resource
15 private ApplicationEventPublisher applicationEventPublisher;
16
17 /**
18 * 发布自定义事件
19 * @param message 事件消息
20 */
21 public void publishEvent(String message) {
22 // 创建自定义事件实例,传入事件源和消息内容
23 CustomEvent event = new CustomEvent(this, message);
24 // 打印日志,标记事件发布动作
25 System.out.println("发布事件: " + message);
26 // 使用 ApplicationEventPublisher 发布事件
27 applicationEventPublisher.publishEvent(event);
28 }
29
30}
3.3 创建事件监听器
最后,创建一个事件监听器类,实现对自定义的 CustomEvent
类型的事件进行监听。不过在给出示例之前,需要先介绍下 @EventListener
注解。
(1) @EventListener 注解
实现事务监听最简单的办法,就是使用注解 @EventListener
来对指定类型的事件进行监听,该注解会自动将事件监听器注册到 Spring 事件驱动框架中,当有事件发布时,会自动触发事件监听器中的事件监听方法,实现对指定事件进行处理。该注解可以配置的参数如下:
- classes 或 value: 指定监听的事件类型,这里可以指定监听一个或多个事件类型。
- condition: 基于 SpEL 表达式设置条件,用于确定事件处理器方法是否应该被执行。只有当表达式的值为 true 时,事件处理方法才会被调用。
(2) 创建自定义事件监听器
接下来,创建一个事件监听器类,然后在类中定义一个事件监听方法,并且在方法上添加 @EventListener
注解,实现对 CustomEvent
类型的事件进行监听。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.event.EventListener;
3import org.springframework.stereotype.Component;
4
5/**
6 * 通过使用 @EventListener 注解来对 CustomEvent 事件进行监听
7 *
8 * @author mydlq
9 */
10@Component
11public class MyEventListener1 {
12
13 /**
14 * 监听 CustomEvent 类型事件
15 */
16 @EventListener
17 public void handleCustomEvent(CustomEvent event) {
18 System.out.println("接收到事件: " + event.getMessage());
19 }
20
21}
(3) 根据指定条件进行监听
在 @EventListener
注解中有一个参数 condition
,该参数可以设置一个 SpEL 表达式,用于确定事件处理器方法是否应该被执行。下面是一个使用示例,当事件消息内容为 hello
时,才会执行事件处理方法。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.event.EventListener;
3import org.springframework.stereotype.Component;
4
5/**
6 * 监听指定条件的事件
7 *
8 * @author mydlq
9 */
10@Component
11public class MyEventListener2 {
12
13 /**
14 * 事件监听 (监听消息内容为'hello'的事件)
15 */
16 @EventListener(condition = "#event.message.equals('hello')")
17 public void handleCustomEvent(CustomEvent event) {
18 System.out.println("接收到事件: " + event.getMessage());
19 }
20
21}
(4) 实现 ApplicationListener 接口进行事件监听
除了使用 @EventListener
注解的方式对事件进行监听外,其实还可以通过实现 ApplicationListener
接口中的 onApplicationEvent
方法来对指定事件进行监听。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.ApplicationListener;
3import org.springframework.stereotype.Component;
4
5/**
6 * 实现 ApplicationListener 接口创建事件监听器,对 CustomEvent 类型事件进行监听
7 *
8 * @author mydlq
9 */
10@Component
11public class MyEventListener3 implements ApplicationListener<CustomEvent> {
12
13 @Override
14 public void onApplicationEvent(CustomEvent event) {
15 System.out.println("接收到事件: " + event.getMessage());
16 }
17
18}
四、Spring Event 异步事件
4.1 Spring Event 异步事件监听
在默认情况下,当事件监听器监听到指定事件后,就会使用事件发布者的线程,使用同步的方式按部就班的执行监听器中的方法。这意味着事件监听器对指定事件进行处理过程中,会占用事件的发布者的线程,来执行事件监听器中对指定事件进行处理的方法,这无疑会使事件发布者在执行过程被堵塞,直到事件处理完成为止。
这种同步模型在执行短时操作时效率很高,但在处理复杂逻辑或执行耗时任务 (如网络请求、文件操作等) 时,可能导致应用程序响应变慢,甚至造成线程阻塞,进而影响整个系统的性能和响应速度。为了避免这种情况,Spring 框架提供了异步事件处理机制,允许事件监听器在独立的线程中执行,从而一定程度上提升了事件处理效率。
4.2 Spring Event 异步事件监听示例
(1) 创建自定义线程池
首先,为了能够实现线程的复用,避免线程被频繁的销毁重建,我们需要在配置类中定义一个自定义线程池的 Bean
对象,并且在配置类上添加 @EnableAsync
注解来启用异步功能。
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.scheduling.annotation.EnableAsync;
4import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
5import java.util.concurrent.ThreadPoolExecutor;
6
7/**
8 * 自定义线程池
9 *
10 * @author mydlq
11 */
12@EnableAsync
13@Configuration
14public class ThreadPoolsConfig {
15
16 /**
17 * 自定义线程池 Bean
18 */
19 @Bean("asyncExecutor")
20 public ThreadPoolTaskExecutor asyncExecutor() {
21 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
22 executor.setThreadNamePrefix("AsyncEventExecutor-");
23 // 核心线程数
24 executor.setCorePoolSize(10);
25 // 最大线程数
26 executor.setMaxPoolSize(20);
27 // 队列容量
28 executor.setQueueCapacity(10000);
29 // 等待时间(秒)
30 executor.setAwaitTerminationSeconds(60);
31 // 应用关闭时等待任务完成
32 executor.setWaitForTasksToCompleteOnShutdown(true);
33 // 拒绝策略
34 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
35 return executor;
36 }
37
38}
(2) 创建异步事件监听器
接下来,创建一个事件监听器类,实现对 CustomEvent
类型的事件进行监听,然后在事件监听方法上添加 @Async
注解,并指定使用自定义的线程池,从而实现事件处理的异步化。这样当事件监听方法监听到指定事件时,就可以从线程池中获取线程,异步的对事件进行处理,从而提高应用的响应速度和吞吐量。
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.event.EventListener;
3import org.springframework.scheduling.annotation.Async;
4import org.springframework.stereotype.Component;
5
6/**
7 * 异步事件监听
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventListener {
13
14 /**
15 * 监听 CustomEvent 类型事件,使用自定义线程池,实现事件异步处理
16 */
17 @Async("asyncExecutor")
18 @EventListener
19 public void handleCustomEvent(CustomEvent event) {
20 System.out.println("接收到事件: " + event.getMessage());
21 }
22
23}
五、Spring Event 事务事件
5.1 Spring Event 事务事件监听
从 Spring 4.2 版本开始,Spring 框架提供了专门用于监听事务事件注解 @TransactionalEventListener
。该注解不仅可以对事务事件进行监听,并且还可以指定监听事务的阶段。比如,可以设置只监听事务提交阶段的事件,也可以设置只监听事务回滚阶段的事件等。通过监听事务事件,可以实现诸如记录日志、资源清理、异步处理等功能,并确保这些操作与事务的生命周期保持一致。
5.2 @TransactionalEventListener 注解参数
注解 @TransactionalEventListener
参数如下:
- classes 或 value: 指定监听的事件类型,可以是一个或多个事件类。
- condition: 基于 SpEL 表达式设置条件,用于确定事件处理器方法是否应该被执行。只有当表达式的值为 true 时,方法才会被调用。
- fallbackOnNoTransaction: 指定在没有事务的情况下是否执行事件处理方法。默认值为 false,即在没有事务的情况下不执行。
- phase: 指定监听器方法在事务的哪个阶段执行。它是支持传入一个 TransactionPhase 枚举类型的值,可能的值包括:
- ① BEFORE_COMPLETION: 在事务提交前调用。
- ② AFTER_COMMIT: 在事务提交后调用(默认)。
- ③ AFTER_ROLLBACK: 在事务回滚后调用。
- ④ AFTER_COMPLETION: 不管事务是提交还是回滚,在事务完成后调用。
5.3 Spring Event 事务事件监听示例
(1) 引入相关依赖
因为 @TransactionalEventListener
注解与事务相关,所以需要引入 Spring 事务依赖 spring-tx,这个依赖在 spring-boot-starter-jdbc、mybatis-spring-boot-starter 和 spring-boot-starter-data-jpa 等包中已经包含,所以一般情况下不需要再单独引入该依赖。
比如,这里在 Maven 的 pom.xml
文件中引入 Mybatis
依赖,配置如下:
1<dependency>
2 <groupId>org.mybatis.spring.boot</groupId>
3 <artifactId>mybatis-spring-boot-starter</artifactId>
4 <version>3.0.3</version>
5</dependency>
(2) 创建事务事件发布者
接下来创建一个事务事件的发布者类,并且在事务方法中发布自定义的 CustomEvent
类型的事件。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import jakarta.annotation.Resource;
3import org.springframework.context.ApplicationEventPublisher;
4import org.springframework.stereotype.Component;
5import org.springframework.transaction.annotation.Transactional;
6
7/**
8 * 事务事件发布者
9 *
10 * @author mydlq
11 */
12@Component
13public class MyEventPublisher {
14
15 @Resource
16 private ApplicationEventPublisher applicationEventPublisher;
17
18 /**
19 * 事务方法中发布事件
20 */
21 @Transactional(rollbackFor = Exception.class)
22 public void publishEvent(String message) {
23 CustomEvent event = new CustomEvent(this, message);
24 applicationEventPublisher.publishEvent(event);
25 System.out.println("发布事件: " + message);
26 }
27
28}
(3) 创建事务事件监听器
最后,创建一个事件监听器类,并且在类中实现对自定义的 CustomEvent
类型的事务事件进行监听,并使用 @TransactionalEventListener
注解来指定监听的事务阶段。代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.stereotype.Component;
3import org.springframework.transaction.event.TransactionPhase;
4import org.springframework.transaction.event.TransactionalEventListener;
5
6/**
7 * 事务事件监听器
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventListener {
13 /**
14 * 在事务提交前的事件进行监听
15 */
16 @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
17 public void handleBeforeCommitEvent(CustomEvent event) {
18 System.out.println("事务提交前进行检查。例如,检查是否满足业务规则。");
19 }
20
21 /**
22 * 在事务提交完成后的事件进行监听
23 */
24 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
25 public void handleAfterCommitEvent(CustomEvent event) {
26 System.out.println("事务提交成功,进行后续处理。例如,记录审计日志、发送通知邮件、修改库存等。");
27 }
28
29 /**
30 * 在事务回滚事件进行监听
31 */
32 @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
33 public void handleAfterRollbackEvent(CustomEvent event) {
34 System.out.println("事务失败回滚,进行后续处理。例如,记录日志、进行重试、发送告警通知等");
35 }
36
37 /**
38 * 在事务完成时进行监听 (无论事务是否执行成功或失败)
39 */
40 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
41 public void handleAfterCompletionEvent(CustomEvent event) {
42 System.out.println("事务执行完成,进行后续处理。例如,清理资源、关闭链接等");
43 }
44}
六、Spring Event 监听顺序
6.1 控制监听顺序的两种方式
同一类型的事件可以被多个监听器进行监听,这时候我们就需要控制事件监听器的执行顺序。常用的方式有两种:
- ⑴ 使用 @Order 注解: 在监听器类或方法上使用
@Order
注解,然后设置优先级,数值越小优先级越高。 - ⑵ 实现 Ordered 接口: 通过实现
Ordered
接口,实现接口中的getOrder()
方法,方法返回的数值就是优先级,并且数值越小优先级越高。
6.2 使用 @Order 注解控制顺序示例
(1) 在方法上使用 @Order 注解控制监听顺序
通过在 方法 上使用 @Order
注解,控制事件的监听顺序,示例代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.event.EventListener;
3import org.springframework.core.annotation.Order;
4import org.springframework.stereotype.Component;
5
6/**
7 * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高)
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventListener {
13
14 /**
15 * 设置执行顺序为第一个执行
16 */
17 @Order(100)
18 @EventListener
19 public void handleCustomEvent1(CustomEvent event) {
20 System.out.println("通过@Order控制执行顺序,第一个执行: " + event.getMessage());
21 }
22
23 /**
24 * 设置执行顺序为第二个执行
25 */
26 @Order(200)
27 @EventListener
28 public void handleCustomEvent2(CustomEvent event) {
29 System.out.println("通过@Order控制执行顺序,第二个执行: " + event.getMessage());
30 }
31
32}
(2) 在类上使用 @Order 注解控制监听顺序
除了可以在方法使用 @Order
注解外,还可以在 类 上使用 @Order
注解,控制事件监听顺序,示例代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.ApplicationListener;
3import org.springframework.core.annotation.Order;
4import org.springframework.stereotype.Component;
5
6/**
7 * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高)
8 * 第一个执行
9 *
10 * @author mydlq
11 */
12@Order(100)
13@Component
14public class MyEventListener1 implements ApplicationListener<CustomEvent> {
15
16 @Override
17 public void onApplicationEvent(CustomEvent event) {
18 System.out.println("通过实现Ordered接口控制下顺序,第一个执行: " + event.getMessage());
19 }
20
21}
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.ApplicationListener;
3import org.springframework.core.annotation.Order;
4import org.springframework.stereotype.Component;
5
6/**
7 * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高)
8 * 第二个执行
9 *
10 * @author mydlq
11 */
12@Order(200)
13@Component
14public class MyEventListener2 implements ApplicationListener<CustomEvent> {
15
16 @Override
17 public void onApplicationEvent(CustomEvent event) {
18 System.out.println("通过实现Ordered接口控制下顺序,第二个执行: " + event.getMessage());
19 }
20
21}
6.3 通过实现 Ordered 接口控制监听顺序示例
除了使用 @Order
注解控制监听顺序外,还可以通过实现 Ordered
接口中的 getOrder
方法来设置优先级,示例代码如下:
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.ApplicationListener;
3import org.springframework.core.Ordered;
4import org.springframework.stereotype.Component;
5
6/**
7 * 通过实现Order接口控制监听顺序,第一个执行的监听器
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventListener1 implements ApplicationListener<CustomEvent>, Ordered {
13
14 @Override
15 public int getOrder() {
16 return 300;
17 }
18
19 @Override
20 public void onApplicationEvent(CustomEvent event) {
21 System.out.println("通过实现Ordered接口控制下顺序,第一个执行: " + event.getMessage());
22 }
23
24}
1import club.mydlq.example.model.CustomEvent;
2import org.springframework.context.ApplicationListener;
3import org.springframework.core.Ordered;
4import org.springframework.stereotype.Component;
5
6/**
7 * 通过实现Order接口控制监听顺序,第二个执行的监听器
8 *
9 * @author mydlq
10 */
11@Component
12public class MyEventListener2 implements ApplicationListener<CustomEvent>, Ordered {
13
14 @Override
15 public int getOrder() {
16 return 400;
17 }
18
19 @Override
20 public void onApplicationEvent(CustomEvent event) {
21 System.out.println("通过实现Ordered接口控制下顺序,第二个执行: " + event.getMessage());
22 }
23
24}
七、Spring Event 应用场景
Spring Event 在实际项目开发中有着广泛的应用场景,它通过事件发布与订阅的模式,促进了系统各组件之间的解耦,增强了系统的灵活性和可维护性。以下是一些典型的应用场景:
- 用户行为触发的事件处理: 如用户登录、注册、密码修改等操作后,可以发布事件通知其他系统组件,比如更新缓存、记录日志、发送欢迎邮件或通知、触发安全审计等。
- 系统状态变化通知: 当系统的某个状态发生变化时,如订单状态从 "待支付" 变为 "已支付",可以发布事件通知库存系统减少库存、通知物流系统准备发货、更新用户账户余额等。
- 异步处理任务: 对于一些耗时的操作,如文件上传后的处理、大数据分析、报表生成等,可以通过发布事件来触发异步任务,避免阻塞主线程,提高系统响应速度。
- 定时任务触发: 虽然 Spring Event 本身不直接用于定时任务,但它可以与定时任务框架(如Spring Schedule或Quartz) 结合,定时发布事件来触发一系列业务逻辑。
- 系统集成与微服务通信: 在微服务架构中,Spring Event 可以作为一种轻量级的内部通信机制,用于服务间的异步消息传递,虽然不如消息队列成熟,但对于简单的内部通信非常有用。
- 日志与监控: 系统可以发布事件来记录关键操作日志,或触发监控报警,例如记录错误日志、性能指标变动、资源使用超标等。
- 权限与安全事件: 当发生权限变更、登录失败尝试过多、敏感操作记录等安全相关事件时,通过事件通知安全模块进行处理,如锁定账户、发送警报或调整访问权限。
- 工作流与业务流程管理: 在复杂的工作流中,每个步骤完成时可以发布事件,以触发下一阶段的任务,如审批流程的下一步骤通知、任务状态的更新等。
Spring Event 的主要使用场景就是实现业务解耦、异步处理以及状态同步等逻辑,能够帮助开发者设计出更加灵活和可扩展的应用系统。
八、Spring Event 最佳实践
在使用和设计 Spring 事件机制时,应遵循一系列最佳实践和注意事项,这对于提升系统的设计质量和可维护性至关重要。下面是使用 Spring 事件机制时需要遵守的一些几个关键点:
① 明确事件类型
当设计事件时,应确保每一个事件都准确反映特定的业务含义。这不仅有助于事件监听器聚焦于具体的业务逻辑,同时也便于理解和维护。
② 利用泛型指定事件类型
实现 ApplicationListener 接口时,推荐使用泛型来明确指定监听的事件类型。这不仅增强代码的类型安全性,减少运行时类型转换错误,而且还能提高代码的可读性和可维护性。
③ 适度使用事件驱动模式
尽管事件驱动模式能够有效实现系统组件间的解耦,但如果过度使用可能会导致系统逻辑变得复杂且难以追踪。因此,应当谨慎选择哪些场景适合使用事件驱动,确保其应用于确实需要异步处理或解耦的场合。
④ 考虑异步事件处理
对于可能造成延迟的操作,如数据库写入、文件I/O、网络通信或邮件发送等,应该优先考虑使用异步事件进行处理。在使用异步事件时,应该实现自定义线程池来配合,从而提升系统的响应速度和整体性能。
⑤ 确保事件处理的幂等性
设计事件处理逻辑时,务必保证其幂等性,即无论事件被处理多少次,最终的结果应当一致。这对于防止重复消费或处理异常重试至关重要。
⑥ 关注事件顺序与一致性
若事件的处理顺序对业务逻辑有直接影响,需格外小心处理事件的发布与消费顺序。对于需要保证顺序的场景,可能需要引入额外的同步机制,或采用能够确保消息顺序的 MQ 中间件。
⑦ 详尽的文档与注释
编写清晰的文档和注释,详细描述每个事件的作用、触发条件及其处理逻辑,对团队协作和系统维护至关重要。这有助于新成员快速理解事件在系统架构中的作用,降低维护成本。
九、Spring Event 示例
这里列举一个完整的 Spring Event 示例作为参考。
9.1 Maven 引入相关依赖
在 pom.xml 文件中引入 SpringBoot
和 Lombok
相关依赖,这里引入 Lombok
的作用主要是为了简化代码。
1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5
6 <parent>
7 <groupId>org.springframework.boot</groupId>
8 <artifactId>spring-boot-starter-parent</artifactId>
9 <version>3.3.2</version>
10 <relativePath/>
11 </parent>
12
13 <groupId>club.mydlq</groupId>
14 <artifactId>springboot-event-example</artifactId>
15 <version>0.0.1</version>
16 <name>springboot-event-example</name>
17 <description>spring event example</description>
18
19 <properties>
20 <java.version>17</java.version>
21 </properties>
22
23 <dependencies>
24 <!--SpringBoot-->
25 <dependency>
26 <groupId>org.springframework.boot</groupId>
27 <artifactId>spring-boot-starter-web</artifactId>
28 </dependency>
29 <!--Lombok-->
30 <dependency>
31 <groupId>org.projectlombok</groupId>
32 <artifactId>lombok</artifactId>
33 <scope>provided</scope>
34 </dependency>
35 <!--Test-->
36 <dependency>
37 <groupId>org.springframework.boot</groupId>
38 <artifactId>spring-boot-starter-test</artifactId>
39 <scope>test</scope>
40 </dependency>
41 </dependencies>
42
43 <build>
44 <plugins>
45 <plugin>
46 <groupId>org.springframework.boot</groupId>
47 <artifactId>spring-boot-maven-plugin</artifactId>
48 </plugin>
49 </plugins>
50 </build>
51
52</project>
9.2 创建自定义线程池
创建一个 自定义线程池,用于异步事件处理过程中使用。
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.scheduling.annotation.EnableAsync;
4import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
5import java.util.concurrent.ThreadPoolExecutor;
6
7/**
8 * 自定义线程池
9 *
10 * @author mydlq
11 */
12@EnableAsync
13@Configuration
14public class ThreadPoolsConfig {
15
16 /**
17 * 自定义线程池 Bean
18 */
19 @Bean("asyncExecutor")
20 public ThreadPoolTaskExecutor getParamTaskExecutor() {
21 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
22 executor.setThreadNamePrefix("AsyncEventExecutor-");
23 // 核心线程数
24 executor.setCorePoolSize(10);
25 // 最大线程数
26 executor.setMaxPoolSize(20);
27 // 队列容量
28 executor.setQueueCapacity(10000);
29 // 等待时间(秒)
30 executor.setAwaitTerminationSeconds(60);
31 // 应用关闭时等待任务完成
32 executor.setWaitForTasksToCompleteOnShutdown(true);
33 // 拒绝策略
34 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
35 return executor;
36 }
37
38}
9.3 创建订单事件类
通过继承 ApplicationEvent
类,创建一个自定义的 订单事件类。
1import lombok.Getter;
2import lombok.Setter;
3import org.springframework.context.ApplicationEvent;
4
5/**
6 * 订单事件
7 *
8 * @author mydlq
9 */
10@Setter
11@Getter
12public class OrderEvent extends ApplicationEvent {
13 /**
14 * 订单ID
15 */
16 private final Long orderId;
17
18 /**
19 * 构造函数
20 * @param source 事件源
21 * @param orderId 订单ID
22 */
23 public OrderEvent(Object source, Long orderId) {
24 super(source);
25 this.orderId = orderId;
26 }
27
28}
9.4 创建订单事件监听器
创建一个 订单事件监听器类,通过 @EventListener
注解监听自定义的 订单事件,并使用 @Async
注解实现对事件进行异步处理。
1import club.mydlq.example.model.OrderEvent;
2import lombok.extern.slf4j.Slf4j;
3import org.springframework.context.event.EventListener;
4import org.springframework.scheduling.annotation.Async;
5import org.springframework.stereotype.Component;
6
7/**
8 * 异步事件监听
9 *
10 * @author mydlq
11 */
12@Slf4j
13@Component
14public class OrderEventListener {
15
16 /**
17 * 监听订单事件,发送邮件 (这里使用 @Async 注解,实现对事件进行异步处理)
18 * @param orderEvent 订单事件
19 */
20 @Async("asyncExecutor")
21 @EventListener
22 public void handleCustomEvent(OrderEvent orderEvent) {
23 System.out.println("监听到订单事件");
24 sendEmail(orderEvent.getOrderId());
25 }
26
27 /**
28 * 发送邮件
29 * @param orderId 订单ID
30 */
31 private void sendEmail(long orderId) {
32 // 模拟发送邮件
33 System.out.println("发送订单通知邮件,订单ID=" + orderId);
34 }
35
36}
9.5 创建订单 Service 接口
创建 订单Service接口,并定义一个处理订单的 processOrder
方法。
1import org.springframework.stereotype.Component;
2
3/**
4 * 订单 Service
5 *
6 * @author mydlq
7 */
8@Component
9public interface OrderService {
10
11 /**
12 * 发布自定义事件
13 * @param orderId 订单ID
14 */
15 void processOrder(Long orderId);
16
17}
9.6 创建订单 Service 实现类
创建 订单Service实现类,并实现 processOrder
方法,用于处理订单,并且发布订单事件。
1import club.mydlq.example.model.OrderEvent;
2import club.mydlq.example.service.OrderService;
3import jakarta.annotation.Resource;
4import org.springframework.context.ApplicationEventPublisher;
5import org.springframework.stereotype.Component;
6
7/**
8 * 订单 Service 实现类
9 *
10 * @author mydlq
11 */
12@Component
13public class OrderServiceImpl implements OrderService {
14
15 @Resource
16 private ApplicationEventPublisher applicationEventPublisher;
17
18 /**
19 * 发布自定义事件
20 * @param orderId 订单ID
21 */
22 @Override
23 public void processOrder(Long orderId) {
24 // 模拟订单处理
25 System.out.println("模拟对订单进行处理...");
26 // 订单处理成功后发布订单事件
27 System.out.println("发布订单事件,订单ID=" + orderId);
28 applicationEventPublisher.publishEvent(new OrderEvent(this, orderId));
29 }
30
31}
9.7 创建订单 Controller 类
创建 订单Controller类,添加一个接口 /api/order
,用于模拟下单操作。
1import club.mydlq.example.service.OrderService;
2import jakarta.annotation.Resource;
3import org.springframework.http.ResponseEntity;
4import org.springframework.web.bind.annotation.PostMapping;
5import org.springframework.web.bind.annotation.RequestMapping;
6import org.springframework.web.bind.annotation.RequestParam;
7import org.springframework.web.bind.annotation.RestController;
8
9/**
10 * 订单 Controller
11 *
12 * @author mydlq
13 */
14@RestController
15@RequestMapping("/api")
16public class OrderController {
17
18 @Resource
19 private OrderService orderService;
20
21 /**
22 * 下单接口
23 * @param orderId 订单ID
24 */
25 @PostMapping("/order")
26 public ResponseEntity<String> order(@RequestParam("orderId") Long orderId) {
27 orderService.processOrder(orderId);
28 return ResponseEntity.ok("success");
29 }
30
31}
9.8 创建 SpringBoot 启动类
创建 SpringBoot 启动类。
1import org.springframework.boot.SpringApplication;
2import org.springframework.boot.autoconfigure.SpringBootApplication;
3
4/**
5 * 启动类
6 *
7 * @author mydlq
8 */
9@SpringBootApplication
10public class Application {
11
12 public static void main(String[] args) {
13 SpringApplication.run(Application.class, args);
14 }
15
16}
9.9 创建订单测试类
创建 订单测试类,并创在类中添加一个用于触发测试的 testOrder
方法,模拟调用下单接口,触发订单事件。
1package club.mydlq.example;
2
3import jakarta.annotation.Resource;
4import org.junit.jupiter.api.Test;
5import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
6import org.springframework.boot.test.context.SpringBootTest;
7import org.springframework.test.web.servlet.MockMvc;
8import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
9
10/**
11 * 订单测试类
12 *
13 * @author mydlq
14 */
15@AutoConfigureMockMvc
16@SpringBootTest(classes = Application.class)
17class OrderTest {
18
19 @Resource
20 private MockMvc mockMvc;
21
22 /**
23 * 模拟调用下单接口,触发订单事件
24 */
25 @Test
26 public void testOrder() throws Exception {
27 mockMvc.perform(post("/api/order").param("orderId", "10001"));
28 }
29
30}
9.10 执行测试类进行测试
启动 OrderTest
测试类,执行测试方法 testOrder
,即可看到控制台输出如下结果:
1模拟对订单进行处理...
2发布订单事件,订单ID=10001
3监听到订单事件
4发送订单通知邮件,订单ID=10001
可以看到通过调用下单接口,进行订单处理,然后触发订单事件,并且通过异步的方法,模拟发送订单通知邮件。
---END---
如果本文对你有帮助,可以关注我的公众号"小豆丁技术栈"了解最新动态,顺便也请帮忙 github 点颗星哦~感谢~
!版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。