Spring 事件监听机制
候选人小王在面试京东时,面试官翻到简历上"熟练使用 Spring 事件机制",随口问了一句:
"你用过 Spring 的 ApplicationEvent 吗?说说你对事件监听机制的理解。"
小王说:"用过,就是定义一个事件类,发布出去,然后写个监听器接收。"
面试官:"那你用过 @TransactionalEventListener 吗?它和 @EventListener 有什么区别?"
小王愣了两秒:"呃...好像是在事务提交后才触发?"
面试官追问:"那它内部是怎么做到的?"
小王开始擦汗。
【面试官心理】
Spring 事件机制是 Spring Framework 中被严重低估的模块。大多数候选人只知道"发布-订阅"这个基本概念,但说不清 ApplicationEventMulticaster 的广播机制、@TransactionalEventListener 的事务同步原理,以及如何在事件监听中处理异常。这道题能答到源码层面的候选人,通常有较好的系统设计和架构思维。
一、事件三要素 🔴
1.1 最简单的用法
// 1. 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
public OrderCreatedEvent(Object source, Order order) {
super(source);
this.order = order;
}
public Order getOrder() { return order; }
}
// 2. 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(Order order) {
orderMapper.insert(order);
// 发布事件,不要在这里做核心业务!
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
}
}
// 3. 监听事件
@Component
public class OrderEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
Order order = event.getOrder();
// 发送通知、更新缓存、记录日志...
}
}
1.2 ❌ 错误示范
候选人原话:"Spring 事件就是用来解耦的,把业务代码和通知代码分开。"
问题诊断:
- 知道用途但不知道底层原理
- 不理解同步/异步的执行模式
- 不知道事件监听在事务中的特殊处理
【面试官心理】
这道题我通常会从"事件机制解决了什么问题"切入,但真正想问的是"你有没有意识到事件机制的核心价值是解耦,同时有没有踩过事务同步的坑"。知道基本用法的占80%,能说出 @TransactionalEventListener 的占30%,能解释清楚事务同步原理的不到10%。
1.3 事件发布订阅的核心流程
graph TD
A[ApplicationEventPublisher<br/>publishEvent] --> B[ApplicationEventMulticaster<br/>multicastEvent]
B --> C{查找匹配的监听器<br/>ApplicationListener}
C --> D[同步执行<br/>invokeListener]
C --> E[异步执行<br/>@Async + invokeListener]
D --> F[监听器处理<br/>onApplicationEvent]
E --> F
F --> G[可选:@TransactionalEventListener<br/>等待事务提交后再执行]
核心组件:
// ApplicationEventMulticaster.java
// 负责保存所有监听器,并广播事件
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void removeApplicationListener(ApplicationListener<?> listener);
void multicastEvent(ApplicationEvent event); // 广播事件
}
二、@EventListener 注解 🟡
2.1 基本用法
@Component
public class UserEventListener {
// 监听单个事件
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
System.out.println("用户创建: " + event.getUser().getName());
}
// 监听多个事件(用 classes 指定)
@EventListener(classes = {UserCreatedEvent.class, UserDeletedEvent.class})
public void handleUserChanges(ApplicationEvent event) {
if (event instanceof UserCreatedEvent) {
// 处理创建
} else if (event instanceof UserDeletedEvent) {
// 处理删除
}
}
}
2.2 条件监听
@EventListener(condition = "#event.order.amount > 1000")
public void handleHighValueOrder(OrderCreatedEvent event) {
// 只有订单金额 > 1000 时才处理
// SpEL 表达式
sendSmsNotification(event.getOrder());
}
2.3 监听器排序
@Component
public class OrderEventProcessor {
// 监听器按 order 值从小到大执行
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级,最先执行
public void validateOrder(OrderCreatedEvent event) {
// 第一步:校验
}
@EventListener
@Order(100)
public void saveOrder(OrderCreatedEvent event) {
// 第二步:持久化
}
@EventListener
@Order(LOWEST_PRECEDENCE) // 最低优先级,最后执行
public void sendNotification(OrderCreatedEvent event) {
// 第三步:通知
}
}
三、泛型事件 PayloadApplicationEvent 🟡
3.1 为什么需要泛型事件
// 传统方式:需要定义具体的事件类
public class OrderCreatedEvent extends ApplicationEvent {
private final Order order;
// getter...
}
// 泛型方式:直接用 PayloadApplicationEvent
@Service
public class NotificationService {
@EventListener
public void handleOrderCreated(PayloadApplicationEvent<Order> event) {
Order order = event.getPayload();
// 直接拿到 payload
sendEmail(order);
}
}
3.2 发布泛型事件
@Autowired
private ApplicationEventPublisher eventPublisher;
// 简化发布
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
// 泛型发布
eventPublisher.publishEvent(new PayloadApplicationEvent<>(this, order));
四、@TransactionalEventListener —— 事务同步核心 🔴
4.1 核心问题:事件监听和事务的冲突
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
// 发布事件时,事务还没提交!
// 如果监听器去查询数据库,可能查不到刚插入的数据
eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
}
}
@Component
public class NotificationListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 事务可能还没提交
// 如果监听器去读数据库、调用外部服务,可能出问题
Order order = orderMapper.selectById(event.getOrder().getId());
// 可能查到 null!因为事务还没提交
}
}
4.2 @TransactionalEventListener 的解决方案
@Component
public class NotificationListener {
// BEFORE_COMMIT: 事务提交前执行
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(OrderCreatedEvent event) {
// 事务即将提交,但还没有真正提交
// 此时可以回滚(抛异常)
}
// AFTER_COMMIT: 事务提交后执行(最常用)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderCreatedEvent event) {
// 事务已提交,可以安全地查询数据库、调用外部服务
Order order = orderMapper.selectById(event.getOrder().getId()); // 一定能查到
sendEmail(order);
}
// AFTER_ROLLBACK: 事务回滚后执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(OrderCreatedEvent event) {
// 发送告警、记录补偿日志
}
// AFTER_COMPLETION: 事务完成后(无论提交还是回滚)执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(OrderCreatedEvent event) {
// 清理资源
}
}
底层原理:Spring 在 TransactionSynchronizationManager 中注册了一个回调:
// TransactionInterceptor.java 中的 publishEvent
// 伪代码
public Object invoke(MethodInvocation invocation) throws Throwable {
TransactionStatus status = transactionManager.getTransaction();
try {
Object result = invocation.proceed();
// 业务方法执行完毕,准备提交
transactionManager.commit(status, () -> {
// 这里会在事务提交后执行!
publishEventDuringCommit(status, event);
});
return result;
} catch (Throwable ex) {
transactionManager.rollback(status);
throw ex;
}
}
💡
@TransactionalEventListener 的 AFTER_COMMIT 是最常用的场景。比如发送通知、更新缓存、记录审计日志——这些都不应该影响主事务的成功与否。即使这些操作失败了,主订单事务也不应该回滚。
五、同步与异步切换 🟡
5.1 @Async 异步执行
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-event-");
executor.initialize();
return executor;
}
}
@Component
public class NotificationListener {
// 加上 @Async,事件监听异步执行
@Async
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 在独立线程中执行,不阻塞主线程
sendEmail(event.getOrder());
sendSms(event.getOrder());
}
}
5.2 同步 vs 异步的选择
六、生产避坑 🟡
6.1 监听器异常导致事件丢失
@Component
public class OrderListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 如果这里抛异常,后面的监听器就收不到事件了!
throw new RuntimeException("处理失败");
}
}
@Component
public class AuditListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 由于 OrderListener 先抛异常,这个监听器不会执行
saveAuditLog(event.getOrder());
}
}
解决方式:使用 @TransactionalEventListener + 异常处理,或使用 ErrorHandler:
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(
ApplicationEventMulticastericasterFactory factory,
@Qualifier("errorHandler") ErrorHandler errorHandler) {
SimpleApplicationEventMulticaster multicaster = factory.createApplicationEventMulticaster();
multicaster.setErrorHandler(errorHandler);
return multicaster;
}
6.2 事件监听与 Spring Security 的坑
如果监听器中需要获取当前登录用户:
@Component
public class SecurityContextListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 注意:在 @TransactionalEventListener(AFTER_COMMIT) 中
// SecurityContext 可能已经被清空!
// 因为主线程的事务可能已经结束
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// 可能返回 null
}
}
【面试官心理】
这道追问我是想看候选人是否理解"线程上下文和事务上下文的关系"。Spring Security 的 SecurityContext 默认绑定到 ThreadLocal,主线程事务结束后如果 SecurityContext 被清理,监听器中可能拿不到当前用户信息。
七、工程选型 🟢
7.1 事件机制的使用规范
- 事件是纯数据对象:不要在事件中传递 Entity,应该传 DTO
- 监听器要幂等:同一事件可能被重复发送,监听器需要能重复执行
- 优先用 @TransactionalEventListener(AFTER_COMMIT):除非你确定需要在事务中处理
- 不要在事件监听器中抛受检异常:会导致后续监听器不执行
【面试官心理】
我通常会问候选人:"既然有了 @TransactionalEventListener,为什么还需要 @EventListener?" 答出"@EventListener 可以在事务中立即执行,用于需要在事务提交前做一些校验或拦截"的,通常是有实战经验的。
八、面试追问链 🔴
第一层:基本用法
面试官问:"Spring 事件机制怎么用?"
候选人答:"定义事件、发布事件、写监听器..."
考察点:基本使用
第二层:事务问题
面试官追问:"事件监听器在事务提交前还是后执行?怎么保证事务提交后再处理?"
候选人答:...(可能说不清楚)
考察点:@TransactionalEventListener 理解
第三层:源码原理
面试官追问:"ApplicationEventMulticaster 的广播机制是什么?"
候选人答:...(源码层面)
考察点:Spring 事件机制内部实现
第四层:工程实践
面试官追问:"如果事件监听器处理失败了怎么办?怎么保证不丢消息?"
候选人答:...(生产经验)
考察点:可靠性设计