Spring Boot 启动流程与生命周期详解
定位:第 07 篇,讲透 SpringApplication 启动全流程、各启动事件与扩展点、启动后任务、优雅停机
适用版本:Spring Boot 3.x(JDK 17+)
前置:容器 refresh 流程见 FW-01篇
目录
一、启动入口
SpringApplication.run() 分两个阶段:
1.1 构造阶段(准备)
new SpringApplication(primarySources)
├── 推断应用类型:SERVLET / REACTIVE / NONE(按 classpath 判断)
├── 加载 ApplicationContextInitializer(从 imports/spring.factories)
├── 加载 ApplicationListener
└── 推断主应用类(main 所在类)
应用类型推断 决定了用哪种 ApplicationContext:Servlet 应用用 ServletWebServerApplicationContext(内嵌容器),响应式用 ReactiveWebServerApplicationContext。
1.2 run 阶段(执行)
依次执行启动步骤并在关键点发布事件(第二节)。核心骨架:
① 启动计时,创建引导上下文
② 准备环境(Environment):加载配置、应用 profile
③ 创建 ApplicationContext(按应用类型)
④ 准备上下文:注册主配置类、执行 Initializer
⑤ 刷新上下文 refresh():组件扫描、自动配置、实例化单例、起内嵌容器
⑥ 刷新后处理 + 执行 Runner
⑦ 发布 ApplicationReadyEvent
二、启动步骤与事件
事件是启动流程的"观测点",按序发布:
| 事件 | 时机 | 可做什么 |
|---|---|---|
ApplicationStartingEvent |
刚启动,任何处理前 | 最早的钩子 |
ApplicationEnvironmentPreparedEvent |
环境就绪、配置已加载 | 依据配置做决策 |
ApplicationContextInitializedEvent |
上下文创建、Initializer 执行后 | --- |
ApplicationPreparedEvent |
Bean 定义已加载,未刷新 | 注册额外 Bean |
ContextRefreshedEvent |
容器刷新完成(单例就绪) | 框架级初始化(FW-01) |
ApplicationStartedEvent |
启动完成、Runner 未跑 | --- |
ApplicationReadyEvent |
完全就绪,可接收请求 | 业务就绪动作 ★ |
ContextClosedEvent |
关闭前 | 清理资源 |
记忆锚点:EnvironmentPrepared 是配置就绪,Refreshed 是容器就绪,Ready 是可接流量。
三、扩展点
3.1 ApplicationContextInitializer
在上下文 refresh() 之前对上下文编程式定制(注册属性源、激活 profile、注册 Bean):
java
public class CustomInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext ctx) { /* 刷新前定制 */ }
}
3.2 ApplicationListener
监听第二节各事件,把自定义逻辑挂到启动/关闭的精确时点(如 Ready 时预热缓存)。
3.3 启动后一次性任务(高频)
java
@Component
public class WarmupRunner implements ApplicationRunner { // 或 CommandLineRunner
public void run(ApplicationArguments args) {
// 容器就绪后执行一次:预热缓存、数据初始化、自检
}
}
| 接口 | 参数 |
|---|---|
CommandLineRunner |
原始 String\[\] |
ApplicationRunner |
封装的 ApplicationArguments(解析命名参数) |
用 @Order 控制多个 Runner 顺序;Runner 抛异常会导致启动失败(适合做"启动自检")。
3.4 注册扩展的方式
ApplicationContextInitializer/ApplicationListener 可通过 AutoConfiguration.imports/spring.factories、SpringApplicationBuilder、spring.properties 声明;Runner 直接 @Component 即可。
四、优雅停机
4.1 停机触发与流程
SIGTERM(kill)/ shutdown 端点 / context.close()
▼
① server.shutdown=graceful:Web 容器停止接收新请求,等待在途请求(超时上限内)
② 发布 ContextClosedEvent → 监听器清理(停定时任务、关连接池)
③ 销毁单例(@PreDestroy 等,FW-01)
④ 关闭容器,进程退出
4.2 配合无损下线
发布流程(呼应无损上下线):
负载均衡摘流 → 宽限等待 → 优雅停机(等在途)→ 进程退出
仅靠优雅停机不够------已建立的客户端连接在途请求需要摘流先行。
五、总结
- 两阶段启动:构造期推断应用类型并加载 Initializer/Listener;run 期准备环境 → 建上下文 → refresh(起容器、实例化单例)→ 跑 Runner → Ready。
- 事件序列 :Starting → EnvironmentPrepared → ...Prepared → Refreshed → Started → Ready(可接流量);是挂钩启动逻辑的观测点。
- 扩展点:Initializer(刷新前定制)、Listener(事件挂钩)、ApplicationRunner/CommandLineRunner(启动后一次性任务,可做自检)。
- 优雅停机:graceful 停接新请求等在途 + ContextClosedEvent 清理 + Bean 销毁;配合摘流实现无损下线。
六、常见高频面试题
1. 详细描述 Spring Boot 的启动流程。
要点:SpringApplication.run 两阶段。构造阶段:推断应用类型(Servlet/Reactive)、加载 ApplicationContextInitializer 与 ApplicationListener。run 阶段:创建并准备 Environment(加载配置、激活 profile)→ 按类型创建 ApplicationContext → 准备上下文(注册主配置、执行 Initializer)→ refresh(组件扫描、自动配置、实例化非懒加载单例、启动内嵌容器)→ 执行 Runner → 发布 ApplicationReadyEvent。全程伴随启动事件发布。
2. SpringApplication 如何推断应用类型?
要点:按 classpath 判断------存在 Servlet 相关类(如 jakarta.servlet.Servlet + Spring 的 DispatcherServlet)判为 SERVLET,用 ServletWebServerApplicationContext;存在 Spring WebFlux 的 DispatcherHandler 且无 Servlet 判为 REACTIVE;否则 NONE(普通上下文)。这决定了容器类型与 Web 栈。也可通过 setWebApplicationType 显式指定。
3. ApplicationReadyEvent 和 ContextRefreshedEvent 的区别?
要点:ContextRefreshedEvent 在容器刷新完成(所有单例实例化、内嵌容器启动)时发布,是 Framework 层的就绪信号;ApplicationReadyEvent 是 Boot 层信号,在 Runner 执行后发布,表示应用完全就绪、可以接收外部请求。业务"启动后动作"(预热、注册服务、开始消费)推荐监听 Ready;框架级初始化可用 Refreshed。
4. CommandLineRunner 和 ApplicationRunner 的区别与用途?
要点:都在容器就绪后执行一次性任务,都可用 @Order 控制顺序。区别在参数:CommandLineRunner 接收原始 String\[\],ApplicationRunner 接收封装的 ApplicationArguments(可解析 --name=value 命名参数,更友好)。用途:数据初始化、缓存预热、启动自检(抛异常可让启动失败,健康防线)。注意它们运行一次,不是定时任务。
5. ApplicationContextInitializer 有什么用?
要点:在 ApplicationContext refresh() 之前编程式定制上下文的钩子------如动态激活 profile、注册属性源、注册额外 Bean、调整上下文行为。通过 spring.factories/imports、SpringApplicationBuilder 或配置注册。典型场景:根据环境变量注入配置源、测试时替换上下文实现。它在生命周期极早期生效,早于 Bean 定义加载。
6. Spring Boot 如何实现优雅停机?
要点:配置 server.shutdown=graceful 后,收到停机信号(SIGTERM/close)时 Web 容器停止接收新请求,等待在途请求完成(超时由 spring.lifecycle.timeout-per-shutdown-phase 控制),同时发布 ContextClosedEvent 让监听器清理资源(停定时任务、关连接),再销毁单例执行 @PreDestroy,最后退出。配合负载均衡摘流与宽限期实现发布零中断。
7. 启动事件有哪些?分别什么时候触发?
要点:按时序------ApplicationStartingEvent(刚启动)→ ApplicationEnvironmentPreparedEvent(环境/配置就绪)→ ApplicationContextInitializedEvent(Initializer 执行后)→ ApplicationPreparedEvent(Bean 定义加载、未刷新)→ ContextRefreshedEvent(容器刷新、单例就绪)→ ApplicationStartedEvent(启动完成)→ ApplicationReadyEvent(可接请求);关闭时 ContextClosedEvent。是挂载启动/关闭逻辑的精确钩子。
8. 为什么"启动自检"建议放在 Runner 里并让失败阻断启动?
要点:关键依赖(配置、下游连通、必要数据)若缺失,宁可启动失败(快速失败)也不要带病上线接流量造成更大故障。Runner 在容器就绪后执行、能拿到全部 Bean,抛异常会使启动失败------适合做自检(探下游、校验配置完整性)。配合部署系统的健康检查与回滚,形成"起不来就不接流量"的保护。
9. 应用启动很慢,如何分析和优化?
要点:分析:观察启动日志各阶段耗时(Boot 会输出启动时间与阶段);开条件评估与 Bean 初始化耗时(如 ApplicationStartup 的 BufferingApplicationStartup + Actuator 或 startup 端点)。优化:缩小组件扫描范围、重资源 Bean 改 @Lazy、排除不需要的自动配置(减少条件评估与装配)、减少启动期同步初始化(移到 Runner 异步或按需)。目标:缩短就绪时间,加快弹性伸缩。
10. Boot 应用关闭时,各资源的关闭顺序是怎样的?
要点:大致逆序。① Web 容器先停接新请求(若开优雅停机则在途请求等待);② 发布 ContextClosedEvent,监听器执行清理(停调度、关消费者);③ 销毁单例:执行 @PreDestroy、DisposableBean、destroy-method(FW-01 销毁回调顺序);④ 关闭 ApplicationContext 与底层资源(连接池等)。要点:关闭是"先停入口、再清任务、后毁资源",自定义资源应通过 DisposableBean/@PreDestroy 或监听 ContextClosedEvent 纳入统一管理。
