🎉🎉《Spring Boot实战案例合集》目前已更新184个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
在 Spring Boot 开发中,通过事件机制实现组件解耦堪称一大妙招。传统紧密耦合的代码结构,让各模块相互依赖,修改一处往往牵一发而动全身。而 Spring Boot 的事件机制,基于观察者模式,利用 @EventListener 注解,让发布者与监听者各司其职。发布者专注事件触发,无需知晓后续处理逻辑;监听者则按需响应特定事件。如此一来,模块间依赖大幅降低,代码可维护性与扩展性显著提升,能轻松应对复杂多变的业务需求。
本篇文章将详细介绍关于基于注解 @EventListener 在 Spring Boot 中的配置方式以及最佳实践。
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。
public 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", 100, 200)) ;}}
通过上面2个类都可以进行事件的发布,其中ApplicationContext类继承自ApplicationEventPublisher。
2.3 事件监听
public class CreditListener {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)绑定到事务提交阶段,则事件可能在事务完成前立即发出。一旦后续事务回滚,事件却已传播并被消费,将导致业务状态与事件不一致。因此,应使用事务性事件监听器,确保事件仅在事务成功提交后才真正发布,从而保证数据一致性。
// UserServicepublic 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()));}// BusinessServicepublic void create(User user) {this.userService.save(user) ;// 人为制造错误System.err.println(1 / 0) ;}// 事件监听public void createUserEvent(UserEvent event) {System.err.printf("%s - 创建用户【%s】%n", Thread.currentThread().getName(), event.getName()) ;}
我们期望的是只有事务正常提交后才应该监听到事件,但实际如下:
按照期望抛出异常了,事务肯定是回滚的,但是我们还是收到了UserEvent事件
修复,我们只需要将@EventListener修改为@TransactionalEventListener即可。
public void createUserEvent(UserEvent event) {// ...}
再次运行,控制台输出:
2.5 全局异步事件
通过@Async注解对事件监听方法加以标记,可极为便捷地达成事件监听的异步执行效果。然而,当面临数量众多的事件监听场景时,逐个为每个事件监听方法添加@Async注解,不仅操作繁琐,而且代码维护成本也会相应增加。接下来,我们介绍一种全局异步处理事件的办法。如下示例:
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 种终极方案
告别内存溢出!Spring StreamingResponseBody 三大实战案例,性能提升100%
SpringBoot冷门但逆天的5个神级注解,老司机都在偷偷用!
太赞了!AOP弃用@Aspect,一个注解让你的代码灵活十倍!


