Loading
Loading...

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,并且在该类中注入 ApplicationEventPublisherBean 对象,可以通过调用这个 Bean 对象中的 publishEvent 方法发布自定义事件。
  • ③ 创建事件监听器: 最后,创建事件监听器。比如,在本次示例中创建一个事件监听器类 MyEventListener,并且在类中实现了事件监听方法,还在方法上添加 @EventListener 注解,并指定监听的事件类型为自定义的 CustomEvent 事件,从而实现对 CustomEvent 类型的事件进行监听与处理。

3.1 定义自定义事件类

首先,通过继承应用事件类 ApplicationEvent 来创建一个自定义事件类 CustomEvent。代码如下:

import lombok.Getter;
import lombok.Setter;
import org.springframework.context.ApplicationEvent;
/**
* 定义自定义事件类
*
* @author mydlq
*/
@Setter
@Getter
public 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 创建事件发布者

然后,创建一个自定义事件发布者类,并且引入应用事件发布者 ApplicationEventPublisherBean 对象,然后通过该 Bean 对象发布自定义 CustomEvent 事件。代码如下:

import club.mydlq.example.model.CustomEvent;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
/**
* 事件发布者
*
* @author mydlq
*/
@Component
public 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
*/
@Component
public 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
*/
@Component
public 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
*/
@Component
public 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
@Configuration
public 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
*/
@Component
public 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-jdbcmybatis-spring-boot-starterspring-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
*/
@Component
public 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
*/
@Component
public 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
*/
@Component
public 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)
@Component
public 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)
@Component
public 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
*/
@Component
public 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
*/
@Component
public 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 文件中引入 SpringBootLombok 相关依赖,这里引入 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
@Configuration
public 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
@Getter
public 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
@Component
public 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
*/
@Component
public 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
*/
@Component
public 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
*/
@SpringBootApplication
public 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,即可看到控制台输出如下结果:

Terminal window
模拟对订单进行处理...
发布订单事件,订单ID=10001
监听到订单事件
发送订单通知邮件,订单ID=10001

可以看到通过调用下单接口,进行订单处理,然后触发订单事件,并且通过异步的方法,模拟发送订单通知邮件。

---END---
如果本文对你有帮助,可以关注我的公众号 "小豆丁技术栈" 了解最新动态,顺便也请帮忙 Github 点颗星哦,感谢~

本文作者:超级小豆丁 @ 小豆丁技术栈

本文链接:http://www.mydlq.club/article/135/

本文标题:SpringBoot 结合 Spring Event 实现事件发布与监听

本文版权:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!