Skip to content

Spring 中的 @EventListener 注解的作用是什么? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

@EventListener 的作用是声明事件监听方法:标注的方法在容器发布对应类型事件时被回调,是 Spring 内置的观察者模式入口:

java
// 发布方:下单成功后发布领域事件
eventPublisher.publishEvent(new OrderPaidEvent(orderId));
// 监听方:积分、通知、风控各自订阅,互不感知
@EventListener
public void addPoints(OrderPaidEvent event) { ... }

业务架构师视角,这是模块解耦的核心工具,我的三个典型落地:

  1. 主流程瘦身:下单主流程只保留"创建订单、扣库存"这些强一致步骤,发券、积分、推送等通过事件异步补充——主链路 RT 从串行叠加变成核心几步,用户体验和吞吐直接受益;
  2. 领域事件驱动:DDD 里限界上下文之间不直接调用,靠领域事件通信,@EventListener 是进程内实现,跨服务时换 MQ 但模型不变——事件风暴的产出可以平滑映射到代码;
  3. 平台扩展点:框架启动事件(ContextRefreshedEvent)挂初始化逻辑,避免侵入框架代码。

治理红线:默认监听是同步执行(在发布方线程、事务内跑),监听器抛异常会拖垮主流程——非核心逻辑必须加 @Async + @TransactionalEventListener(事务提交后才触发,避免"事务还没提交、下游查不到数据"的经典 Bug)。事件虽好,一致性与隔离语义必须显式设计。

🏗 系统架构师 ​

作用:声明式订阅容器事件——方法参数即事件类型,默认在发布线程同步执行(标 @Async 才异步)。配合 ApplicationEventPublisher 构成进程内事件总线。

架构上我把它看作解耦的第一级火箭:

  1. 进程内事件:@EventListener 解耦调用方与处理方(下单成功后发积分、记流水),双方代码互不感知;
  2. 事务绑定事件:@TransactionalEventListener(phase = AFTER_COMMIT) 只在事务提交后触发——解决「事务未提交就发消息/写缓存,下游读到旧值」的经典一致性坑,这是我在生产中用得最重的注解之一;
  3. 跨服务事件:进程内事件不可靠(进程崩溃即丢),可靠性要求高的链路要演进到 Transactional Outbox 加 MQ——本地消息表与业务同事务落库,再异步投递,消费方幂等。

设计纪律:事件处理器异常不得影响主流程(异步时只记日志告警);事件命名表达「已发生的事实」(OrderPaid)而非命令口吻;事件载荷带 traceId 便于跨环节追踪。

💻 全栈开发专家 ​

订阅容器事件,实现解耦的发布-订阅:

java
// 发布
applicationEventPublisher.publishEvent(new OrderPaidEvent(orderId));

// 监听(默认同步、同线程、同事务)
@Component
public class NotifyListener {
    @EventListener
    public void onPaid(OrderPaidEvent event) { smsService.notify(event); }

    @EventListener
    @Async                                // 异步执行不阻塞主流程
    public void addPoint(OrderPaidEvent event) { pointService.add(event); }
}

默认同步执行——监听器抛异常会打断发布方,这个语义必须记牢。两个高阶武器:

1. @TransactionalEventListener:事务提交后才执行,解决"事务还没提交 MQ/短信就发了,消费方查库查不到"的经典问题:

java
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterPaid(OrderPaidEvent event) { mqProducer.send(event); }

2. 条件过滤:@EventListener(condition = "#event.type == 'VIP'")。

工程价值:下单后的积分、短信、缓存刷新全挂监听器,新增副作用不动核心代码。注意幂等——事件可能重放/并发,监听器里按业务键做幂等表;异步监听的线程里 MDC 上下文要用 TaskDecorator 带过去,不然 traceId 断了。