SpringBoot 结合 Spring Event 实现事件发布与监听

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,并且在该类中注入 ApplicationEventPublisherBean 对象,可以通过调用这个 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 创建事件发布者

然后,创建一个自定义事件发布者类,并且引入应用事件发布者 ApplicationEventPublisherBean 对象,然后通过该 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-jdbcmybatis-spring-boot-starterspring-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 文件中引入 SpringBootLombok 相关依赖,这里引入 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 点颗星哦~感谢~


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。