Spring 事件机制 — ApplicationEvent / ApplicationListener
一句话定位:Spring 事件机制是容器内的"广播站"——事件发布者(Publisher)只管发广播,监听器(Listener)按需订阅,双方互不认识,彻底解耦。
为什么需要事件机制?
乐途公司的小崔写完订单创建逻辑后,还要做一堆"善后":
- 发短信通知用户
- 给库存系统发消息扣减库存
- 给积分系统发消息加积分
- 给数据分析系统埋点
如果小崔在 OrderService.createOrder() 里直接调用这些系统,代码会变成这样:
public Order createOrder(...) {
// ... 创建订单
smsService.send(userId, "订单已创建");
inventoryClient.deduct(productId, quantity);
pointsService.add(userId, quantity * 10);
analyticsClient.track("order_created", ...);
return order;
}
问题:
- 订单服务与短信、库存、积分、分析系统强耦合
- 任何一个下游系统变慢或故障,都会影响订单创建主流程
- 新增一个下游需求,就要改
OrderService代码
Spring 事件机制的解决思路:订单创建完成后发一个事件,谁关心谁去听。
核心组件
| 组件 | 说明 |
|---|---|
| ApplicationEvent | 事件基类,自定义事件需继承它(Spring 4.2+ 也支持任意对象作为事件) |
| ApplicationListener | 监听器接口,实现 onApplicationEvent(E event) 方法 |
| ApplicationEventPublisher | 发布者接口,通过 publishEvent(Object event) 发送事件 |
| ApplicationEventMulticaster | 事件多播器,负责将事件分发给所有匹配的监听器 |
乐途场景:订单生命周期事件
定义事件
// 订单创建事件
public class OrderCreatedEvent extends ApplicationEvent {
private final Long orderId;
private final Long userId;
private final Long productId;
private final int quantity;
private final LocalDateTime createdAt;
public OrderCreatedEvent(Object source, Long orderId, Long userId,
Long productId, int quantity) {
super(source);
this.orderId = orderId;
this.userId = userId;
this.productId = productId;
this.quantity = quantity;
this.createdAt = LocalDateTime.now();
}
// getters...
}
// 订单支付事件
public class OrderPaidEvent extends ApplicationEvent {
private final Long orderId;
private final BigDecimal amount;
private final String paymentMethod;
public OrderPaidEvent(Object source, Long orderId, BigDecimal amount, String paymentMethod) {
super(source);
this.orderId = orderId;
this.amount = amount;
this.paymentMethod = paymentMethod;
}
// getters...
}
发布事件
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;
public OrderService(OrderRepository orderRepository,
ApplicationEventPublisher eventPublisher) {
this.orderRepository = orderRepository;
this.eventPublisher = eventPublisher;
}
@Transactional
public Order createOrder(Long userId, Long productId, int quantity) {
Order order = new Order(userId, productId, quantity, OrderStatus.CREATED);
Order saved = orderRepository.save(order);
// 发布事件:订单已创建
eventPublisher.publishEvent(new OrderCreatedEvent(
this, saved.getId(), userId, productId, quantity
));
return saved;
}
@Transactional
public void payOrder(Long orderId, String paymentMethod) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
order.markAsPaid();
orderRepository.save(order);
// 发布事件:订单已支付
eventPublisher.publishEvent(new OrderPaidEvent(
this, orderId, order.getTotalAmount(), paymentMethod
));
}
}
监听事件(传统方式)
@Component
public class SmsNotificationListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.printf("[SMS] 用户 %d 的订单 %d 已创建,发送短信通知...%n",
event.getUserId(), event.getOrderId());
// 调用短信网关...
}
}
@Component
public class InventoryDeductionListener implements ApplicationListener<OrderCreatedEvent> {
@Override
public void onApplicationEvent(OrderCreatedEvent event) {
System.out.printf("[库存] 扣减商品 %d 库存 %d 件%n",
event.getProductId(), event.getQuantity());
// 调用库存服务...
}
}
监听事件(注解方式 — 推荐)
Spring 4.2+ 提供了 @EventListener,无需实现接口:
@Component
public class OrderEventHandlers {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.printf("[积分] 给用户 %d 增加 %d 积分%n",
event.getUserId(), event.getQuantity() * 10);
}
@EventListener
public void handleOrderPaid(OrderPaidEvent event) {
System.out.printf("[分析] 记录支付事件:订单 %d,金额 %.2f,方式 %s%n",
event.getOrderId(), event.getAmount(), event.getPaymentMethod());
}
@EventListener(condition = "#event.amount > 1000")
public void handleBigOrder(OrderPaidEvent event) {
System.out.printf("[运营] 大单 alert!订单 %d 金额 %.2f,通知高英跟进%n",
event.getOrderId(), event.getAmount());
}
}
异步事件与事务绑定
异步监听
默认事件是同步的:发布者会等所有监听器执行完才继续。如果监听器耗时,会阻塞主流程。
@Configuration
@EnableAsync
public class EventConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(new SimpleAsyncTaskExecutor("event-"));
return multicaster;
}
}
或使用 @Async + @EventListener:
@Async
@EventListener
public void handleOrderCreatedAsync(OrderCreatedEvent event) {
// 在独立线程中执行
smsService.send(event.getUserId(), "订单已创建");
}
事务绑定事件
乐途公司要求:只有订单事务成功提交后,才发送短信和扣减库存。如果事务回滚,事件不应触发。
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreatedAfterCommit(OrderCreatedEvent event) {
// 只在事务提交后执行
inventoryService.deduct(event.getProductId(), event.getQuantity());
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleOrderRollback(OrderCreatedEvent event) {
// 事务回滚后执行补偿逻辑
System.out.println("[补偿] 订单创建失败,释放预留资源");
}
| phase | 触发时机 |
|---|---|
BEFORE_COMMIT | 事务提交前 |
AFTER_COMMIT | 事务提交后(最常用) |
AFTER_ROLLBACK | 事务回滚后 |
AFTER_COMPLETION | 事务完成(无论提交或回滚) |
注意事项
| 注意点 | 说明 |
|---|---|
| 同步默认 | 默认事件处理是同步的,会阻塞发布者线程。耗时操作请用异步或 @TransactionalEventListener(AFTER_COMMIT) |
| 异常传播 | 同步监听器抛异常会中断后续监听器,并可能回滚发布者的事务。建议监听器内部 try-catch |
| 同一 JVM 内 | Spring 事件是单机内存广播,不支持分布式。跨服务请用消息队列(Kafka/RabbitMQ) |
| Spring 4.2+ 泛型事件 | 可以发布任意对象(不强制继承 ApplicationEvent),监听器用 @EventListener 配合参数类型匹配 |
| 顺序控制 | 用 @Order(1) / @Order(2) 控制多个监听器的执行顺序,数字越小越先执行 |
常见面试题
Q1:Spring 事件和消息队列(MQ)有什么区别?
Spring 事件是进程内(单机)的发布-订阅,基于
ApplicationContext的内存广播,速度快但无法跨 JVM。MQ 是分布式消息中间件,支持跨服务、持久化、削峰填谷。Spring 事件适合单体应用内部解耦,MQ 适合微服务间通信。
Q2:@EventListener 和 ApplicationListener 接口方式怎么选?
@EventListener更灵活:支持 SpEL 条件过滤、一个类监听多种事件、方法名自由。ApplicationListener是接口实现,类型安全但一个类只能监听一种事件。现代 Spring 项目推荐@EventListener。
Q3:为什么 @TransactionalEventListener(AFTER_COMMIT) 的事件没触发?
可能原因:① 发布事件的方法没有
@Transactional;② 事务被标记为rollbackOnly;③ 事件在事务外发布;④ 异步模式下事务上下文丢失。
Q4:同一个事件有多个监听器,如何控制执行顺序?
在监听器方法或类上标注
@Order(数值),数值越小优先级越高。默认所有监听器顺序不确定。