这是本专栏的最后一篇。
在前面十八篇里,我们拆解了 Servlet 规范、IoC 容器、AOP、事务、MyBatis、Spring MVC------这些是 Spring 框架和生态的"零件"。而 Spring Boot 的职责,就是把这些零件按照正确的顺序组装起来,让整个机器能够一键启动。
你每天写的 SpringApplication.run(Application.class, args),背后到底发生了什么?为什么一行代码就能启动一个 Web 服务器、加载所有配置、扫描所有 Bean、打开数据库连接?
这一篇,我们把 Spring Boot 的启动过程从头到尾走一遍。
学习目标
- 理解
SpringApplication.run()的核心流程 - 掌握 Spring Boot 启动的三大阶段(SpringApplication 初始化 → 运行 → 刷新容器)
- 理解 Spring Boot 的事件机制 (
ApplicationEvent、ApplicationListener) - 了解 Spring Boot 的自动配置在启动流程中的加载时机
正文
一、SpringApplication 的初始化:启动前的"准备工作"
Spring Boot 的启动始于 SpringApplication.run() 这个静态方法。但在这之前,有一个容易被忽略的步骤------创建 SpringApplication 实例。
java
// SpringApplication.java (Spring Boot 3.2.x)
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return new SpringApplication(primarySource).run(args);
}
run() 方法实际上做了两件事:先 new 一个 SpringApplication 对象,再调用它的 run() 方法。
SpringApplication 的构造方法做了哪些事情?我们来看核心逻辑:
java
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 1. 推断 Web 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2. 加载 ApplicationContextInitializer
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
this.initializers = getInitializers();
// 3. 加载 ApplicationListener
this.listeners = getListeners();
// 4. 推断主配置类
this.mainApplicationClass = deduceMainApplicationClass();
}
① 推断 Web 应用类型(deduceFromClasspath())
Spring Boot 根据 classpath 中是否存在特定的类来判断应用类型:
| 类型 | 判断依据 |
|---|---|
REACTIVE |
classpath 中存在 org.springframework.web.reactive.DispatcherHandler,且不存在 org.springframework.web.servlet.DispatcherServlet |
SERVLET |
classpath 中存在 jakarta.servlet.Servlet 和 org.springframework.web.context.ConfigurableWebApplicationContext |
NONE |
以上都不满足(纯命令行应用) |
这个判断决定了后续创建什么类型的 ApplicationContext------AnnotationConfigServletWebServerApplicationContext(Servlet 环境)或 AnnotationConfigReactiveWebServerApplicationContext(Reactive 环境)。
② 加载 ApplicationContextInitializer 和 ApplicationListener
通过 SpringFactoriesLoader 从 META-INF/spring.factories(Spring Boot 2.x)或 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x)中加载。
这些 Initializer 和 Listener 是 Spring Boot 启动流程中的"扩展点"------你可以在这些阶段插入自定义逻辑。
③ 推断主配置类(deduceMainApplicationClass())
通过分析堆栈信息,找到包含 main 方法的类。这个类通常就是标注了 @SpringBootApplication 的启动类。
二、run 方法的执行流程:从 main 到容器就绪
SpringApplication 对象创建完成后,run() 方法才是真正的"启动引擎"。我们来看 run() 方法的完整骨架:
java
public ConfigurableApplicationContext run(String... args) {
// 1. 启动计时
long startTime = System.nanoTime();
// 2. 创建引导上下文(BootstrapContext)
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 3. 配置 headless 模式
configureHeadlessProperty();
// 4. 获取并启动运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 5. 准备应用参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 6. 准备环境(Environment)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 7. 打印 Banner
printBanner(environment);
// 8. 创建应用上下文(ApplicationContext)
ConfigurableApplicationContext context = createApplicationContext();
// 9. 准备上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 10. 刷新上下文 ------ 最核心的一步!
refreshContext(context);
// 11. 刷新后处理
afterRefresh(context, applicationArguments);
// 12. 启动完成,发布事件
listeners.started(context);
// 13. 执行 Runner
callRunners(context, applicationArguments);
// 14. 就绪,发布事件
listeners.ready(context);
return context;
} catch (Throwable ex) {
// 异常处理
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
}
我们把其中几个关键步骤拆开来看。
步骤 4:获取并启动运行监听器(SpringApplicationRunListeners)
SpringApplicationRunListener 是 Spring Boot 特有的监听器接口,它和 Spring 的 ApplicationListener 不同------前者专门监听 Spring Boot 启动过程的不同阶段,后者监听 Spring 容器内的事件。
启动过程中会按顺序发布以下事件:
| 事件 | 触发时机 |
|---|---|
ApplicationStartingEvent |
run() 方法开始执行时 |
ApplicationEnvironmentPreparedEvent |
Environment 准备完成时 |
ApplicationContextInitializedEvent |
ApplicationContext 创建并初始化完成时 |
ApplicationPreparedEvent |
ApplicationContext 刷新之前 |
ApplicationStartedEvent |
ApplicationContext 刷新完成之后 |
ApplicationReadyEvent |
所有 Runner 执行完成,应用完全就绪 |
ApplicationFailedEvent |
启动失败时 |
步骤 6:准备环境(prepareEnvironment())
Environment 是 Spring 对"配置来源"的抽象,它聚合了系统属性、环境变量、命令行参数、application.yml 等各种配置源。
prepareEnvironment() 的核心工作:
- 创建
ConfigurableEnvironment实例(根据 Web 类型选择StandardServletEnvironment、StandardReactiveWebEnvironment或StandardEnvironment) - 添加默认的
PropertySource(命令行参数、系统属性、环境变量等) - 加载
application.properties/application.yml - 激活 Profile(
spring.profiles.active) - 发布
ApplicationEnvironmentPreparedEvent
步骤 8:创建应用上下文(createApplicationContext())
根据之前推断的 Web 应用类型,创建对应的 ApplicationContext 实现:
| Web 类型 | ApplicationContext 实现 |
|---|---|
SERVLET |
AnnotationConfigServletWebServerApplicationContext |
REACTIVE |
AnnotationConfigReactiveWebServerApplicationContext |
NONE |
AnnotationConfigApplicationContext |
步骤 9:准备上下文(prepareContext())
在刷新容器之前,做一些前置准备工作:
- 将
Environment设置到ApplicationContext中 - 执行所有
ApplicationContextInitializer - 将启动类(
primarySources)注册为 Bean 定义 - 发布
ApplicationContextInitializedEvent和ApplicationPreparedEvent
步骤 10:刷新上下文(refreshContext())
这是整个启动流程中最核心的一步 ------它调用了 Spring 容器的 refresh() 方法,完成了 IoC 容器的初始化。我们单独展开。
三、refresh 容器的核心:AbstractApplicationContext.refresh() 的 12 个步骤
refreshContext() 最终会调用 AbstractApplicationContext.refresh() 方法。这是 Spring IoC 容器的"总调度中心"。
java
// AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 步骤1:容器启动前的准备与状态验证
prepareRefresh();
// 步骤2:获取底层 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 步骤3:配置 BeanFactory 的标准特征
prepareBeanFactory(beanFactory);
try {
// 步骤4:允许子容器对 BeanFactory 进行后置处理
postProcessBeanFactory(beanFactory);
// 步骤5:实例化并调用所有 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 步骤6:注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 步骤7:初始化消息源(国际化支持)
initMessageSource();
// 步骤8:初始化应用事件广播器
initApplicationEventMulticaster();
// 步骤9:留给子类初始化其他特殊的 Bean
onRefresh();
// 步骤10:注册监听器,并发布早期事件
registerListeners();
// 步骤11:完成所有单例 Bean 的实例化(重点!)
finishBeanFactoryInitialization(beanFactory);
// 步骤12:完成刷新,发布相应事件
finishRefresh();
} catch (BeansException ex) {
// 异常处理:销毁已创建的单例
destroyBeans();
cancelRefresh(ex);
throw ex;
} finally {
resetCommonCaches();
}
}
}
步骤 2:obtainFreshBeanFactory()
获取或创建一个新的 BeanFactory 实例(通常是 DefaultListableBeanFactory)。这一步会加载所有的 Bean 定义(BeanDefinition)------无论是通过 XML、注解还是 Java 配置定义的。
步骤 5:invokeBeanFactoryPostProcessors()------自动配置的触发时机
这是自动配置被加载的关键步骤。
BeanFactoryPostProcessor 是在 Bean 实例化之前 执行的,它可以修改 BeanDefinition。而 Spring Boot 的自动配置正是通过 ConfigurationClassPostProcessor(一个 BeanFactoryPostProcessor)来解析 @Configuration 类、处理 @Import(包括 @EnableAutoConfiguration 导入的 AutoConfigurationImportSelector)。
简单说,自动配置类的加载发生在 invokeBeanFactoryPostProcessors() 阶段。AutoConfigurationImportSelector 会读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,加载所有自动配置类,然后通过 @Conditional 条件注解决定哪些配置类真正生效。
步骤 6:registerBeanPostProcessors()
注册所有 BeanPostProcessor。这些后置处理器会在 Bean 初始化前后被调用,AOP 的代理创建就是通过 BeanPostProcessor 实现的。
步骤 11:finishBeanFactoryInitialization()------所有 Bean 被创建
这一步实例化所有非懒加载的单例 Bean 。每个 Bean 都会经历我们在第 18 篇讲过的完整生命周期:实例化 → 属性赋值 → 初始化(Aware → BeanPostProcessor 前置 → @PostConstruct → InitializingBean → init-method → BeanPostProcessor 后置)。
四、自动配置的触发时机:定位到源码行
把自动配置在整个启动流程中的位置精确地定位一下:
SpringApplication.run()
↓
new SpringApplication() ← ① 初始化:加载 ApplicationListener 等
↓
SpringApplication.run() ← ② 执行 run 方法
↓
prepareEnvironment() ← ③ 准备环境,加载 application.yml
↓
createApplicationContext()
↓
prepareContext() ← ④ 准备上下文,注册启动类
↓
refreshContext()
↓
AbstractApplicationContext.refresh()
↓
invokeBeanFactoryPostProcessors() ← ⑤ ★ 自动配置在这里触发!
├── ConfigurationClassPostProcessor 解析 @Configuration
├── @EnableAutoConfiguration → AutoConfigurationImportSelector
├── 读取 AutoConfiguration.imports 文件
├── 应用 @Conditional 条件过滤
└── 注册符合条件的自动配置类
↓
finishBeanFactoryInitialization() ← ⑥ 实例化所有单例 Bean
↓
应用启动完成
关键认知 :自动配置不是在 SpringApplication 初始化时加载的,而是在 refresh() 的 invokeBeanFactoryPostProcessors() 阶段------此时 Bean 还没有被实例化 ,但 Bean 的定义(BeanDefinition)已经可以被修改和新增。
这也是为什么自动配置类上可以用 @ConditionalOnMissingBean------因为在 invokeBeanFactoryPostProcessors() 阶段,容器中已经注册了用户自定义的 Bean 定义,自动配置可以判断"这个 Bean 用户有没有自己定义",从而决定是否创建默认的 Bean。
五、事件机制:启动过程中的"广播站"
Spring Boot 的启动过程中,事件机制扮演着"广播站"的角色------在不同阶段发布不同的事件,让监听器可以在特定时机插入自定义逻辑。
两种监听器:
| 类型 | 接口 | 作用范围 |
|---|---|---|
ApplicationListener |
Spring 框架 | 监听 Spring 容器内的事件 |
SpringApplicationRunListener |
Spring Boot | 监听 Spring Boot 启动过程的阶段 |
Spring Boot 启动过程中的事件发布顺序:
listeners.starting()
↓
ApplicationStartingEvent
↓
prepareEnvironment()
↓
ApplicationEnvironmentPreparedEvent
↓
prepareContext()
↓
ApplicationContextInitializedEvent
↓
ApplicationPreparedEvent
↓
refreshContext()
↓
(容器刷新完成)
↓
listeners.started()
↓
ApplicationStartedEvent
↓
callRunners()
↓
listeners.ready()
↓
ApplicationReadyEvent
自定义监听器的用法:
java
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("应用已就绪,可以执行初始化数据、预热缓存等操作");
}
}
或者使用 @EventListener 注解:
java
@Component
public class MyEventListener {
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
System.out.println("应用已就绪!");
}
}
代码示例
示例一:在启动过程中添加自定义 Listener
方式一:实现 ApplicationListener 接口
java
package com.example.demo.listener;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class CustomStartedListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
System.out.println("🟢 [监听器] ApplicationStartedEvent ------ 容器已刷新,应用启动中");
// 可以在这里做一些初始化工作,但此时 Runner 还没执行
}
}
方式二:使用 @EventListener 注解(更简洁)
java
package com.example.demo.listener;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class CustomReadyListener {
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
System.out.println("✅ [监听器] ApplicationReadyEvent ------ 应用已完全就绪!");
// 在这里做初始化数据、预热缓存等操作
}
@EventListener(ApplicationFailedEvent.class)
public void onApplicationFailed(ApplicationFailedEvent event) {
System.err.println("❌ [监听器] ApplicationFailedEvent ------ 应用启动失败");
Throwable exception = event.getException();
System.err.println("异常原因: " + exception.getMessage());
}
}
方式三:通过 SpringApplication.addListeners() 编程式添加
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
app.addListeners(new CustomStartedListener());
app.run(args);
}
}
运行观察:
启动应用后,控制台会按顺序输出:
🟢 [监听器] ApplicationStartedEvent ------ 容器已刷新,应用启动中
...(其他启动日志)...
✅ [监听器] ApplicationReadyEvent ------ 应用已完全就绪!
示例二:分析自动配置的加载时机(断点调试)
如果你想亲自验证自动配置的加载时机,可以在 IDE 中做以下操作:
第一步:在 AutoConfigurationImportSelector.selectImports() 方法上打断点
这个方法的调用链是:
AbstractApplicationContext.refresh()
→ invokeBeanFactoryPostProcessors()
→ ConfigurationClassPostProcessor.processConfigBeanDefinitions()
→ ConfigurationClassParser.processImports()
→ AutoConfigurationImportSelector.selectImports()
第二步:以 Debug 模式启动应用
当断点命中时,观察调用栈(Call Stack):
selectImports:XXX, AutoConfigurationImportSelector
processImports:XXX, ConfigurationClassParser
processConfigurationClass:XXX, ConfigurationClassParser
processConfigBeanDefinitions:XXX, ConfigurationClassPostProcessor
invokeBeanFactoryPostProcessors:XXX, AbstractApplicationContext
refresh:XXX, AbstractApplicationContext
refreshContext:XXX, SpringApplication
run:XXX, SpringApplication
main:XXX, DemoApplication
关键观察 :调用栈中可以看到,selectImports() 是在 refresh() → invokeBeanFactoryPostProcessors() 的调用链中被触发的------印证了自动配置在 invokeBeanFactoryPostProcessors() 阶段加载。
第三步:查看自动配置报告
在 application.yml 中添加:
yaml
debug: true
启动后,控制台会输出自动配置报告,显示哪些配置类被加载(Positive matches)和未被加载(Negative matches)及其原因。
调试技巧 :在 AutoConfigurationImportSelector 的 getAutoConfigurationEntry() 方法中,你可以看到自动配置类被加载、过滤、去重的完整过程。
新手错误 vs 正确姿势
| 错误表象 | 根本原因 | 正确姿势 |
|---|---|---|
启动时某些 Bean 未加载,明明加了 @Component 却不生效 |
@ComponentScan 默认只扫描启动类所在包及其子包 |
使用 @ComponentScan(basePackages = {"com.example.module1", "com.example.module2"}) 显式指定扫描范围 |
| 引入了一个 Starter,但它的自动配置没有生效 | 自动配置的条件不满足(如缺少某个类、某个配置属性不匹配) | 在 application.yml 中添加 debug: true,查看自动配置报告中的 Negative matches |
| 启动慢,项目启动需要几十秒 | 类路径扫描范围过大,或大量 Bean 在启动时被实例化 | 优化 @ComponentScan 范围;开启延迟初始化 spring.main.lazy-initialization=true |
@EventListener 方法没有被调用 |
监听器类未被 Spring 管理(未加 @Component) |
确保监听器类添加了 @Component 或在配置类中显式声明 |
ApplicationReadyEvent 中执行了耗时操作,导致应用启动完成但接口无法访问 |
误解了 ApplicationReadyEvent 的语义------此时应用已完全就绪,但监听器中的逻辑是同步执行的 |
如果需要在启动后执行耗时操作,使用 @Async 异步执行,或使用 CommandLineRunner / ApplicationRunner |
疑难深度追问
Q1:SpringApplication.run() 和 ApplicationContext.refresh() 是什么关系?
SpringApplication.run() 是 Spring Boot 层面的启动入口,它负责统筹整个启动流程 ------包括创建 SpringApplication 实例、准备 Environment、创建 ApplicationContext、打印 Banner、执行 Runner 等。
ApplicationContext.refresh() 是 Spring 容器层面的核心方法,它负责初始化 IoC 容器------加载 Bean 定义、注册后置处理器、实例化单例 Bean 等。
两者的关系是:SpringApplication.run() 调用了 ApplicationContext.refresh()。refresh() 是 run() 方法中最核心的一步,但不是全部。
Q2:Spring Boot 启动时是如何判断当前是 Web 应用还是非 Web 应用的?
通过 WebApplicationType.deduceFromClasspath() 方法。它检查 classpath 中是否存在特定的类:
java
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", null)
&& !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", null)) {
return REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return NONE;
}
}
return SERVLET;
}
- 如果有
DispatcherHandler(WebFlux)且没有DispatcherServlet(Spring MVC)→REACTIVE - 如果有
Servlet和ConfigurableWebApplicationContext→SERVLET - 否则 →
NONE
Q3:Spring Boot 启动过程中的事件发布机制有什么实际用途?
最常见的用途有三个:
- 启动后初始化数据 :监听
ApplicationReadyEvent,在应用完全启动后执行数据初始化、缓存预热等操作 - 启动失败告警 :监听
ApplicationFailedEvent,在启动失败时发送告警通知 - 动态配置加载 :
ConfigFileApplicationListener监听ApplicationEnvironmentPreparedEvent,在环境准备阶段加载application.yml
思考与延伸
-
动手验证 :在
application.yml中添加debug: true,启动应用后查看自动配置报告,找出 3 个 Positive matches 和 3 个 Negative matches,理解它们生效/不生效的原因。 -
思考题 :
ApplicationReadyEvent和ApplicationStartedEvent有什么区别?如果你需要在应用启动后执行初始化数据的逻辑,应该监听哪个事件?为什么? -
延伸阅读 :Spring Boot 官方文档的 "SpringApplication" 章节对启动流程和事件机制有详细的说明。另外,
SpringApplication.run()方法的源码是理解整个启动流程的最佳入口------建议在 IDE 中打开源码,跟着文中的步骤走一遍。
参考与延伸阅读
- Spring Boot. SpringApplication. Spring Boot Documentation, 3.2.x
- Spring Boot. Application Events and Listeners. Spring Boot Documentation
- 腾讯云. 深入解析Spring Boot启动魔法:SpringApplication.run()源码全流程拆解. 2025-08-27
- 腾讯云. 深入解析Spring Boot核心启动流程与上下文初始化. 2025-08-27
- CSDN. Spring IOC容器启动全流程揭秘:从配置元数据加载到Refresh()方法执行. 2025-10-25
- CSDN. Spring Boot 启动过程深度解析. 2025-02-19
完