Spring Boot 启动流程
候选人小刘在面试美团P6时,被问到:
"Spring Boot 的启动流程说一下,从 main 方法开始。"
小刘说:"先创建 SpringApplication,然后调用 run 方法。"面试官追问:"run 方法里面做了什么?"小刘说:"就是启动 Spring 容器吧?"
面试官拿起笔:"那 prepareContext 做了什么?refreshContext 呢?"小刘卡住了。
面试官又问:"SpringApplicationRunListener 是什么时候触发的?ApplicationContextInitializer 呢?"小刘彻底答不上来。
【面试官心理】
这道题我用来测试候选人对 Spring Boot 启动链路的整体理解。能说出"new SpringApplication 然后 run"的占 80%,能说出 run 方法中关键步骤的占 30%,能完整画出启动流程图并说出每个扩展点的只有 5%。这道题太细节了,大多数人只用过,没研究过。
一、启动链路全解析 🔴
1.1 从 main 方法说起
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
SpringApplication.run() 实际上做了两件事:
public static ConfigurableApplication run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
new SpringApplication(primarySources):创建 SpringApplication 实例
run(args):执行启动流程
1.2 new SpringApplication 做了什么
public SpringApplication(Class<?>... primarySources) {
// 1. 保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 推断应用类型:REACTIVE / SERVLET / NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3. 设置 BootstrapRegistryInitializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 4. 设置 ApplicationListener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 5. 推断主入口类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
📖 点击展开应用类型推断逻辑
// WebApplicationType.deduceFromClasspath()
static WebApplicationType deduceFromClasspath() {
// 如果有 REACTIVE 包但没有 SERVLET 包 → REACTIVE
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)
&& !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) {
return WebApplicationType.REACTIVE;
}
// 如果没有 SERVLET 相关类 → NONE(非 Web 应用)
for (String className : SERVLET_INDICATIVE_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 否则 → SERVLET(传统 Web 应用)
return WebApplicationType.SERVLET;
}
1.3 run 方法的核心流程
public ConfigurableApplication run(String... args) {
// 1. 创建计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 创建 BootstrapContext(引导上下文)
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 3. 配置 Headless 属性(无头模式)
configureHeadlessProperty();
// 4. 获取并启动 SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
// 5. 准备 Environment
ConfigurableEnvironment environment = prepareEnvironment(
bootstrapContext, listeners, applicationArguments);
// 6. 打印 Banner
Banner printedBanner = printBanner(environment);
// 7. 创建 ApplicationContext
ConfigurableApplicationContext context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 8. 准备 Context(关键步骤)
prepareContext(bootstrapContext, context, environment, listeners,
applicationArguments, printedBanner);
// 9. 刷新 Context(核心:创建 Bean、启动服务器等)
refreshContext(context);
// 10. 刷新后处理
afterRefresh(context, applicationArguments);
// 11. 停止计时器并发布 ApplicationReadyEvent
stopWatch.stop();
listeners.ready(context, this.typedApplicationArguments);
return context;
}
📖 点击展开启动流程 Mermaid 图
flowchart TD
A[main 方法] --> B[new SpringApplication]
B --> C[推断应用类型]
B --> D[加载 Initializers]
B --> E[加载 Listeners]
B --> F[deduceMainApplicationClass]
F --> G[run 方法]
G --> H[创建 BootstrapContext]
H --> I[启动 SpringApplicationRunListeners.starting]
I --> J[prepareEnvironment<br/>加载配置 + profile]
J --> K[printBanner]
K --> L[createApplicationContext]
L --> M[prepareContext<br/>加载 BeanDefinition<br/>执行 Initializer]
M --> N[refreshContext<br/>BeanFactoryPostProcessor<br/>BeanPostProcessor<br/>实例化单例Bean]
N --> O[afterRefresh]
O --> P[listeners.ready]
P --> Q[应用启动完成]
二、prepareContext 详解 🟡
2.1 prepareContext 的四个步骤
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 1. 设置 Environment
context.setEnvironment(environment);
// 2. 配置 ResourceLoader 和 ClassLoader
postProcessApplicationContext(context);
// 3. 应用 Initializers
applyInitializers(context);
// 4. 加载 Bean 定义
load(context, sources.toArray(new Object[0]));
}
2.2 ApplicationContextInitializer 的触发时机
protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍历所有 Initializers,逐个调用 initialize()
for (ApplicationContextInitializer initializer : this.initializers) {
initializer.initialize(context);
}
}
ApplicationContextInitializer 是在 refreshContext 之前执行的扩展点,用于在容器刷新前做预处理:
// 自定义 Initializer 示例
public class MyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
// 在容器刷新前执行:修改 BeanFactory、设置环境变量等
context.getBeanFactory().registerSingleton("myService", new MyService());
}
}
// 注册方式:在 spring.factories 中声明
# META-INF/spring.factories
org.springframework.boot.SpringApplicationRunListener=\
com.example.MyInitializer
⚠️
ApplicationContextInitializer 是在 prepareContext 阶段执行的,此时 Bean 定义还没加载,容器还没刷新。如果你在这里调用 context.getBean(),会直接抛出 NoSuchBeanDefinitionException。
三、refreshContext 核心流程 🟡
3.1 refreshContext 等价于 Spring 的 refresh
Spring Boot 的 refreshContext 调用的是 Spring Framework 的 AbstractApplicationContext.refresh():
protected void refresh(ApplicationContext applicationContext) {
((AbstractApplicationContext) applicationContext).refresh();
}
refresh() 的完整步骤:
public void refresh() {
// 1. 准备 BeanFactory
prepareBeanFactory(beanFactory);
// 2. 允许子类修改 BeanFactory
postProcessBeanFactory(beanFactory);
// 3. 调用 BeanFactoryPostProcessor(包括 ConfigurationClassPostProcessor)
invokeBeanFactoryPostProcessors(beanFactory);
// 4. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 5. 初始化消息源
initMessageSource();
// 6. 初始化事件广播器
initApplicationEventMulticaster();
// 7. 初始化特定上下文子类
onRefresh();
// 8. 注册监听器
registerListeners();
// 9. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 10. 初始化 LifecycleProcessor
finishRefresh();
}
3.2 Spring Boot 的 onRefresh
Spring Boot 在 onRefresh() 阶段做了额外的事情:
@Override
protected void onRefresh() {
super.onRefresh();
// 创建 Web 服务器(Tomcat/Undertow/Jetty)
createWebServer();
}
createWebServer() 是 Spring Boot 引入 Web 服务器的关键步骤。
四、SpringApplicationRunListener 触发时机 🟡
4.1 五个回调方法
SpringApplicationRunListener 定义了 5 个生命周期回调:
public interface SpringApplicationRunListener {
// 应用刚启动(run 方法开始时)
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass);
// Environment 准备好之后
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment);
// ApplicationContext 准备好之后
void contextPrepared(ConfigurableApplicationContext context);
// ApplicationContext 加载完成、refresh 之前
void contextLoaded(ConfigurableApplicationContext context);
// 应用启动完成
void ready(ConfigurableApplicationContext context,
ApplicationArguments arguments);
}
sequenceDiagram
participant SA as SpringApplication
participant Listener as SpringApplicationRunListener
participant Context as ApplicationContext
SA->>Listener: starting()
Note over Listener: 应用刚启动
SA->>Listener: environmentPrepared()
Note over Listener: Environment 配置完成
SA->>Listener: contextPrepared()
Note over Listener: Context 创建完成
SA->>Listener: contextLoaded()
Note over Listener: Bean 定义加载完成
SA->>Context: refresh()
Note over Context: 容器刷新
SA->>Listener: ready()
Note over Listener: 应用就绪
4.2 自定义 SpringApplicationRunListener
// 必须是 SpringApplication 的构造函数的参数
public class MyListener implements SpringApplicationRunListener {
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
long start = System.currentTimeMillis();
System.out.println("应用启动开始...");
}
@Override
public void ready(ConfigurableApplicationContext context, ApplicationArguments arguments) {
long duration = System.currentTimeMillis() - startTime;
System.out.println("应用启动完成,耗时: " + duration + "ms");
}
}
五、❌ 错误示范
5.1 只会说皮毛
候选人原话:"Spring Boot 启动就是创建 SpringApplication 然后调用 run 方法,run 方法会启动 Spring 容器。"
问题诊断:
- 说不清 run 方法中的具体步骤
- 不知道 prepareContext、refreshContext、afterRefresh 的区别
- 不理解 SpringApplicationRunListener 和 ApplicationContextInitializer 的触发时机
面试官内心 OS:"这个候选人肯定没看过 Spring Boot 的源码。run 方法里有十几个步骤,他只说了一个'启动容器'。"
5.2 张冠李戴
候选人原话:"refreshContext 里面就是注册 Bean,BeanFactoryPostProcessor 是在 prepareContext 里执行的。"
问题诊断:
- 把执行顺序搞混了
- BeanFactoryPostProcessor 是在 refresh 的 invokeBeanFactoryPostProcessors 阶段执行的,不是在 prepareContext
六、标准回答
6.1 P5 级别
"Spring Boot 启动流程是:先 new SpringApplication 设置各种属性,然后调用 run 方法。run 方法中会准备 Environment、创建 ApplicationContext、加载 Bean 定义、启动 Web 服务器。"
6.2 P6 级别
"Spring Boot 启动分两个阶段:构造 SpringApplication 和执行 run。
构造阶段做四件事:推断应用类型(REACTIVE/SERVLET/NONE)、加载 ApplicationContextInitializer、加载 ApplicationListener、推断主入口类。
run 方法的流程是:启动 BootstrapContext → listeners.starting → prepareEnvironment → createApplicationContext → prepareContext → refreshContext → afterRefresh → listeners.ready。
其中 prepareContext 会调用 Initializers、加载 Bean 定义;refreshContext 调用 Spring 容器的 refresh(),完成 BeanFactoryPostProcessor 调用、BeanPostProcessor 注册、单例 Bean 实例化;afterRefresh 做刷新后的额外处理。SpringApplicationRunListener 有 5 个回调方法,分别在启动的不同阶段触发。"
6.3 P7 级别
"这道题背后考的是 Spring Boot 的扩展点设计。我在项目中用过两个扩展点:ApplicationContextInitializer 用于在容器刷新前注册一些系统级的单例 Bean;SpringApplicationRunListener 用于做启动耗时监控。
Spring Boot 2.4+ 引入的 DefaultBootstrapContext 是新的扩展点,比 SpringApplicationRunListener 更早执行,适用于需要共享状态的场景。
容器刷新阶段最耗时的是 finishBeanFactoryInitialization(实例化所有单例 Bean)。如果启动慢,重点排查这个阶段——通常是 DataSource 初始化、Redis 连接池初始化、或者大量 @Bean 的懒加载没生效。"
【面试官心理】
P7 的回答重点在于"扩展点使用"和"性能优化"。能说出具体在哪个扩展点踩过坑、做过优化的候选人,说明他对 Spring Boot 的理解已经进入了工程化层面。