目录
[一、Spring Boot 启动过程核心步骤解析](#一、Spring Boot 启动过程核心步骤解析)
[1. SpringApplication 实例化阶段](#1. SpringApplication 实例化阶段)
[2. run () 方法核心执行全流程](#2. run () 方法核心执行全流程)
[1. ApplicationContextInitializer](#1. ApplicationContextInitializer)
[2. ApplicationListener](#2. ApplicationListener)
[3. BeanFactoryPostProcessor](#3. BeanFactoryPostProcessor)
[4. BeanPostProcessor](#4. BeanPostProcessor)
[5. CommandLineRunner / ApplicationRunner](#5. CommandLineRunner / ApplicationRunner)
[1. 核心原理](#1. 核心原理)
[2. 完整优先级排序(从高到低)](#2. 完整优先级排序(从高到低))
[4. Profile 配置加载源码](#4. Profile 配置加载源码)
[5. 拓展](#5. 拓展)
一、Spring Boot 启动过程核心步骤解析
Spring Boot 启动的核心入口是 SpringApplication 类,整个流程分为实例化构造 和 run() 方法执行两大阶段,本质是对 Spring 原生容器的封装与生命周期扩展。
1. SpringApplication 实例化阶段
构造方法主要完成 4 件核心事,为后续启动做前置准备:
java
// org.springframework.boot.SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 1. 保存主启动类,后续作为包扫描的根入口
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 推断当前Web应用类型:SERVLET / REACTIVE / NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 3. SPI加载所有ApplicationContextInitializer上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 4. SPI加载所有ApplicationListener事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 5. 遍历异常栈,推断main方法所在的主启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
- 推断 Web 应用类型 通过
ClassUtils.isPresent()判断类路径下是否存在 Servlet、DispatcherServlet 等核心类,将应用分为 3 种类型:SERVLET:传统 Servlet Web 应用,对应内嵌 Tomcat/JettyREACTIVE:响应式 Web 应用,对应 Netty 等容器NONE:非 Web 应用,仅运行普通 Spring 容器
- 加载初始化器
ApplicationContextInitializer借助SpringFactoriesLoader扫描所有META-INF/spring.factories文件,加载并缓存所有上下文初始化器实现类。 - 加载事件监听器
ApplicationListener同样通过 SPI 机制加载全局事件监听器,用于后续启动各阶段的事件回调。 - 推断主启动类 通过构造运行时栈,遍历栈帧找到包含
main方法的类,作为启动的主配置类。
2. run () 方法核心执行全流程
run() 是 Spring Boot 启动的主流程,完整主干源码如下,去掉了异常分支的冗余包装:
java
// org.springframework.boot.SpringApplication
public ConfigurableApplicationContext run(String... args) {
// 1. 启动计时器,统计启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 2. 设置Java.awt.headless模式,无图形环境也能正常运行
configureHeadlessProperty();
// 3. SPI加载SpringApplicationRunListener,用于发布启动各阶段事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(); // 发布应用开始启动事件
try {
// 4. 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 5. 准备运行环境:加载配置文件、环境变量、系统属性
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 6. 打印启动Banner
Banner printedBanner = printBanner(environment);
// 7. 根据Web类型创建对应ApplicationContext上下文实例
context = createApplicationContext();
// 8. 加载异常报告器,启动失败时输出分析信息
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 9. 上下文前置准备:注入环境、执行初始化器、注册启动类、发布事件
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 10. 刷新上下文:Spring核心流程,Bean扫描、实例化、容器初始化
refreshContext(context);
// 11. 刷新后钩子,空实现留给子类扩展
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 12. 发布应用启动完成事件
listeners.started(context);
// 13. 执行所有CommandLineRunner和ApplicationRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 14. 发布应用就绪事件,此时应用可对外提供服务
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这是 Spring Boot 启动的主干逻辑,每一步都对应明确的扩展点与生命周期事件。

关键步骤拆解
-
准备运行环境 创建
ConfigurableEnvironment对象,加载系统属性、环境变量、配置文件等所有属性源;同时激活指定的 profile,完成多环境配置的筛选。这一步完成后,所有配置信息就已经全部就绪。 -
创建应用上下文 根据前面推断的应用类型,创建对应上下文实现类:Servlet 环境 →
AnnotationConfigServletWebServerApplicationContext;Reactive 环境 →AnnotationConfigReactiveWebServerApplicationContext;非 Web 环境 →AnnotationConfigApplicationContext -
上下文前置处理 调用所有
ApplicationContextInitializer的initialize()方法,对上下文做自定义预处理;随后发布上下文准备完成事件,通知所有监听器。 -
刷新上下文(核心) 调用 Spring 原生的
refresh()方法,完成 Bean 定义扫描、Bean 实例化、依赖注入、后置处理器执行、容器初始化等全部核心流程。Web 环境下,这一步还会触发内嵌 Tomcat 的创建与启动。java// org.springframework.context.support.AbstractApplicationContext public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh(); // 1. 刷新前预处理 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 2. 获取Bean工厂 prepareBeanFactory(beanFactory); // 3. Bean工厂基础配置 // 4. 执行所有BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); // 5. 注册所有BeanPostProcessor registerBeanPostProcessors(beanFactory); initMessageSource(); // 6. 初始化国际化 initApplicationEventMulticaster();// 7. 初始化事件多播器 onRefresh(); // 8. 子类扩展钩子:内嵌Tomcat在这里启动 registerListeners(); // 9. 注册事件监听器 finishBeanFactoryInitialization(beanFactory); // 10. 实例化所有单例Bean finishRefresh(); // 11. 完成刷新,发布上下文刷新事件 } }自动配置类的解析、
@Bean方法的扫描,都发生在第 4 步invokeBeanFactoryPostProcessors中,由ConfigurationClassPostProcessor负责完成。 -
刷新后处理与收尾 执行
afterRefresh钩子,发布ApplicationStartedEvent启动完成事件;依次执行所有CommandLineRunner和ApplicationRunner;最后发布ApplicationReadyEvent就绪事件,标志应用完全启动可用。
二、启动过程扩展点解析
Spring Boot 全程采用事件驱动 + 钩子接口的设计,在启动的每个节点都预留了扩展点,开发者可以在不修改框架源码的情况下介入启动流程。
按执行顺序排列的核心扩展点
1. ApplicationContextInitializer
- 执行时机:上下文创建完成后,refresh 刷新之前
- 核心作用:对 Spring 上下文做预处理,比如注册自定义属性源、强制激活指定 profile、注册 Bean 定义加载器
2. ApplicationListener
- 执行时机:贯穿启动全流程,对应不同生命周期事件触发
- 核心作用:基于观察者模式,监听启动各阶段事件,做解耦的扩展逻辑
- 常用事件 :
ApplicationEnvironmentPreparedEvent:环境准备完成ApplicationContextInitializedEvent:上下文初始化完成ApplicationPreparedEvent:上下文准备完成ApplicationStartedEvent:应用启动完成ApplicationReadyEvent:应用完全就绪
3. BeanFactoryPostProcessor
- 执行时机:所有 Bean 定义加载完成后,Bean 实例化之前
- 核心作用:修改或增强 Bean 定义,比如批量修改 Bean 属性、注册额外的 Bean 定义
- 典型代表 :
PropertySourcesPlaceholderConfigurer,负责把配置文件中的${xxx}占位符替换成真实值。
4. BeanPostProcessor
- 执行时机:Bean 实例化后、初始化方法执行的前后
- 核心作用:对 Bean 实例做增强,比如动态代理、属性填充
- 典型代表 :
AutowiredAnnotationBeanPostProcessor,负责@Autowired注解的依赖注入。
5. CommandLineRunner / ApplicationRunner
- 执行时机:容器完全刷新完成、应用即将就绪之前
- 核心作用:项目启动后执行初始化逻辑,比如数据预热、缓存加载、服务自检
- 两者区别 :
ApplicationRunner对命令行参数做了封装,可以更方便地解析--key=value格式的参数;CommandLineRunner接收原始字符串数组。
java
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 收集所有ApplicationRunner
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 收集所有CommandLineRunner
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 按@Order注解排序
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
((ApplicationRunner) runner).run(args);
}
if (runner instanceof CommandLineRunner) {
((CommandLineRunner) runner).run(args.getSourceArgs());
}
}
}
设计细节 :两种 Runner 放在同一个列表排序,
@Order值越小优先级越高,支持跨类型排序。
三、配置文件优先级与加载解析
1. 核心原理
Spring Boot 所有配置最终都会被封装成 PropertySource 对象,按优先级顺序存入 Environment 的属性源列表中。同名属性时,排在列表前面的属性源会覆盖后面的,这就是配置优先级的底层逻辑。
java
// org.springframework.core.env.MutablePropertySources
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
// 加到最前面,优先级最高
public void addFirst(PropertySource<?> propertySource) {
this.propertySourceList.add(0, propertySource);
}
// 加到最后面,优先级最低
public void addLast(PropertySource<?> propertySource) {
this.propertySourceList.add(propertySource);
}
}
获取属性时按列表顺序遍历,找到第一个匹配值就返回,这就是优先级的底层实现。
2. 完整优先级排序(从高到低)
| 优先级 | 配置来源 | 说明 |
|---|---|---|
| 1 | 命令行参数 | 形如 --server.port=8080 的启动参数,优先级最高 |
| 2 | SPRING_APPLICATION_JSON 环境变量 |
以 JSON 格式注入的整体配置 |
| 3 | ServletConfig 初始化参数 | Web 容器的 Servlet 配置参数 |
| 4 | ServletContext 初始化参数 | Web 应用上下文参数 |
| 5 | JNDI 属性 | java:comp/env 下的 JNDI 配置 |
| 6 | Java 系统属性 | System.getProperties() 中的属性 |
| 7 | 操作系统环境变量 | 系统层面设置的环境变量 |
| 8 | random.* 随机值属性源 |
框架内置的随机数生成属性源 |
| 9 | Jar 包外的 application-{profile}.yml/properties |
外部带环境的配置文件 |
| 10 | Jar 包内的 application-{profile}.yml/properties |
内部带环境的配置文件 |
| 11 | Jar 包外的 application.yml/properties |
外部主配置文件 |
| 12 | Jar 包内的 application.yml/properties |
内部主配置文件 |
| 13 | @PropertySource 注解引入的配置 |
注解指定的配置文件 |
| 14 | SpringApplication.setDefaultProperties 设置的默认属性 |
代码硬编码的默认配置,优先级最低 |
3.配置文件加载核心源码
Spring Boot 2.4+ 版本通过 ConfigDataEnvironmentPostProcessor 加载配置文件,核心调用链:
java
// ConfigDataEnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 解析所有配置数据位置(classpath、file路径等)
ConfigDataLocations locations = resolveLocations(environment);
// 加载所有配置文件
ConfigData configData = loadConfigData(locations, environment);
// 按优先级顺序添加到环境中
addPropertySources(environment, configData);
}
- 配置文件的加载核心由
ConfigFileApplicationListener(2.4 版本后升级为ConfigDataEnvironmentPostProcessor)负责,它本身是一个环境准备阶段的事件监听器。 - 加载时会先加载所有
application主配置,再加载对应 profile 的配置;profile 配置会覆盖主配置中的同名属性。 - 同一目录下,
.properties文件优先级高于.yml文件。
配置文件搜索顺序源码逻辑
默认搜索 4 个位置,优先级从高到低:
file:./config/--- 项目目录下的 config 子目录file:./--- 项目根目录classpath:/config/--- 类路径下的 config 目录classpath:/--- 类路径根目录
加载时按从低到高 顺序加载,后加载的调用 addFirst 插入到列表头部,最终实现前面路径覆盖后面路径的效果。
4. Profile 配置加载源码
加载完主配置后,会根据激活的 profile 加载对应环境配置:
- 先加载所有
application.yml主配置 - 再加载
application-{profile}.yml环境配置 - 环境配置通过
addFirst加入,因此优先级高于主配置 - 多 profile 同时激活时,后声明的 profile 优先级更高
5. 拓展
- 外部配置文件会覆盖 Jar 包内的同名配置,生产环境常用这个特性做差异化配置,不用重新打包。
- 配置文件的搜索路径优先级:
./config/>./>classpath:/config/>classpath:/。 - 多 profile 同时激活时,后激活的 profile 配置会覆盖先激活的。
四、面试速记总结
- 启动主干:构造方法推断类型 + 加载 SPI 扩展 → run 方法准备环境 → 创建上下文 → 执行初始化器 → refresh 刷新容器 → 回调 Runner → 发布就绪事件。
- 扩展点顺序:ApplicationContextInitializer(refresh 前)→ BeanFactoryPostProcessor(Bean 定义后)→ BeanPostProcessor(Bean 实例化前后)→ Runner(启动完成后)。
- 配置优先级 :命令行 > 系统 / 环境变量 > 外部带 profile 配置 > 内部带 profile 配置 > 外部主配置 > 内部主配置 > 默认配置。配置本质:所有属性源存在 List 中,靠前覆盖靠后;addFirst 提高优先级,addLast 降低优先级;profile 配置覆盖主配置。
- 内嵌 Tomcat 入口 :触发点是
ServletWebServerApplicationContext的onRefresh方法,由 Spring 刷新流程驱动,Tomcat 是容器内的普通 Bean。 - 设计本质:Spring Boot 没有发明新技术,只是把 Spring 容器、Web 服务器、自动配置等组件通过 SPI 和事件机制串联起来,用「约定大于配置」的思想降低了使用门槛。