当前位置: 首页 > news >正文

Spring 的事件机制你用了三年,但 @TransactionalEventListener 的坑一个都没绕过去

Spring 的事件机制你用了三年,但 @TransactionalEventListener 的坑一个都没绕过去

事情是这样的:用户下单后要发短信通知。我在 OrderService 里写了个@EventListener

```java @Service public class OrderService {

@Autowired private ApplicationEventPublisher publisher; @Transactional public void createOrder(OrderDTO dto) { Order order = orderRepo.save(dto.toEntity()); publisher.publishEvent(new OrderCreatedEvent(order)); // 发短信 smsService.send(order.getUserPhone(), "下单成功"); }

} ```

测试环境一切正常。上线当天,客服电话被打爆——"我下单成功了但没收到短信"。

查日志发现,publishEvent是同步执行的,在事务提交前就发了短信。如果事务回滚了(比如库存扣减失败),短信已经发出去了——用户收到了"下单成功"但订单根本没创建。

我去掉publishEvent,直接在事务提交后调smsService。看起来解决了。然后产品说"下单成功还要发 App Push、记录用户行为、更新推荐算法"。我写了三个调用。又过一周,产品说"VIP 用户要多发一封邮件"。我又加一个。

到此为止,OrderService.createOrder() 里有 5 个非核心的副作用调用,每个都可能抛异常阻塞主流程。这就是所谓的观察者模式缺位导致的代码腐化

观察者模式的正确姿势:不是你写个 Listener 就叫观察者了

很多 Java 工程师觉得"我用了 @EventListener 就是用了观察者模式",但实际情况是 90% 的人都在误用。

观察者模式的核心契约是这个:Subject 不应该知道 Observer 是谁,Observer 也不应该影响 Subject 的主流程。

对照这两个标准,上面那个例子两样都违反了: - OrderService 直接调用 smsService,知道 Observer 是谁 - Observer 的异常会阻断下单主流程

Spring 的@TransactionalEventListener就是为这个场景设计的:

```java @Service public class OrderService {

@Autowired private ApplicationEventPublisher publisher; @Transactional public void createOrder(OrderDTO dto) { Order order = orderRepo.save(dto.toEntity()); publisher.publishEvent(new OrderCreatedEvent(order)); // 代码到此为止。其他副作用由 Listener 负责 }

}

@Component public class OrderNotificationListener {

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onOrderCreated(OrderCreatedEvent event) { // 事务提交后才执行,不会在回滚时误发 smsService.send(event.getOrder().getUserPhone(), "下单成功"); }

}

@Component public class OrderAnalyticsListener {

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Async // 异步执行,不阻塞主流程 public void onOrderCreated(OrderCreatedEvent event) { analyticsService.record("order_created", event.getOrder().getId()); }

} ```

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)保证了 Listener 只在事务成功提交后执行。如果事务回滚,Listener 根本不会被触发。

加上@Async,分析埋点这类非关键操作就不会拖慢下单接口的响应时间。

你以为这就完了?生产环境会教做人的

坑一:AFTER_COMMIT 不是 AFTER_COMPLETION

```java // 事务提交成功 → 执行 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)

// 事务结束(无论提交还是回滚)→ 都会执行 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION) ```

大多数业务通知应该用 AFTER_COMMIT——只有真正写库成功了才发。但如果你要在回滚时发一个"下单失败"的消息,就得用 AFTER_COMPLETION 配合判断事务状态。

另外,AFTER_COMMIT 的 Listener 如果自己抛了异常,不会回滚主事务——因为主事务已经提交了。也就是说,发短信失败不会让订单回滚。这是你想要的吗?不一定。如果你的业务要求"短信发不出去订单就不能算完成",那 AFTER_COMMIT 就不适合——你得回到事务内同步调用。

坑二:@Async 的线程池不隔离,慢任务拖死整个系统

Spring 默认的@Async线程池是SimpleAsyncTaskExecutor——这玩意每次创建一个新线程,没有上限。高并发下直接 OOM。

你必须自己配置线程池:

```java @Configuration @EnableAsync public class AsyncConfig {

@Bean("eventExecutor") public Executor eventExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(20); executor.setQueueCapacity(100); executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy() // 满了让调用线程执行 ); executor.setThreadNamePrefix("event-"); executor.initialize(); return executor; }

}

// Listener 指定线程池 @Async("eventExecutor") @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onOrderCreated(OrderCreatedEvent event) { // ... } ```

这里还有一个容易被忽略的细节:CallerRunsPolicy——当队列满了,任务由发布事件的线程(也就是你的 HTTP 线程)自己执行。这听起来会让接口变慢,但比直接丢弃任务导致数据丢失要好。取舍在于你的业务对延迟的容忍度。

坑三:异步事件里拿不到 HttpServletRequest

这应该是踩坑率最高的问题了:

java // 异步事件监听器 @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void onOrderCreated(OrderCreatedEvent event) { // ❌ 异步线程里这玩意儿是 null HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); }

因为@Async在新线程执行,RequestContextHolder 是基于 ThreadLocal 的,新线程里没有。解决方案:在事件里携带需要的上下文,而不是在 Listener 里现取。

```java public class OrderCreatedEvent { private final Order order; private final String clientIp; // 从 request 里取出放到事件里 private final String userAgent;

public OrderCreatedEvent(Order order, HttpServletRequest request) { this.order = order; this.clientIp = request.getRemoteAddr(); this.userAgent = request.getHeader("User-Agent"); }

} ```

事件的职责不只是"通知",还应该携带 Observer 需要的全部数据。

坑四:事件顺序没有保证

@EventListener@TransactionalEventListener的多个 Listener 之间,执行顺序是不确定的。

如果 A Listener 必须比 B Listener 先执行(比如先更新缓存再发消息),你得用@Order注解:

```java @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Order(1) public void updateCache(OrderCreatedEvent event) { ... }

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) @Order(2) public void notifyMq(OrderCreatedEvent event) { ... } ```

但如果有多层依赖关系,@Order就会变得脆弱。更好的方案是:用消息队列替代 Spring Event 做跨服务的事件传递。Spring Event 适合进程内的轻量级解耦,跨服务的事件驱动还是交给 MQ 更靠谱。

什么时候不该用观察者模式

Spring Event 好用到容易滥用。有几个场景应该克制:

场景一:需要强一致性的操作。比如"创建订单 + 扣库存"——这不适合用事件解耦,因为扣库存失败必须回滚订单。这类操作应该在同一事务内完成。

场景二:事件数量爆炸。一个操作发布 20 个事件,每个事件有 3 个 Listener,你根本追踪不到整个调用链。与其用事件满天飞,不如回归到明确的流程编排(中介者模式/编排器)。

场景三:只有两个组件通信。如果 A 只需要通知 B,直接调用比绕一层事件更清晰。观察者模式的价值在 Observer 数量 ≥ 3 时才真正体现出来。

实际经验总结

Spring Event 机制是观察者模式的工程化实现,但它不是银弹。

正确用法:@TransactionalEventListener(AFTER_COMMIT)+ 自定义线程池 + 事件携带完整上下文 +@Order控制顺序。

但一旦你发现自己在写第 10 个@EventListener,就该停下来想一想了——你是不是在用事件机制逃避架构设计?把正常的流程编排拆成一堆离散的 Listener,除了让调用链不可追踪之外,没有任何好处。

观察者模式解决的是"Subject 不依赖 Observer"的问题,不是"我不知道自己代码在干嘛"的问题。

我正在做一个小程序叫「爪爪代码冒险记」,用卡皮巴拉的漫画故事讲 23 个设计模式,观察者模式这一集是森林广播站的故事——猫头鹰当 Subject 发布消息,动物们各自订阅自己关心的内容。感兴趣的可以搜一下,或者等我后面的文章,每个模式我都会同步对应的小程序内容。

http://www.gsyq.cn/news/1543732.html

相关文章:

  • 2026国内储能环凸焊机厂家推荐:高品质焊接装备选型指南 - 资讯纵览
  • 电动车带电池怎么托运?这个办法最省心 - 快递物流资讯
  • 5分钟快速上手League Akari:英雄联盟玩家的终极自动化工具指南
  • 2026命理软件功能榜单:易学入门App和易学排盘软件怎么选?
  • 85个公共Tracker终极指南:三步解决BT下载卡顿问题
  • 3步搞定微信QQ防撤回:让重要消息不再“凭空消失“的终极方案
  • 2026中频点焊机选型指南:解析电阻焊机领域代表性品牌 - 资讯纵览
  • 2026年武汉职业装定制厂家推荐——基于华中地区团体着装采购视角的深度测评与选型参考 - 资讯速览
  • 250+ Xshell配色方案终极宝典:彻底告别单调终端的完整指南
  • 详细解析HTTP协议完整进化史——从/1.0到/3.0
  • 2026年6月国内口碑好的虫害防治服务公司有哪些,防鼠服务/灭蟑螂服务/灭臭虫服务/防治服务,虫害防治服务公司哪家好 - 品牌推荐师
  • 2026柯桥区湘菜馆消费选购指南 - 资讯速览
  • 从“前 3 秒“到“AB 实验“:数据驱动的产品增长方法论
  • 2026深圳市民黄金变现便民手册,合规回收门店完整名录汇总 - 奢侈品回收测评
  • 成都钻戒变现避雷手册,回收商家不会透露的 4C 计价隐藏陷阱 - 奢侈品回收评测
  • 终极指南:如何在浏览器中免费使用CADmium进行3D建模
  • 新手出手黄金必看指南,收的顶教你杭州本地变现守住金价利润 - 奢侈品回收评测
  • 从杭州出发:AI搜索优化主体爱搜索GEO赋能本地企业抢占AI搜索蓝海 - 品牌报告
  • 公考行测逻辑推理:从“且或非”到“箭头转化”的实战通关指南
  • 青岛问题肌肤修复修护和医美机构区别 斑痘敏皱怎么选更合适 - 资讯速览
  • SEO 在 2026 年:AI 在胡说,而我在改爬虫配置
  • 2026 南充装修公司推荐 Top3: 企业资质信誉核查清单 + 预算报价 + 用户口碑全解析 - 资讯纵览
  • 2026 昆明二手名表回收行业全面剖析:如何筛选正规有实力回收服务商 - 奢侈品回收评测
  • Rufus v4.14.2377 U 盘启动盘制作工具完整使用教程
  • 2026 成都优质钻石回收机构汇总,不压净度、不扣损耗诚信商家 - 奢侈品回收评测
  • 南京市江宁区烟酒回收哪家好 吉丰寄卖行 15366141303 - 资讯速览
  • VALMET ND9106HX8 定位器工业现场应用指南
  • 嵌入式AI模型部署实战:NXP eIQ Toolkit性能分析与量化优化指南
  • 环凸焊机常见问题解答(2026最新专家版) - 资讯纵览
  • 海豚湾海鲜地方菜孔雀城店阿那亚海鲜推荐聚餐体验分享 - 资讯纵览