@EventListener
一句话定位:
@EventListener是 Spring 4.2 带来的"懒人福音"——不用实现ApplicationListener接口,直接在任意 Bean 的方法上贴个注解,就能监听 Spring 容器内的事件广播。
概念说明
@EventListener 是 Spring 对事件监听机制的注解化封装。传统方式需要实现 ApplicationListener<E> 接口,一个类只能监听一种事件。@EventListener 打破了这种限制:
- 一个类可以监听多种事件
- 支持 SpEL 条件过滤
- 支持 异步执行
- 支持 事务绑定
为什么需要它?
传统接口方式的问题:
- 侵入性强:必须实现 Spring 接口,与框架耦合
- 一个类一种事件:想监听两种事件?写两个类
- 无法条件过滤:所有事件都进
onApplicationEvent,内部再 if-else - 无法直接异步:需要配置
ApplicationEventMulticaster
@EventListener 用声明式注解解决了所有痛点。
使用示例(乐途场景)
基础监听
乐途公司的订单系统,一个类处理所有订单相关事件:
@Component
public class OrderNotificationHandler {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.printf("[通知] 订单 %d 已创建,给用户 %d 发站内信%n",
event.getOrderId(), event.getUserId());
}
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
System.out.printf("[通知] 订单 %d 已支付 %.2f 元,准备发货%n",
event.getOrderId(), event.getAmount());
}
@EventListener
public void onOrderShipped(OrderShippedEvent event) {
System.out.printf("[通知] 订单 %d 已发货,物流单号 %s%n",
event.getOrderId(), event.getTrackingNumber());
}
}
SpEL 条件过滤
乐途公司的大单需要运营高英特别关注:
@Component
public class BigOrderAlertHandler {
// 只监听金额 > 10000 的订单
@EventListener(condition = "#event.amount > 10000")
public void alertBigOrder(OrderPaidEvent event) {
System.out.printf("[运营警报] 大单!订单 %d,金额 %.2f,通知高英跟进%n",
event.getOrderId(), event.getAmount());
}
// 只监听使用支付宝支付的订单
@EventListener(condition = "#event.paymentMethod == 'ALIPAY'")
public void alipayPromotion(OrderPaidEvent event) {
System.out.printf("[营销] 支付宝订单 %d,发放红包%n", event.getOrderId());
}
}
异步监听
短信通知不需要阻塞订单主流程:
@Component
public class AsyncNotificationHandler {
@Async
@EventListener
public void sendSms(OrderCreatedEvent event) {
// 在独立线程池中执行
smsService.send(event.getUserId(), "您的订单已提交,订单号:" + event.getOrderId());
}
@Async
@EventListener
public void sendEmail(OrderPaidEvent event) {
emailService.sendPaymentConfirmation(event.getOrderId(), event.getAmount());
}
}
需要先在配置类上标注
@EnableAsync。
事务绑定监听
库存扣减必须在订单事务提交后才执行:
@Component
public class InventoryEventHandler {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void deductInventory(OrderCreatedEvent event) {
// 事务已提交,安全地扣减库存
inventoryService.deduct(event.getProductId(), event.getQuantity());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void releaseInventory(OrderCreatedEvent event) {
// 事务回滚,释放预留库存
inventoryService.release(event.getProductId(), event.getQuantity());
}
}
监听多个事件类型
@Component
public class AuditLogHandler {
// 一个方法监听多种事件
@EventListener({OrderCreatedEvent.class, OrderPaidEvent.class, OrderShippedEvent.class})
public void recordAudit(AbstractOrderEvent event) {
auditLogService.record(event.getOrderId(), event.getClass().getSimpleName());
}
}
核心属性
| 属性 | 说明 |
|---|---|
value / classes | 指定监听的事件类型,可省略(由方法参数类型推断) |
condition | SpEL 表达式,只有结果为 true 时才触发 |
id | 监听器标识,可用于程序化移除 |
注意事项
| 注意点 | 说明 |
|---|---|
| 方法参数即事件类型 | 方法有且仅有一个参数,类型即为监听的事件类型。如果监听多种事件,参数用共同父类 |
| 返回值 | 如果方法返回值不是 void,Spring 会把它当作新事件再次发布 |
| 异常处理 | 同步监听器抛异常会中断后续监听器。建议内部 try-catch,或用异步隔离 |
与 @Transactional 的关系 | @EventListener 默认参与发布者的事务。如需独立事务,方法上加 @Transactional(propagation = REQUIRES_NEW) |
| 泛型事件 | Spring 4.2+ 支持发布任意对象(不强制继承 ApplicationEvent),@EventListener 按参数类型匹配 |
常见面试题
Q1:@EventListener 和 ApplicationListener 接口方式有什么区别?
@EventListener更灵活:一个类可监听多种事件、支持 SpEL 条件过滤、支持异步和事务绑定、无需实现接口。ApplicationListener是标准接口,类型安全但功能受限。现代项目推荐@EventListener。
Q2:@EventListener 方法的返回值有什么用?
如果方法返回值不是 void,Spring 会将其包装为
PayloadApplicationEvent再次发布到容器中。这可以实现事件链:A 事件触发 → 监听器处理 → 返回 B 事件 → 另一个监听器处理 B。
Q3:@TransactionalEventListener 和 @EventListener + @Transactional 有什么区别?
@TransactionalEventListener专门用于绑定事务阶段(AFTER_COMMIT / AFTER_ROLLBACK),确保在正确的事务时机执行。@EventListener+@Transactional只是让监听器在独立事务中运行,不绑定发布者的事务阶段。
Q4:为什么我的 @Async @EventListener 没触发?
可能原因:① 没有
@EnableAsync;② 异步线程池已满;③ 监听器方法抛异常被吞掉;④ 事件在事务中发布但监听器是 AFTER_COMMIT,而事务已回滚。