Spring Boot 启动过程中会发布哪些事件?怎么监听这些启动事件?
简化版
**Spring Boot 在启动过程中会按顺序发布一系列「应用事件」,你可以监听它们,在启动的特定阶段插入自己的逻辑。**主要的启动事件(按发布顺序):① ApplicationStartingEvent——刚开始启动、几乎什么都还没做(连 Environment 都没准备好);② ApplicationEnvironmentPreparedEvent——Environment 准备好了(配置、profile 已加载),但容器还没创建;③ ApplicationContextInitializedEvent——容器创建好、但 Bean 定义还没加载;④ ApplicationPreparedEvent——Bean 定义加载完、但还没 refresh(Bean 还没实例化);⑤ ApplicationStartedEvent——容器 refresh 完成、Bean 都就绪,但 Runner 还没执行;⑥ ApplicationReadyEvent——应用完全就绪(Runner 执行完,可对外服务,最常用);⑦ ApplicationFailedEvent——启动过程中出错时发布。怎么监听:常规的 @EventListener 只能监听 ApplicationReadyEvent 之后的(因为它依赖容器就绪);更早的事件(如 ApplicationStartingEvent)要用 SpringApplication.addListeners() 或在 META-INF/spring.factories 里注册 ApplicationListener(因为那时容器还没起来,@EventListener 还不可用)。
详细版
启动事件顺序与时机:
| 顺序 | 事件 | 时机 | Environment | 容器 | Bean |
|---|---|---|---|---|---|
| 1 | ApplicationStartingEvent | 刚开始 | ❌ | ❌ | ❌ |
| 2 | ApplicationEnvironmentPreparedEvent | 环境就绪 | ✅ | ❌ | ❌ |
| 3 | ApplicationContextInitializedEvent | 容器已建 | ✅ | ✅(空) | ❌ |
| 4 | ApplicationPreparedEvent | Bean 定义已载 | ✅ | ✅ | 定义✅ 实例❌ |
| 5 | ApplicationStartedEvent | refresh 完 | ✅ | ✅ | ✅ |
| 6 | ApplicationReadyEvent | 完全就绪 | ✅ | ✅ | ✅ + Runner✅ |
| — | ApplicationFailedEvent | 启动失败 | — | — | — |
// 方式一:监听 ApplicationReadyEvent(容器已就绪,@EventListener 可用)
@Component
public class ReadyListener {
@EventListener(ApplicationReadyEvent.class)
public void onReady(ApplicationReadyEvent event) {
// 应用完全就绪后执行(最常用的启动后钩子)
}
}
// 方式二:监听更早的事件——用 SpringApplication.addListeners
public static void main(String[] args) {
SpringApplication app = new SpringApplication(App.class);
app.addListeners((ApplicationListener<ApplicationStartingEvent>) e -> {
// 极早期,容器还没起来
});
app.run(args);
}
// 方式三:META-INF/spring.factories 注册 ApplicationListener(同样能监听早期事件)
⚠️ 关键认知:越早的事件,能用的东西越少,监听方式也受限。
@EventListener是靠 Spring 容器的事件机制工作的——它要先有一个就绪的容器来处理@EventListener注解。所以@EventListener只能监听容器就绪之后的事件(ApplicationStartedEvent、ApplicationReadyEvent);而ApplicationStartingEvent、ApplicationEnvironmentPreparedEvent这些发生在「容器还没起来」时,@EventListener根本还没生效,必须用SpringApplication.addListeners()或spring.factories注册ApplicationListener(这些是 Spring Boot 用另一套机制在早期就加载的)。这也解释了为什么日志系统、配置加载这类「极早期就要工作」的组件,都是通过spring.factories注册监听器来介入的。
完整版教学
一、为什么要有启动事件
Spring Boot 启动事件的价值是「在启动的不同阶段提供介入点」:
启动是一个"分阶段"的过程:
准备环境 → 创建容器 → 加载 Bean 定义 → 实例化 Bean → 就绪
不同的组件需要在"不同阶段"介入:
- 日志系统:要在"环境准备好"时就初始化(拿到日志配置)
- 配置加密/外部配置:要在"环境准备阶段"介入
- 一些框架:要在"Bean 定义加载后、实例化前"动手脚
- 业务初始化:要在"完全就绪后"执行
Spring Boot 的方案:在每个关键阶段发布一个事件
想在某阶段介入 → 监听对应的事件
→ 把"启动流程"变成"可插拔的、事件驱动的"扩展点
启动事件的价值是「把启动流程的各阶段变成可介入的扩展点」——启动是分阶段的(准备环境→建容器→加载定义→实例化→就绪),不同组件要在不同阶段介入(日志系统在环境就绪时、业务初始化在完全就绪后)。Spring Boot 在每个关键阶段发布一个事件,监听对应事件就能在该阶段插入逻辑。理解「启动分阶段、不同组件在不同阶段介入、Spring Boot 每阶段发事件让启动流程可插拔」,就理解了启动事件的设计意图。
二、七个事件的顺序与时机
把七个事件按顺序和「此时什么就绪了」讲清楚:
① ApplicationStartingEvent:
刚调 run()、几乎什么都没做(Environment、容器都还没有)
用途:极早期初始化(如日志系统的最早介入点)
② ApplicationEnvironmentPreparedEvent:
Environment 准备好了(配置文件、profile、环境变量都加载了)
但容器还没创建
用途:读/改配置、根据配置做早期决策(如根据 profile 决定日志配置)
③ ApplicationContextInitializedEvent:
ApplicationContext 创建好了,但 Bean 定义还没加载、Bean 还没实例化
用途:对容器做早期定制(ApplicationContextInitializer 相关)
④ ApplicationPreparedEvent:
Bean 定义已加载(BeanDefinition 都注册了),但还没 refresh(没实例化)
用途:在实例化前操作 Bean 定义
⑤ ApplicationStartedEvent:
容器 refresh 完成,所有 Bean 都实例化、初始化好了
但 CommandLineRunner/ApplicationRunner 还没执行
用途:Bean 就绪后、Runner 前的介入
⑥ ApplicationReadyEvent:★ 最常用
Runner 也执行完了,应用完全就绪、可对外服务
用途:启动后业务初始化、"应用已就绪"的标志(发通知、注册服务发现)
⑦ ApplicationFailedEvent:
启动过程中任何阶段抛异常 → 发布它
用途:启动失败的兜底处理(告警、清理)
七个事件按启动进度排列,核心是「每个事件时刻什么就绪了」:Starting(啥都没有)→ EnvironmentPrepared(配置就绪、容器没有)→ ContextInitialized(容器建好、Bean 定义没有)→ Prepared(Bean 定义就绪、没实例化)→ Started(Bean 就绪、Runner 没执行)→ Ready(完全就绪,最常用)→ Failed(出错时)。理解每个事件对应的就绪状态,就知道该监听哪个。理解「七事件按启动进度:Starting→EnvironmentPrepared→ContextInitialized→Prepared→Started→Ready(最常用)→Failed、每个对应不同就绪状态」,就掌握了事件顺序。
三、@EventListener 的限制:容器就绪后才可用
监听事件有个关键限制——@EventListener 只能用于「容器就绪后」的事件:
@EventListener 是怎么工作的?
它靠 Spring 容器的事件机制——需要容器扫描到这个注解、
注册成事件监听器;这要求"容器已经就绪、能处理注解"
所以 @EventListener 只能监听:
⑤ ApplicationStartedEvent(容器 refresh 完,@EventListener 已生效)
⑥ ApplicationReadyEvent
→ 这两个是容器就绪之后的
不能用 @EventListener 监听(因为那时容器还没起来):
① ApplicationStartingEvent
② ApplicationEnvironmentPreparedEvent
③ ApplicationContextInitializedEvent
④ ApplicationPreparedEvent(这个略特殊,容器有了但 @EventListener 未必可靠)
→ 这些"早期事件"要用别的方式监听(下一节)
记忆:@EventListener 只覆盖"容器就绪后",早期事件够不着
@EventListener 的限制是「只能监听容器就绪后的事件」——它靠 Spring 容器的事件机制工作(要容器扫描注解、注册监听器),所以只覆盖 ApplicationStartedEvent、ApplicationReadyEvent 这些「容器 refresh 完成后」的事件。而 ApplicationStartingEvent 等早期事件发生在容器还没起来时,@EventListener 够不着。理解「@EventListener 靠容器事件机制、只能监听容器就绪后的事件(Started/Ready)、早期事件够不着」,就理解了监听方式的核心限制。
四、监听早期事件:addListeners 与 spring.factories
监听早期事件要用「不依赖容器」的两种方式:
早期事件(容器没起来时发布的)要用这两种方式监听:
方式一:SpringApplication.addListeners()
public static void main(String[] args) {
SpringApplication app = new SpringApplication(App.class);
app.addListeners((ApplicationListener<ApplicationStartingEvent>) e -> {
// 在启动最早期执行
});
app.run(args);
}
→ 在 run() 之前手动注册,能监听所有事件(含最早的)
方式二:META-INF/spring.factories 注册 ApplicationListener
org.springframework.context.ApplicationListener=\
com.example.MyStartingListener
→ Spring Boot 启动极早期就会加载 spring.factories 里的监听器
→ 所以能监听最早的事件(框架/库常用这种方式介入)
为什么这两种能监听早期事件:
它们不依赖"容器就绪"——是 Spring Boot 在启动流程里
用 SpringApplicationRunListeners 机制主动加载和调用的
→ 在容器起来之前就能工作
Spring Boot 3.x 也支持在 spring.factories 之外用
spring-boot 的新机制注册,但思路相同
监听早期事件用不依赖容器的两种方式:① SpringApplication.addListeners()(在 run() 前手动注册,能监听所有事件);② META-INF/spring.factories 注册 ApplicationListener(Spring Boot 启动极早期就加载,框架/库常用)。它们不依赖「容器就绪」——是 Spring Boot 用 SpringApplicationRunListeners 机制主动加载调用的,在容器起来前就能工作。理解「监听早期事件用 addListeners 或 spring.factories 注册 ApplicationListener、不依赖容器就绪、Spring Boot 主动加载调用」,就掌握了监听早期事件的方式。
五、SpringApplicationRunListener:事件的发布者
深入一层:这些事件是谁发布的?是 SpringApplicationRunListener:
SpringApplicationRunListener:
Spring Boot 启动过程的"广播员"
在 run() 的各个阶段,调用它的方法来发布对应事件:
starting() → 发 ApplicationStartingEvent
environmentPrepared → 发 ApplicationEnvironmentPreparedEvent
contextPrepared/Loaded → ...
started() → 发 ApplicationStartedEvent
ready() → 发 ApplicationReadyEvent
failed() → 发 ApplicationFailedEvent
默认实现:EventPublishingRunListener
它内部有一个事件广播器(早期用 SimpleApplicationEventMulticaster)
把这些事件广播给所有注册的 ApplicationListener
所以整条链路:
SpringApplication.run() 各阶段
→ 调 SpringApplicationRunListener 的对应方法
→ EventPublishingRunListener 广播事件
→ 各 ApplicationListener 收到并处理
事件的发布者是 SpringApplicationRunListener(Spring Boot 启动的「广播员」)——run() 的各阶段调它的方法(starting/environmentPrepared/started/ready/failed)发布对应事件,默认实现 EventPublishingRunListener 内部用事件广播器把事件广播给所有 ApplicationListener。这解释了「为什么早期事件能被 spring.factories 注册的监听器收到」——因为 SpringApplicationRunListener 在容器起来前就已经在广播了。理解「SpringApplicationRunListener 是启动广播员、各阶段发对应事件、EventPublishingRunListener 广播给 ApplicationListener」,就理解了启动事件的发布机制。
六、实际应用与选择
总结启动事件的实际用途和「该监听哪个」:
常见需求 → 该监听哪个事件:
启动后执行业务初始化(预热、通知、注册服务发现)
→ ApplicationReadyEvent(最常用,应用完全就绪)
→ 或直接用 CommandLineRunner/ApplicationRunner(时机接近)
需要在配置加载后、容器创建前做事(如根据配置定制)
→ ApplicationEnvironmentPreparedEvent(用 addListeners/spring.factories)
启动失败时告警/清理
→ ApplicationFailedEvent
框架/库要在极早期介入
→ ApplicationStartingEvent(spring.factories 注册)
ApplicationReadyEvent vs CommandLineRunner:
两者时机接近(都在应用就绪后)
- Runner:更直接,适合"执行一段初始化代码"
- ReadyEvent:事件语义更明确,且能被多个监听器解耦地处理
- 顺序:Runner 先执行,然后才发 ApplicationReadyEvent
选择原则:
能用 @EventListener(ApplicationReadyEvent) 就用它(简单)
要监听早期事件才用 addListeners / spring.factories
实际选择「监听哪个事件」按需求定:启动后业务初始化 → ApplicationReadyEvent(最常用)或 Runner;配置加载后容器前 → ApplicationEnvironmentPreparedEvent(早期,用 addListeners);启动失败告警 → ApplicationFailedEvent;框架极早期介入 → ApplicationStartingEvent(spring.factories)。ApplicationReadyEvent 和 Runner 时机接近(Runner 先执行、再发 ReadyEvent),能用简单的 @EventListener(ApplicationReadyEvent) 就用它。理解「按需求选事件:业务初始化用 ReadyEvent(最常用)、早期定制用 EnvironmentPrepared、失败用 Failed;Runner 先于 ReadyEvent;能用 @EventListener 就用」,就掌握了启动事件的实际应用。
记忆钩子:「Spring Boot 启动事件(按顺序):①ApplicationStartingEvent(啥都没有)②EnvironmentPreparedEvent(配置就绪容器没有)③ContextInitializedEvent(容器建好Bean定义没有)④PreparedEvent(Bean定义就绪没实例化)⑤StartedEvent(Bean就绪Runner没执行)⑥ReadyEvent(完全就绪,最常用)⑦FailedEvent(出错);@EventListener 只能监听容器就绪后的(Started/Ready);早期事件用 SpringApplication.addListeners 或 spring.factories 注册 ApplicationListener(不依赖容器);发布者是 SpringApplicationRunListener(EventPublishingRunListener 广播);Runner 先于 ReadyEvent」。
七、常见误区与追问
- 误区:所有启动事件都能用 @EventListener 监听。 不能——@EventListener 靠容器事件机制,只能监听容器就绪后的事件(ApplicationStartedEvent、ApplicationReadyEvent);早期事件(ApplicationStartingEvent 等)容器还没起来,要用 SpringApplication.addListeners() 或 spring.factories 注册 ApplicationListener。
- 误区:ApplicationStartedEvent 就是应用完全就绪了。 ApplicationStartedEvent 时容器 refresh 完、Bean 就绪,但 CommandLineRunner/ApplicationRunner 还没执行;ApplicationReadyEvent 才是「Runner 也跑完、应用完全就绪」——两者差一个 Runner 执行阶段。
- 误区:越早的事件能用的资源越多。 恰恰相反——越早的事件能用的越少:ApplicationStartingEvent 时连 Environment 都没有;EnvironmentPreparedEvent 才有配置;Started/Ready 才有就绪的 Bean;监听早期事件时不能指望注入 Bean。
- 误区:启动失败没有事件通知。 有 ApplicationFailedEvent——启动过程任何阶段抛异常都会发布它,可以监听它做告警、清理、记录失败原因(配合 FailureAnalyzer 分析失败)。
- 追问:ApplicationReadyEvent 和 CommandLineRunner 有什么区别,谁先谁后? 时机接近(都在应用就绪后),Runner 先执行、执行完才发 ApplicationReadyEvent;Runner 更直接(写一段初始化代码),ReadyEvent 语义更明确且能被多个监听器解耦处理;一般用哪个都行,能用 @EventListener(ApplicationReadyEvent) 就用它。
- 追问:日志系统为什么能在极早期就初始化? Spring Boot 通过 spring.factories 注册了监听 ApplicationStartingEvent/ApplicationEnvironmentPreparedEvent 的监听器(如 LoggingApplicationListener),这些监听器不依赖容器,在启动极早期就被 SpringApplicationRunListener 广播到,从而能在环境准备阶段就初始化日志系统。
- 追问:这些事件是谁发布的? SpringApplicationRunListener(默认实现 EventPublishingRunListener)——SpringApplication.run() 在各阶段调用它的 starting/environmentPrepared/started/ready/failed 等方法,由它内部的事件广播器把事件广播给所有注册的 ApplicationListener。
八、加强记忆
Spring Boot 启动时按顺序发布一系列「应用事件」,监听它们可在启动特定阶段插入逻辑。七个事件(按发布顺序,核心是「此时什么就绪了」):① ApplicationStartingEvent(刚开始,Environment/容器都没有)→ ② ApplicationEnvironmentPreparedEvent(配置/profile 就绪、容器没有)→ ③ ApplicationContextInitializedEvent(容器建好、Bean 定义没有)→ ④ ApplicationPreparedEvent(Bean 定义就绪、没实例化)→ ⑤ ApplicationStartedEvent(refresh 完、Bean 就绪、Runner 没执行)→ ⑥ ApplicationReadyEvent(Runner 也跑完、完全就绪,最常用)→ ⑦ ApplicationFailedEvent(启动出错时)。监听方式:@EventListener 靠容器事件机制,只能监听容器就绪后的事件(Started/Ready);早期事件(Starting/EnvironmentPrepared)容器还没起来,要用 SpringApplication.addListeners() 或 META-INF/spring.factories 注册 ApplicationListener(不依赖容器,Spring Boot 启动极早期就加载)。事件的发布者是 SpringApplicationRunListener(默认 EventPublishingRunListener 广播)。业务初始化用 ApplicationReadyEvent 或 Runner(Runner 先于 ReadyEvent)。一句话「Spring Boot 启动事件按序:Starting→EnvironmentPrepared→ContextInitialized→Prepared→Started→Ready(最常用)→Failed;@EventListener 只能监听容器就绪后的(Started/Ready),早期事件用 addListeners/spring.factories 注册 ApplicationListener;发布者 SpringApplicationRunListener;Runner 先于 ReadyEvent」。