Spring 的事件机制你用了三年但 TransactionalEventListener 的坑一个都没绕过去事情是这样的用户下单后要发短信通知。我在 OrderService 里写了个EventListenerjava 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_COMPLETIONjava // 事务提交成功 → 执行 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 发布消息动物们各自订阅自己关心的内容。感兴趣的可以搜一下或者等我后面的文章每个模式我都会同步对应的小程序内容。