SpringBoot启动流程
开篇:按下 Run 之后发生了什么?
在 IDE 里点一下那个绿色的 Run 按钮,控制台刷刷刷地滚动日志,几秒钟后出现一行 Started Application in 3.456 seconds——你的 SpringBoot 应用就跑起来了。
但是,这短短几秒钟里到底发生了什么?Spring 容器是怎么创建的?Bean 是什么时候初始化的?内嵌的 Tomcat 又是在哪一步启动的?
理解启动流程,不仅是面试的高频考点,更能帮你排查启动失败、Bean 加载顺序、自动配置不生效等实际问题。这篇文章会带你一步步拆解 SpringBoot 的启动过程。
一、SpringApplication 构造
一切始于这行代码:
SpringApplication.run(Application.class, args);这行代码做了两件事:先 new SpringApplication(),再调用它的 run() 方法。
构造阶段做了什么?
public SpringApplication(Class<?>... primarySources) {
// 1. 保存主配置类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 判断应用类型(Servlet / Reactive / None)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3. 从 spring.factories 加载初始化器
setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 4. 从 spring.factories 加载监听器
setListeners(getSpringFactoriesInstances(ApplicationListener.class));
// 5. 推断主应用类(包含 main 方法的类)
this.mainApplicationClass = deduceMainApplicationClass();
}逐步解释:
判断应用类型:通过检查 classpath 中是否存在 DispatcherServlet(Servlet 应用)或 DispatcherHandler(Reactive 应用)等类来判断。不同类型会在后面创建不同的 ApplicationContext。
加载初始化器:ApplicationContextInitializer 是在 ApplicationContext 创建后、refresh 之前执行的回调。Spring 从所有 jar 包的 spring.factories 文件中查找并实例化这些初始化器。
加载监听器:ApplicationListener 用于监听 Spring 应用生命周期中的各种事件(启动、环境准备完成、上下文加载完成等)。
推断主类:通过遍历调用栈,找到包含 main 方法的类。这个信息后面会用于日志输出。
构造阶段可以理解为"准备阶段"——把后面 run 方法需要的零件都找齐备好。
二、run() 方法核心流程
构造完成后,进入最关键的 run() 方法。整个流程可以用下面的流程图概括:
下面逐步拆解每一步。
第 1-3 步:准备工作
// 1. 启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 创建引导上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 3. 配置 Headless 模式(无显示器环境下的 AWT 支持)
configureHeadlessProperty();计时器最终会输出那行 Started Application in x.xxx seconds。引导上下文为启动过程中的早期组件提供存储。Headless 模式对 Web 应用影响不大,但服务端环境通常没有图形界面,需要设置这个属性。
第 4-5 步:获取并启动监听器
// 4. 从 spring.factories 中获取所有 SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 5. 通知所有监听器:应用正在启动
listeners.starting(bootstrapContext, this.mainApplicationClass);SpringApplicationRunListener 是启动过程中的"播报员",在每个关键节点发出事件通知。默认实现是 EventPublishingRunListener,它会把事件广播给所有的 ApplicationListener。
它在整个启动过程中会依次广播这些事件:
| 方法 | 时机 | 对应事件 |
|---|---|---|
starting() | 启动开始 | ApplicationStartingEvent |
environmentPrepared() | 环境准备完成 | ApplicationEnvironmentPreparedEvent |
contextPrepared() | 上下文创建完成 | ApplicationContextInitializedEvent |
contextLoaded() | 上下文加载完成 | ApplicationPreparedEvent |
started() | 应用启动完成 | ApplicationStartedEvent |
running() | 完全就绪 | ApplicationReadyEvent |
failed() | 启动失败 | ApplicationFailedEvent |
第 6-7 步:准备环境
// 6. 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 7. 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);prepareEnvironment() 会创建 StandardServletEnvironment 对象,加载所有的配置源:
application.yml/application.properties- 命令行参数
- 环境变量
- JVM 系统属性
这一步完成后,你在配置文件中写的所有属性就可以被访问了。同时通知监听器 environmentPrepared。
第 8 步:创建 ApplicationContext
context = createApplicationContext();根据第一步推断的应用类型创建不同的上下文:
- Servlet 应用 ->
AnnotationConfigServletWebServerApplicationContext - Reactive 应用 ->
AnnotationConfigReactiveWebServerApplicationContext - 非 Web 应用 ->
AnnotationConfigApplicationContext
上下文创建出来后,还只是一个空壳,里面没有任何 Bean。
第 9 步:准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);这一步做了很多事情:
- 将 Environment 设置到上下文中
- 执行 Bean 工厂的后处理
- 执行所有的 ApplicationContextInitializer:遍历构造阶段加载的初始化器,依次调用
initialize()方法 - 通知监听器
contextPrepared - 注册一些特殊的单例 Bean(如
springApplicationArguments、springBootBanner) - 加载所有的 Bean 定义(从主配置类开始扫描)
- 通知监听器
contextLoaded
注意区分:这一步只是"加载 Bean 定义"——也就是告诉 Spring 有哪些 Bean 需要创建,但还没有真正实例化。
第 10 步:刷新上下文(最核心)
refreshContext(context);这一步调用的是 Spring 框架的 refresh() 方法——这是整个 Spring 容器启动的核心,内容非常多:
public void refresh() {
// 1. 准备 BeanFactory
prepareBeanFactory(beanFactory);
// 2. 执行 BeanFactory 后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 3. 注册 Bean 后处理器
registerBeanPostProcessors(beanFactory);
// 4. 初始化消息源(国际化)
initMessageSource();
// 5. 初始化事件多播器
initApplicationEventMulticaster();
// 6. onRefresh()(重点!Web 服务器在这里启动)
onRefresh();
// 7. 注册监听器
registerListeners();
// 8. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 9. 发布刷新完成事件
finishRefresh();
}其中 onRefresh() 是一个很多人忽略但极其重要的步骤——内嵌 Tomcat 就是在这里启动的。
内嵌 Tomcat 的启动
在 ServletWebServerApplicationContext 的 onRefresh() 方法中,会调用 createWebServer():
private void createWebServer() {
ServletWebServerFactory factory = getWebServerFactory();
// factory 默认是 TomcatServletWebServerFactory
this.webServer = factory.getWebServer(getSelfInitializer());
}TomcatServletWebServerFactory.getWebServer() 的核心逻辑:
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
// 设置基础目录、连接器、引擎等
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
tomcat.setConnector(connector);
// 准备上下文
prepareContext(tomcat.getHost(), initializers);
// 创建并启动 TomcatWebServer
return getTomcatWebServer(tomcat);
}最终在 TomcatWebServer 的构造函数中调用 initialize() 完成 Tomcat 的启动。启动完成后,你的应用就可以接收 HTTP 请求了。
除了 Tomcat,SpringBoot 还支持 Jetty 和 Undertow。切换只需要修改 pom.xml 中的依赖。
第 11-14 步:收尾工作
// 11. afterRefresh:刷新后的扩展点(默认空实现)
afterRefresh(context, applicationArguments);
// 12. 停止计时器,通知监听器 started
stopWatch.stop();
listeners.started(context);
// 13. 执行所有的 Runner
callRunners(context, applicationArguments);
// 14. 通知监听器 running
listeners.running(context);callRunners 是一个实用的扩展点。Spring 会查找容器中所有实现了 ApplicationRunner 或 CommandLineRunner 接口的 Bean,按 @Order 排序后依次执行它们的 run() 方法。适合做一些应用启动后的初始化操作:
@Component
@Order(1)
public class DataInitRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 应用启动后执行,比如预热缓存
cacheService.warmUp();
}
}如果以上任何步骤出现异常,会调用 listeners.failed() 通知所有监听器,然后抛出异常终止启动。
三、自动装配的加载时机
在第 10 步 refreshContext 中,invokeBeanFactoryPostProcessors() 会处理所有的 BeanFactoryPostProcessor。在这个过程中,ConfigurationClassPostProcessor 会解析 @Configuration 类上的注解,包括 @EnableAutoConfiguration。
这时候 AutoConfigurationImportSelector 的 selectImports() 方法被调用,加载所有候选的自动配置类。然后这些配置类经过 @Conditional 条件过滤后,有效的 Bean 定义被注册到容器中。
最终在 finishBeanFactoryInitialization() 阶段,这些 Bean 被真正实例化。
所以自动装配的加载时机是:refresh 阶段的 BeanFactoryPostProcessor 处理过程中。
四、内嵌 Tomcat 启动
上面已经详细介绍了 Tomcat 的启动位置(onRefresh -> createWebServer)。这里补充几个实用知识点:
修改端口
server:
port: 9090切换 Web 服务器
排除 Tomcat,引入 Undertow:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>SpringBoot 会自动检测 classpath 中有哪个 Web 服务器的工厂类,然后使用对应的实现。这也是 @Conditional 机制在发挥作用。
五、常见面试题精选
1. SpringBoot 的启动流程?
分两大阶段。构造阶段:推断应用类型、从 spring.factories 加载初始化器和监听器、确定主类。run 阶段:启动计时 -> 获取 RunListeners -> 准备 Environment -> 创建 ApplicationContext -> 准备上下文(加载 Bean 定义)-> 刷新上下文(实例化 Bean + 启动 Tomcat)-> 执行 Runners -> 发布就绪事件。
2. SpringBoot 是如何实现 main 方法启动 Web 项目的?
核心是内嵌 Web 服务器。在 refresh() 的 onRefresh() 阶段,ServletWebServerApplicationContext 会调用 createWebServer() 方法,通过 TomcatServletWebServerFactory 创建并启动一个内嵌的 Tomcat 实例。所以不需要外部容器,java -jar 就能直接运行。
3. 自动配置在启动流程中的哪个阶段生效?
在 refreshContext 的 invokeBeanFactoryPostProcessors 阶段。ConfigurationClassPostProcessor 解析 @EnableAutoConfiguration,触发 AutoConfigurationImportSelector 加载候选配置类,经 @Conditional 过滤后注册有效的 Bean 定义。
4. ApplicationRunner 和 CommandLineRunner 的区别?
两者都在应用启动完成后执行,都可以用 @Order 排序。区别在于参数类型:ApplicationRunner 接收 ApplicationArguments(结构化参数),CommandLineRunner 接收 String[](原始命令行参数)。
小结
SpringBoot 的启动流程可以用一句话概括:构造 SpringApplication 准备零件,run 方法按顺序组装并启动。
整个过程中有三个关键节点:
- prepareContext:加载 Bean 定义(告诉 Spring 有哪些 Bean 要创建)
- refreshContext:实例化所有 Bean + 启动内嵌 Web 服务器(应用真正可用)
- callRunners:执行初始化逻辑(预热缓存、初始化数据等)
理解了这个流程,当应用启动报错时,你就能根据报错信息和日志定位到具体是哪个阶段出了问题,而不是对着一堆堆栈信息一脸茫然。