Skip to content

说说你对 Spring Boot 事件机制的了解? ​

  • 标签:Spring Boot、后端

🏛 业务架构师 ​

Spring Boot 的事件机制建立在 Spring 的 ApplicationEvent + ApplicationListener 体系上,启动过程发布一组生命周期事件,业务代码借此在正确时机介入:

启动链路的事件序列:ApplicationStartingEvent → EnvironmentPreparedEvent(环境就绪)→ ApplicationContextInitializedEvent → ApplicationPreparedEvent(Bean 定义加载完)→ ContextRefreshedEvent(容器刷新完成) → ApplicationStartedEvent(应用已启动、Runner 未执行)→ ApplicationReadyEvent(Runner 执行完,可以接流量) → 关闭时 ContextClosedEvent。

业务架构师视角,我用事件机制解决三个真问题:

  1. 就绪时序管理:缓存预热、服务注册延迟确认放在 ApplicationReadyEvent(而不是 @PostConstruct,那时容器没就绪)——避免"还没准备好就被网关放流量",这是发布稳定性的细节关键;
  2. 优雅停机:监听 ContextClosedEvent 做资源释放、向调度平台反注册——滚动发布不丢在途请求;
  3. 业务解耦:运行期用领域事件(publishEvent + @EventListener/@TransactionalEventListener)把主流程与副作用(发券、通知)分离——注意默认同步执行,非核心逻辑加 @Async,且事务型监听器要明确 AFTER_COMMIT 语义,防"下游查不到未提交数据"。

一句话:启动事件是生命周期治理的钩子,业务事件是模块解耦的管道——两者都用的好,系统的发布安全与演进速度都有保障。

🏗 系统架构师 ​

体系:ApplicationEventPublisher 加 @EventListener;Boot 又定义了启动生命周期事件序列:

ApplicationStartingEvent → EnvironmentPrepared → ApplicationContextInitialized → Prepared → Started(refresh 完成)→ Ready(Runner 执行后)→ Failed。

三个架构用法:

  1. 启动阶段挂钩:配置中心客户端在 EnvironmentPrepared 介入(早于一切 Bean)、缓存预热放 Ready 之后——知道每个事件的时点,初始化才能放对位置;
  2. 业务解耦:领域事件(OrderPaid)加 @TransactionalEventListener(AFTER_COMMIT)——只在事务提交后触发,是进程内事件最正确的姿势;
  3. 异步化:@EventListener 加 @Async 转异步执行,配套线程池与异常处理。

边界与演进(必讲):进程内事件不可靠——发布后进程崩溃即丢、异步被拒绝即丢。可靠场景(通知、积分、对账)必须演进到 Transactional Outbox 加 MQ:事件与业务同库落表、独立投递、消费幂等。事件机制是解耦的起点,不是可靠性方案。

💻 全栈开发专家 ​

Boot 事件 = Spring 事件模型 + 启动生命周期时间线。

模型三件套:事件(ApplicationEvent 或任意 POJO)、发布者(ApplicationEventPublisher)、监听者(@EventListener)。默认同步执行,@Async 可异步。

启动时间线(要背的主线):ApplicationStartingEvent → ApplicationEnvironmentPreparedEvent(环境就绪、容器未建——配置中心在此挂载远程配置)→ ApplicationContextInitializedEvent → ApplicationPreparedEvent(BeanDefinition 已加载未 refresh)→ ContextRefreshedEvent → WebServerInitializedEvent(Tomcat 起来)→ ApplicationStartedEvent → ApplicationReadyEvent(一切就绪)→ 失败时 ApplicationFailedEvent。

工程落点:

java
@Component
public class WarmupListener {
    @EventListener(ApplicationReadyEvent.class)     // 就绪后预热,不拖慢启动
    public void warm() { cacheService.preload(); }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void afterOrderPaid(OrderPaidEvent e) { mqProducer.send(e); }
}

价值:副作用与主流程解耦——下单后的短信、积分、缓存刷新全挂监听器,新增旁路逻辑不动核心代码。注意:Runner 里抛异常会中断启动,可失败的初始化要 try-catch + 告警;事件可能重复消费,监听器做幂等。