bigywater.tech
Spring · 13

事件机制:同步事件、异步事件与监听器顺序

事件发布到底是同步还是异步?监听器抛异常会怎样?泛型事件怎么匹配?启动过程中发布的事件为什么有的监听器收不到?这篇在 Spring Framework 7.0.9 上逐项验证。

0 ms
多播器配了线程池后 publishEvent 返回花的时间(监听器在 event-pool 线程里执行)
0 个
同步监听器 bad 抛异常后,排在它后面的 after 监听器被执行的个数(异常直接抛给了发布者)
错过
在 Bean 的 @PostConstruct 里发布的事件,@EventListener 方法没有收到,ApplicationListener 的 Bean 收到了

同步

实测 · 本机执行输出
  --- 同步发布两个 OrderPlaced(金额 50 与 500)
  1. publisher 调用 publishEvent 之前  [线程 main]
  2. first(@Order(1)) 收到 A1  [线程 main]
  3. second(@Order(2)) 收到 A1  [线程 main]
  4. onEvt 收到 由监听器的返回值发布:A1  [线程 main]
  5. publisher 的 publishEvent 返回了  [线程 main]
  6. first(@Order(1)) 收到 A2  [线程 main]
  7. second(@Order(2)) 收到 A2  [线程 main]
  8. big(condition amount>100) 收到 A2 金额 500  [线程 main]
  9. onEvt 收到 由监听器的返回值发布:A2  [线程 main]
  10. publisher 的 publishEvent 返回了  [线程 main]
  --- 泛型事件:普通的 Box<String> 与 Box<Integer>(类型实现 ResolvableTypeProvider 之前)
  1. (以上两个 Box 事件发布完,上面没有任何 Box 监听器被调用的记录就是没有被调用)  [线程 main]
  --- 泛型事件:实现了 ResolvableTypeProvider 的 BoxR<String> 与 BoxR<Integer>
  1. Box<String> 的监听器收到 字符串  [线程 main]
  2. BoxR<String> 的监听器收到 字符串  [线程 main]
  3. BoxR<Integer> 的监听器收到 42  [线程 main]
  4. Box<Integer> 的监听器收到 42  [线程 main]

监听器在发布者线程里依次执行,@Order 排序,返回值被当作新事件发布,condition 用 SpEL 过滤。

异常

实测 · 本机执行输出
  --- 同步监听器抛异常
  1. bad(@Order(1)) 抛异常  [线程 main]
  2. publishEvent 抛给了发布者:IllegalStateException: 监听器失败  [线程 main]

同步事件里监听器抛异常,后面的监听器跳过,异常抛给发布者。

异步

实测 · 本机执行输出
  --- 多播器配了线程池(SimpleApplicationEventMulticaster.setTaskExecutor)与 ErrorHandler
  1. publishEvent 返回,用了 0 ms  [线程 main]
  2. 第二个 publishEvent 返回  [线程 main]
  3. bad(@Order(1)) 抛异常  [线程 event-pool]
  4. ErrorHandler 收到异常:监听器失败  [线程 event-pool]
  5. after(@Order(2)) 收到  [线程 event-pool]
  6. Slow 监听器处理完 B1  [线程 event-pool]

给多播器配线程池后发布者立刻返回,异常交给 ErrorHandler。

启动阶段

实测 · 本机执行输出
  --- 在 Early 的 @PostConstruct 里发布事件(此时容器还在 refresh 的单例创建阶段)
  1. ApplicationListener 类型的 Bean 收到 Early.@PostConstruct 里发布  [线程 main]
  2. Early.@PostConstruct 发布完毕  [线程 main]

@EventListener 方法在单例创建完之后才注册,更早发布的事件它收不到。

内置事件

实测 · 本机执行输出
  --- 内置事件
  1. ContextRefreshedEvent  [线程 main]
  2. ContextStartedEvent  [线程 main]
  3. ContextStoppedEvent  [线程 main]
  4. ContextClosedEvent  [线程 main]

refresh、start、stop、close 各发一个对应的 Context 事件。

分步演示

Spring 的事件机制
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用不带 Boot 的 Spring Framework 7.0.9;异步通过给 SimpleApplicationEventMulticaster 设置 TaskExecutor 实现,没有测 @Async 加 @EventListener 的写法;“@EventListener 方法在所有单例创建完之后才注册”是根据现象(Anno 没收到,Cls 收到了)的解释,没有读 EventListenerMethodProcessor 的源码
    • 没有验证:@Async 与 @EventListener 组合;@TransactionalEventListener(见事务篇);事件监听器的执行耗时对发布者的影响;ApplicationEventMulticaster 的缓存机制;Boot 的 ApplicationStartedEvent / ApplicationReadyEvent(见后面的 Boot 篇)
    京ICP备2026067502号-1京公网安备11010502063487号