SpringBoot 的启动流程是怎样的?
结论先行:SpringApplication.run() 是一条编排好的流水线:准备环境 → 创建 ApplicationContext → 准备上下文(加载主配置类、执行 Initializer、广播事件)→ refresh(IoC 容器的主线,内嵌 Web 容器在此启动)→ 收尾(执行 Runner、发布 ready 事件)。面试讲清两段即可:prepareContext 做了什么,refresh() 里 onRefresh 与 finishBeanFactoryInitialization 的先后。
一、主流程
SpringApplication.run(主类, args)
├─ 1. 构造 SpringApplication:推断 WebApplicationType、加载 Initializer 与 Listener
├─ 2. run()
│ ├─ 启动计时、发布 ApplicationStartingEvent
│ ├─ 准备 Environment(加载 application.yml/properties、命令行参数、profile)
│ ├─ 打印 Banner、创建 ApplicationContext(按 Web 类型选实现类)
│ ├─ prepareContext
│ │ ├─ 设置 environment、注册主配置类(primarySources)
│ │ ├─ 执行 ApplicationContextInitializer
│ │ └─ 发布 ApplicationContextInitializedEvent / PreparedEvent
│ ├─ refreshContext(ctx) ← 核心
│ │ └─ AbstractApplicationContext#refresh()
│ │ ├─ obtainFreshBeanFactory
│ │ ├─ invokeBeanFactoryPostProcessors ← 解析配置类、自动配置
│ │ ├─ registerBeanPostProcessors ← AOP、@Autowired 处理器
│ │ ├─ onRefresh ← 内嵌 Tomcat 创建并启动
│ │ ├─ finishBeanFactoryInitialization ← 实例化所有单例
│ │ └─ finishRefresh ← 发布 ContextRefreshedEvent
│ ├─ afterRefresh:调用 ApplicationRunner / CommandLineRunner
│ ├─ 发布 ApplicationReadyEvent(此时才算可接流量)
│ └─ 异常 → 发布 ApplicationFailedEvent,进程退出
二、关键阶段与产物
| 阶段 | 关键方法 | 产物/事件 |
|---|---|---|
| 环境准备 | prepareEnvironment | Environment(含 profile、配置源) |
| 上下文创建 | createApplicationContext | AnnotationConfigServletWebServerApplicationContext |
| 上下文准备 | prepareContext | 主配置类注册、Initializer 执行 |
| 容器刷新 | refreshContext | 全部单例 Bean、内嵌 Web 容器 |
| 收尾 | afterRefresh / callRunners | Runner 执行、ready 事件 |
三、用代码观察启动过程
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
// 1. 自定义启动监听器:观察各阶段
app.addListeners((ApplicationListener<ApplicationEvent>) event ->
System.out.println("[event] " + event.getClass().getSimpleName()));
// 2. 设置默认属性(优先级低于命令行与配置文件)
app.setDefaultProperties(Map.of("server.port", "8081"));
// 3. 关闭 Banner、控制懒加载
app.setBannerMode(Banner.Mode.OFF);
app.setLazyInitialization(false);
app.run(args);
}
}
// Runner 执行时机:容器 refresh 完成、ready 事件之前
@Component
public class StartupTask implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("Runner 执行,此时 Bean 与 Web 容器均已就绪");
}
}
四、两个高频追问点
① 自动配置发生在哪一步? 在 invokeBeanFactoryPostProcessors:ConfigurationClassPostProcessor 解析 @SpringBootApplication,其中 @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 拿到候选配置类,经 @Conditional 过滤后注册 BeanDefinition。此时还没有任何业务 Bean 被实例化。
② 内嵌 Tomcat 在哪一步启动? 在 refresh() 的 onRefresh():ServletWebServerApplicationContext#onRefresh → createWebServer() → TomcatServletWebServerFactory#getWebServer 创建并启动 Tomcat,随后 finishRefresh 里 WebServerStartStopLifecycle 完成端口绑定与 Connector 启动。
五、常见追问
- 问:
CommandLineRunner与ApplicationRunner区别? 前者拿原始String[],后者拿解析后的ApplicationArguments(支持--key=value形式);执行顺序可用@Order控制。 - 问:启动失败怎么定位? 看
ApplicationFailedEvent与FailureAnalyzer输出的报告;--debug可打印自动配置条件评估结果。 - 问:
refresh()能调两次吗? 不能,AbstractApplicationContext有active标志,重复 refresh 会抛IllegalStateException。 - 问:Spring Cloud 的 bootstrap 上下文在哪? 在
prepareEnvironment阶段由BootstrapApplicationListener触发,父上下文先创建,用于拉取配置中心数据,所以配置中心的值能在主上下文初始化前生效。
小结
把启动流程压成五个词:环境 → 上下文 → 准备 → 刷新 → 收尾。再加两个锚点——自动配置在 invokeBeanFactoryPostProcessors、Tomcat 在 onRefresh——足以应对大部分追问,也能解释"为什么配置类里能拿到 Environment,但 Bean 还没创建"。