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 事件机制的使用规范

  1. 事件是纯数据对象:不要在事件中传递 Entity,应该传 DTO
  2. 监听器要幂等:同一事件可能被重复发送,监听器需要能重复执行
  3. 优先用 @TransactionalEventListener(AFTER_COMMIT):除非你确定需要在事务中处理
  4. 不要在事件监听器中抛受检异常:会导致后续监听器不执行
特性@EventListener@TransactionalEventListener
事务同步有(可指定 phase)
默认执行时机立即同步执行取决于 phase
适用场景不依赖事务的操作依赖事务已提交的操作
BEFORE_COMMIT不支持支持

【面试官心理】 我通常会问候选人:"既然有了 @TransactionalEventListener,为什么还需要 @EventListener?" 答出"@EventListener 可以在事务中立即执行,用于需要在事务提交前做一些校验或拦截"的,通常是有实战经验的。

八、面试追问链 🔴

第一层:基本用法 面试官问:"Spring 事件机制怎么用?" 候选人答:"定义事件、发布事件、写监听器..." 考察点:基本使用

第二层:事务问题 面试官追问:"事件监听器在事务提交前还是后执行?怎么保证事务提交后再处理?" 候选人答:...(可能说不清楚) 考察点:@TransactionalEventListener 理解

第三层:源码原理 面试官追问:"ApplicationEventMulticaster 的广播机制是什么?" 候选人答:...(源码层面) 考察点:Spring 事件机制内部实现

第四层:工程实践 面试官追问:"如果事件监听器处理失败了怎么办?怎么保证不丢消息?" 候选人答:...(生产经验) 考察点:可靠性设计