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 实现事件发布与监听的基本流程,这里通过一个直观的流程示意图来说明其执行流程:
+-------------------+ +--------------------------+ +--------------------+| 事件发布者 | | 事件 (CustomEvent) | | 事件监听器 || (MyEventPublisher)| --> | extends ApplicationEvent | --> | (MyEventListener) |+-------------------+ +--------------------------+ +--------------------+ | - source | | - message | +--------------------------+使用 Spring Event 大致遵循以下步骤:
- ① 定义自定义事件类: 首先,根据需求创建一个继承了
ApplicationEvent类的自定义事件类。比如,在本次示例中创建一个自定义事件类CustomEvent,这个自定义事件类需要继承 ApplicationEvent 类,并且在类中可以添加一些与特定事件相关的属性。 - ② 创建事件发布者: 然后,创建事件发布者。比如,在本次示例中创建一个事件发布者类
MyEventPublisher,并且在该类中注入ApplicationEventPublisher的Bean对象,可以通过调用这个 Bean 对象中的 publishEvent 方法发布自定义事件。 - ③ 创建事件监听器: 最后,创建事件监听器。比如,在本次示例中创建一个事件监听器类
MyEventListener,并且在类中实现了事件监听方法,还在方法上添加@EventListener注解,并指定监听的事件类型为自定义的 CustomEvent 事件,从而实现对 CustomEvent 类型的事件进行监听与处理。
3.1 定义自定义事件类
首先,通过继承应用事件类 ApplicationEvent 来创建一个自定义事件类 CustomEvent。代码如下:
import lombok.Getter;import lombok.Setter;import org.springframework.context.ApplicationEvent;
/** * 定义自定义事件类 * * @author mydlq */@Setter@Getterpublic class CustomEvent extends ApplicationEvent { /** * 事件携带的消息内容 */ private String message;
/** * 构造函数 * @param source 事件的发起对象(即事件源) * @param message 事件携带的具体消息内容 */ public CustomEvent(Object source, String message) { super(source); this.message = message; }
}这里之所以需要继承 ApplicationEvent 类,主要是为了使自定义的事件类融入到 Spring 事件驱动框架中,并且将 ApplicationEvent 作为基类,可以使自定义事件类中包含一些 Spring 定义的跟事件相关的信息,比如事件发布时间、事件源等。
3.2 创建事件发布者
然后,创建一个自定义事件发布者类,并且引入应用事件发布者 ApplicationEventPublisher 的 Bean 对象,然后通过该 Bean 对象发布自定义 CustomEvent 事件。代码如下:
import club.mydlq.example.model.CustomEvent;import jakarta.annotation.Resource;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Component;
/** * 事件发布者 * * @author mydlq */@Componentpublic class MyEventPublisher {
@Resource private ApplicationEventPublisher applicationEventPublisher;
/** * 发布自定义事件 * @param message 事件消息 */ public void publishEvent(String message) { // 创建自定义事件实例,传入事件源和消息内容 CustomEvent event = new CustomEvent(this, message); // 打印日志,标记事件发布动作 System.out.println("发布事件: " + message); // 使用 ApplicationEventPublisher 发布事件 applicationEventPublisher.publishEvent(event); }
}3.3 创建事件监听器
最后,创建一个事件监听器类,实现对自定义的 CustomEvent 类型的事件进行监听。不过在给出示例之前,需要先介绍下 @EventListener 注解。
(1) @EventListener 注解
实现事务监听最简单的办法,就是使用注解 @EventListener 来对指定类型的事件进行监听,该注解会自动将事件监听器注册到 Spring 事件驱动框架中,当有事件发布时,会自动触发事件监听器中的事件监听方法,实现对指定事件进行处理。该注解可以配置的参数如下:
- classes 或 value: 指定监听的事件类型,这里可以指定监听一个或多个事件类型。
- condition: 基于 SpEL 表达式设置条件,用于确定事件处理器方法是否应该被执行。只有当表达式的值为 true 时,事件处理方法才会被调用。
(2) 创建自定义事件监听器
接下来,创建一个事件监听器类,然后在类中定义一个事件监听方法,并且在方法上添加 @EventListener 注解,实现对 CustomEvent 类型的事件进行监听。代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Component;
/** * 通过使用 @EventListener 注解来对 CustomEvent 事件进行监听 * * @author mydlq */@Componentpublic class MyEventListener1 {
/** * 监听 CustomEvent 类型事件 */ @EventListener public void handleCustomEvent(CustomEvent event) { System.out.println("接收到事件: " + event.getMessage()); }
}(3) 根据指定条件进行监听
在 @EventListener 注解中有一个参数 condition,该参数可以设置一个 SpEL 表达式,用于确定事件处理器方法是否应该被执行。下面是一个使用示例,当事件消息内容为 hello 时,才会执行事件处理方法。代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Component;
/** * 监听指定条件的事件 * * @author mydlq */@Componentpublic class MyEventListener2 {
/** * 事件监听 (监听消息内容为'hello'的事件) */ @EventListener(condition = "#event.message.equals('hello')") public void handleCustomEvent(CustomEvent event) { System.out.println("接收到事件: " + event.getMessage()); }
}(4) 实现 ApplicationListener 接口进行事件监听
除了使用 @EventListener 注解的方式对事件进行监听外,其实还可以通过实现 ApplicationListener 接口中的 onApplicationEvent 方法来对指定事件进行监听。代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.ApplicationListener;import org.springframework.stereotype.Component;
/** * 实现 ApplicationListener 接口创建事件监听器,对 CustomEvent 类型事件进行监听 * * @author mydlq */@Componentpublic class MyEventListener3 implements ApplicationListener<CustomEvent> {
@Override public void onApplicationEvent(CustomEvent event) { System.out.println("接收到事件: " + event.getMessage()); }
}四、Spring Event 异步事件
4.1 Spring Event 异步事件监听
在默认情况下,当事件监听器监听到指定事件后,就会使用事件发布者的线程,使用同步的方式按部就班的执行监听器中的方法。这意味着事件监听器对指定事件进行处理过程中,会占用事件的发布者的线程,来执行事件监听器中对指定事件进行处理的方法,这无疑会使事件发布者在执行过程被堵塞,直到事件处理完成为止。
这种同步模型在执行短时操作时效率很高,但在处理复杂逻辑或执行耗时任务 (如网络请求、文件操作等) 时,可能导致应用程序响应变慢,甚至造成线程阻塞,进而影响整个系统的性能和响应速度。为了避免这种情况,Spring 框架提供了异步事件处理机制,允许事件监听器在独立的线程中执行,从而一定程度上提升了事件处理效率。
4.2 Spring Event 异步事件监听示例
(1) 创建自定义线程池
首先,为了能够实现线程的复用,避免线程被频繁的销毁重建,我们需要在配置类中定义一个自定义线程池的 Bean 对象,并且在配置类上添加 @EnableAsync 注解来启用异步功能。
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;
/** * 自定义线程池 * * @author mydlq */@EnableAsync@Configurationpublic class ThreadPoolsConfig {
/** * 自定义线程池 Bean */ @Bean("asyncExecutor") public ThreadPoolTaskExecutor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("AsyncEventExecutor-"); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(20); // 队列容量 executor.setQueueCapacity(10000); // 等待时间(秒) executor.setAwaitTerminationSeconds(60); // 应用关闭时等待任务完成 executor.setWaitForTasksToCompleteOnShutdown(true); // 拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; }
}(2) 创建异步事件监听器
接下来,创建一个事件监听器类,实现对 CustomEvent 类型的事件进行监听,然后在事件监听方法上添加 @Async 注解,并指定使用自定义的线程池,从而实现事件处理的异步化。这样当事件监听方法监听到指定事件时,就可以从线程池中获取线程,异步的对事件进行处理,从而提高应用的响应速度和吞吐量。
import club.mydlq.example.model.CustomEvent;import org.springframework.context.event.EventListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;
/** * 异步事件监听 * * @author mydlq */@Componentpublic class MyEventListener {
/** * 监听 CustomEvent 类型事件,使用自定义线程池,实现事件异步处理 */ @Async("asyncExecutor") @EventListener public void handleCustomEvent(CustomEvent event) { System.out.println("接收到事件: " + event.getMessage()); }
}五、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 依赖,配置如下:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version></dependency>(2) 创建事务事件发布者
接下来创建一个事务事件的发布者类,并且在事务方法中发布自定义的 CustomEvent 类型的事件。代码如下:
import club.mydlq.example.model.CustomEvent;import jakarta.annotation.Resource;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;
/** * 事务事件发布者 * * @author mydlq */@Componentpublic class MyEventPublisher {
@Resource private ApplicationEventPublisher applicationEventPublisher;
/** * 事务方法中发布事件 */ @Transactional(rollbackFor = Exception.class) public void publishEvent(String message) { CustomEvent event = new CustomEvent(this, message); applicationEventPublisher.publishEvent(event); System.out.println("发布事件: " + message); }
}(3) 创建事务事件监听器
最后,创建一个事件监听器类,并且在类中实现对自定义的 CustomEvent 类型的事务事件进行监听,并使用 @TransactionalEventListener 注解来指定监听的事务阶段。代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.stereotype.Component;import org.springframework.transaction.event.TransactionPhase;import org.springframework.transaction.event.TransactionalEventListener;
/** * 事务事件监听器 * * @author mydlq */@Componentpublic class MyEventListener { /** * 在事务提交前的事件进行监听 */ @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleBeforeCommitEvent(CustomEvent event) { System.out.println("事务提交前进行检查。例如,检查是否满足业务规则。"); }
/** * 在事务提交完成后的事件进行监听 */ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void handleAfterCommitEvent(CustomEvent event) { System.out.println("事务提交成功,进行后续处理。例如,记录审计日志、发送通知邮件、修改库存等。"); }
/** * 在事务回滚事件进行监听 */ @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void handleAfterRollbackEvent(CustomEvent event) { System.out.println("事务失败回滚,进行后续处理。例如,记录日志、进行重试、发送告警通知等"); }
/** * 在事务完成时进行监听 (无论事务是否执行成功或失败) */ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) public void handleAfterCompletionEvent(CustomEvent event) { System.out.println("事务执行完成,进行后续处理。例如,清理资源、关闭链接等"); }}六、Spring Event 监听顺序
6.1 控制监听顺序的两种方式
同一类型的事件可以被多个监听器进行监听,这时候我们就需要控制事件监听器的执行顺序。常用的方式有两种:
- ⑴ 使用 @Order 注解: 在监听器类或方法上使用
@Order注解,然后设置优先级,数值越小优先级越高。 - ⑵ 实现 Ordered 接口: 通过实现
Ordered接口,实现接口中的getOrder()方法,方法返回的数值就是优先级,并且数值越小优先级越高。
6.2 使用 @Order 注解控制顺序示例
(1) 在方法上使用 @Order 注解控制监听顺序
通过在 方法 上使用 @Order 注解,控制事件的监听顺序,示例代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.event.EventListener;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
/** * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高) * * @author mydlq */@Componentpublic class MyEventListener {
/** * 设置执行顺序为第一个执行 */ @Order(100) @EventListener public void handleCustomEvent1(CustomEvent event) { System.out.println("通过@Order控制执行顺序,第一个执行: " + event.getMessage()); }
/** * 设置执行顺序为第二个执行 */ @Order(200) @EventListener public void handleCustomEvent2(CustomEvent event) { System.out.println("通过@Order控制执行顺序,第二个执行: " + event.getMessage()); }
}(2) 在类上使用 @Order 注解控制监听顺序
除了可以在方法使用 @Order 注解外,还可以在 类 上使用 @Order 注解,控制事件监听顺序,示例代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.ApplicationListener;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
/** * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高) * 第一个执行 * * @author mydlq */@Order(100)@Componentpublic class MyEventListener1 implements ApplicationListener<CustomEvent> {
@Override public void onApplicationEvent(CustomEvent event) { System.out.println("通过实现Ordered接口控制下顺序,第一个执行: " + event.getMessage()); }
}import club.mydlq.example.model.CustomEvent;import org.springframework.context.ApplicationListener;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;
/** * 通过使用 @Order 注解控制监听顺序(数值越小,优先级越高) * 第二个执行 * * @author mydlq */@Order(200)@Componentpublic class MyEventListener2 implements ApplicationListener<CustomEvent> {
@Override public void onApplicationEvent(CustomEvent event) { System.out.println("通过实现Ordered接口控制下顺序,第二个执行: " + event.getMessage()); }
}6.3 通过实现 Ordered 接口控制监听顺序示例
除了使用 @Order 注解控制监听顺序外,还可以通过实现 Ordered 接口中的 getOrder 方法来设置优先级,示例代码如下:
import club.mydlq.example.model.CustomEvent;import org.springframework.context.ApplicationListener;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;
/** * 通过实现Order接口控制监听顺序,第一个执行的监听器 * * @author mydlq */@Componentpublic class MyEventListener1 implements ApplicationListener<CustomEvent>, Ordered {
@Override public int getOrder() { return 300; }
@Override public void onApplicationEvent(CustomEvent event) { System.out.println("通过实现Ordered接口控制下顺序,第一个执行: " + event.getMessage()); }
}import club.mydlq.example.model.CustomEvent;import org.springframework.context.ApplicationListener;import org.springframework.core.Ordered;import org.springframework.stereotype.Component;
/** * 通过实现Order接口控制监听顺序,第二个执行的监听器 * * @author mydlq */@Componentpublic class MyEventListener2 implements ApplicationListener<CustomEvent>, Ordered {
@Override public int getOrder() { return 400; }
@Override public void onApplicationEvent(CustomEvent event) { System.out.println("通过实现Ordered接口控制下顺序,第二个执行: " + event.getMessage()); }
}七、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 的作用主要是为了简化代码。
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.2</version> <relativePath/> </parent>
<groupId>club.mydlq</groupId> <artifactId>springboot-event-example</artifactId> <version>0.0.1</version> <name>springboot-event-example</name> <description>spring event example</description>
<properties> <java.version>17</java.version> </properties>
<dependencies> <!--SpringBoot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!--Test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>9.2 创建自定义线程池
创建一个 自定义线程池,用于异步事件处理过程中使用。
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;
/** * 自定义线程池 * * @author mydlq */@EnableAsync@Configurationpublic class ThreadPoolsConfig {
/** * 自定义线程池 Bean */ @Bean("asyncExecutor") public ThreadPoolTaskExecutor getParamTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("AsyncEventExecutor-"); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(20); // 队列容量 executor.setQueueCapacity(10000); // 等待时间(秒) executor.setAwaitTerminationSeconds(60); // 应用关闭时等待任务完成 executor.setWaitForTasksToCompleteOnShutdown(true); // 拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; }
}9.3 创建订单事件类
通过继承 ApplicationEvent 类,创建一个自定义的 订单事件类。
import lombok.Getter;import lombok.Setter;import org.springframework.context.ApplicationEvent;
/** * 订单事件 * * @author mydlq */@Setter@Getterpublic class OrderEvent extends ApplicationEvent { /** * 订单ID */ private final Long orderId;
/** * 构造函数 * @param source 事件源 * @param orderId 订单ID */ public OrderEvent(Object source, Long orderId) { super(source); this.orderId = orderId; }
}9.4 创建订单事件监听器
创建一个 订单事件监听器类,通过 @EventListener 注解监听自定义的 订单事件,并使用 @Async 注解实现对事件进行异步处理。
import club.mydlq.example.model.OrderEvent;import lombok.extern.slf4j.Slf4j;import org.springframework.context.event.EventListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;
/** * 异步事件监听 * * @author mydlq */@Slf4j@Componentpublic class OrderEventListener {
/** * 监听订单事件,发送邮件 (这里使用 @Async 注解,实现对事件进行异步处理) * @param orderEvent 订单事件 */ @Async("asyncExecutor") @EventListener public void handleCustomEvent(OrderEvent orderEvent) { System.out.println("监听到订单事件"); sendEmail(orderEvent.getOrderId()); }
/** * 发送邮件 * @param orderId 订单ID */ private void sendEmail(long orderId) { // 模拟发送邮件 System.out.println("发送订单通知邮件,订单ID=" + orderId); }
}9.5 创建订单 Service 接口
创建 订单Service接口,并定义一个处理订单的 processOrder 方法。
import org.springframework.stereotype.Component;
/** * 订单 Service * * @author mydlq */@Componentpublic interface OrderService {
/** * 发布自定义事件 * @param orderId 订单ID */ void processOrder(Long orderId);
}9.6 创建订单 Service 实现类
创建 订单Service实现类,并实现 processOrder 方法,用于处理订单,并且发布订单事件。
import club.mydlq.example.model.OrderEvent;import club.mydlq.example.service.OrderService;import jakarta.annotation.Resource;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Component;
/** * 订单 Service 实现类 * * @author mydlq */@Componentpublic class OrderServiceImpl implements OrderService {
@Resource private ApplicationEventPublisher applicationEventPublisher;
/** * 发布自定义事件 * @param orderId 订单ID */ @Override public void processOrder(Long orderId) { // 模拟订单处理 System.out.println("模拟对订单进行处理..."); // 订单处理成功后发布订单事件 System.out.println("发布订单事件,订单ID=" + orderId); applicationEventPublisher.publishEvent(new OrderEvent(this, orderId)); }
}9.7 创建订单 Controller 类
创建 订单Controller类,添加一个接口 /api/order,用于模拟下单操作。
import club.mydlq.example.service.OrderService;import jakarta.annotation.Resource;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
/** * 订单 Controller * * @author mydlq */@RestController@RequestMapping("/api")public class OrderController {
@Resource private OrderService orderService;
/** * 下单接口 * @param orderId 订单ID */ @PostMapping("/order") public ResponseEntity<String> order(@RequestParam("orderId") Long orderId) { orderService.processOrder(orderId); return ResponseEntity.ok("success"); }
}9.8 创建 SpringBoot 启动类
创建 SpringBoot 启动类。
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
/** * 启动类 * * @author mydlq */@SpringBootApplicationpublic class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}9.9 创建订单测试类
创建 订单测试类,并创在类中添加一个用于触发测试的 testOrder 方法,模拟调用下单接口,触发订单事件。
package club.mydlq.example;
import jakarta.annotation.Resource;import org.junit.jupiter.api.Test;import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
/** * 订单测试类 * * @author mydlq */@AutoConfigureMockMvc@SpringBootTest(classes = Application.class)class OrderTest {
@Resource private MockMvc mockMvc;
/** * 模拟调用下单接口,触发订单事件 */ @Test public void testOrder() throws Exception { mockMvc.perform(post("/api/order").param("orderId", "10001")); }
}9.10 执行测试类进行测试
启动 OrderTest 测试类,执行测试方法 testOrder,即可看到控制台输出如下结果:
模拟对订单进行处理...发布订单事件,订单ID=10001监听到订单事件发送订单通知邮件,订单ID=10001可以看到通过调用下单接口,进行订单处理,然后触发订单事件,并且通过异步的方法,模拟发送订单通知邮件。
