大数跨境
0
0

告别紧耦合!用 @EventListener 打造灵活的 Spring Boot 应用

告别紧耦合!用 @EventListener 打造灵活的 Spring Boot 应用 Spring全家桶实战案例
2025-10-22
0
导读:告别紧耦合!用 @EventListener 打造灵活的 Spring Boot 应用
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!

🎉🎉《Spring Boot实战案例合集》目前已更新184个案例,我们将持续不断的更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

→ 现在就订阅合集

环境:SpringBoot3.4.2



1. 简介

在 Spring Boot 开发中,通过事件机制实现组件解耦堪称一大妙招。传统紧密耦合的代码结构,让各模块相互依赖,修改一处往往牵一发而动全身。而 Spring Boot 的事件机制,基于观察者模式,利用 @EventListener 注解,让发布者与监听者各司其职。发布者专注事件触发,无需知晓后续处理逻辑;监听者则按需响应特定事件。如此一来,模块间依赖大幅降低,代码可维护性与扩展性显著提升,能轻松应对复杂多变的业务需求。

本篇文章将详细介绍关于基于注解 @EventListener 在 Spring Boot 中的配置方式以及最佳实践。

2.实战案例

2.1 创建事件对象

public class CreditScoreChangedEvent {
  private final String customerId;  private final int oldScore;  private final int newScore;  public CreditScoreChangedEvent(String customerId, int oldScore, int newScore) {    this.customerId = customerId;    this.oldScore = oldScore;    this.newScore = newScore;  }  // getters}

注意,从Spring 4.2 开始,事件对象不需要继承ApplicationEvent。在内部,会判断当前发布的对象是否是ApplicationEvent类型,如果不是会自动包装为 PayloadApplicationEvent 类型。

2.2 发布事件

通常事件的发布会在Service中,而发布事件的方式我们可以通过:ApplicationEventPublisher 或 ApplicationContext。

@Servicepublic class CreditService {  private final ApplicationEventPublisher eventPublisher ;  private final ApplicationContext context ;  public CreditService(ApplicationEventPublisher eventPublisher, ApplicationContext context) {    this.eventPublisher = eventPublisher;    this.context = context;  }  public void scoreEvent() {//    this.eventPublisher.publishEvent(new CreditScoreChangedEvent("p001", 100, 200)) ;    this.context.publishEvent(new CreditScoreChangedEvent("p001"100200)) ;  }}

通过上面2个类都可以进行事件的发布,其中ApplicationContext类继承自ApplicationEventPublisher。

2.3 事件监听

@Componentpublic class CreditListener {  @Async  @EventListener  public void scoreListener(CreditScoreChangedEvent event) {    System.err.printf("%s - 接收到事件: %s%n"Thread.currentThread().getName(), event) ;  }}

开启异步支持

@Configuration@EnableAsyncpublic class AppConfig {}

最终控制台输出如下:

task-1 - 接收到事件: com.pack.events.CreditScoreChangedEvent@74f2d4b3

2.4 异步事件&事务

当事件与事务相集合时,若事件发布未通过事务钩子(如@TransactionalEventListener)绑定到事务提交阶段,则事件可能在事务完成前立即发出。一旦后续事务回滚,事件却已传播并被消费,将导致业务状态与事件不一致。因此,应使用事务性事件监听器,确保事件仅在事务成功提交后才真正发布,从而保证数据一致性。

// UserService@Transactionalpublic void save(User user) {  this.jdbcClient.sql("insert into t_user (name, age, email) values (?, ?, ?)")    .param(1, user.getName())    .param(2, user.getAge())    .param(3, user.getEmail()).update() ; this.context.publishEvent(new UserEvent(user.getName())); }// BusinessService@Transactionalpublic void create(User user) {  this.userService.save(user) ;  // 人为制造错误  System.err.println(1 / 0) ;}// 事件监听@Async@EventListenerpublic void createUserEvent(UserEvent event) {  System.err.printf("%s - 创建用户【%s】%n"Thread.currentThread().getName(), event.getName()) ;}

我们期望的是只有事务正常提交后才应该监听到事件,但实际如下:

按照期望抛出异常了,事务肯定是回滚的,但是我们还是收到了UserEvent事件

修复,我们只需要将@EventListener修改为@TransactionalEventListener即可。

@Async@TransactionalEventListenerpublic void createUserEvent(UserEvent event) {  // ...}

再次运行,控制台输出:

2.5 全局异步事件

通过@Async注解对事件监听方法加以标记,可极为便捷地达成事件监听的异步执行效果。然而,当面临数量众多的事件监听场景时,逐个为每个事件监听方法添加@Async注解,不仅操作繁琐,而且代码维护成本也会相应增加。接下来,我们介绍一种全局异步处理事件的办法。如下示例:

@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)ApplicationEventMulticaster eventMulticaster(ConfigurableListableBeanFactory beanFactory) {  SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(beanFactory) ;  ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor() ;  taskExecutor.setCorePoolSize(10) ;  taskExecutor.setMaxPoolSize(10) ;  taskExecutor.setThreadNamePrefix("pack-event-") ;  taskExecutor.afterPropertiesSet() ;  multicaster.setTaskExecutor(taskExecutor) ;  return multicaster ;}

如上配置后,事件监听器将在上面配置的线程池中执行。

注意:从Spring 6.1版本开始,不支持事务监听器在异步线程中执行。

测试

// 1.@TransactionalEventListener// 2.@EventListenerpublic void createUserEvent(UserEvent event) {  System.err.printf("%s - 创建用户【%s】%n", Thread.currentThread().getName(), event.getName()) ;}

分别使用上面的2个注解测试,分别输出如下:



以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

只会用JPA中的save方法?Spring Boot 中隐藏的10个强大方法

强大!Spring Boot 使用强大的@Formula注解简化查询

强大!Spring Boot 通过6大核心技术,轻松实现请求数据修改

项目亮点!Spring Boot 多线程事务一致性方案,支持JDBC,MyBatis,JPA

别只会@Aspect!Spring Boot 代理的8种逆天实现,满足各种场景

9种手段!优化 Spring Boot 启动速度,最后一种毫秒级

干掉它!Spring Boot 忽略 JSON 字段的 6 种终极方案

太强了!用SQL实现Excel文件的CRUD操作

告别内存溢出!Spring StreamingResponseBody 三大实战案例,性能提升100%

请不要自己写!Spring Boot非常实用的内置功能

SpringBoot冷门但逆天的5个神级注解,老司机都在偷偷用!

太赞了!AOP弃用@Aspect,一个注解让你的代码灵活十倍!

图片
图片
图片
图片
图片
图片
图片
图片
图片

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832