写了这么多 Spring 原理,但每次面试被问"Spring Boot 启动流程"还是说不全?今天从
SpringApplication.run()出发,逐行跟踪源码,把启动全链路拆清楚。读完这篇,从 main 方法到应用就绪,每一步你都能说出在干什么、为什么这样干。
一、从一个 main 方法说起
java
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
这三行代码背后,Spring Boot 做了大量工作。我们逐层拆解。
scss
SpringApplication.run() 做了什么?
│
├── 1. 创建 SpringApplication 实例
│ ├── 推断 Web 应用类型
│ ├── 加载 ApplicationContextInitializer
│ ├── 加载 ApplicationListener
│ └── 推断主配置类
│
├── 2. 运行 run() 方法
│ ├── 创建并启动计时器
│ ├── 获取 SpringApplicationRunListeners
│ ├── 准备 Environment
│ ├── 创建 ApplicationContext
│ ├── 准备上下文
│ ├── 刷新上下文(核心!)
│ ├── 执行后置处理
│ └── 发布就绪事件
│
└── 3. 返回 ApplicationContext
二、SpringApplication 构造方法
java
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断 Web 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载 Initializer
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载 Listener
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 推断主配置类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
2.1 推断 Web 应用类型
java
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", ...)
&& !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", ...)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, ...)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
css
推断逻辑
│
├── 有 reactive + 无 servlet → REACTIVE
├── 无 servlet 相关类 → NONE(非 Web 应用)
└── 默认 → SERVLET
2.2 加载 Spring.factories
getSpringFactoriesInstances() 从 META-INF/spring.factories 中加载指定类型的实现类。
markdown
Spring.factories 加载机制
│
├── 扫描路径
│ ├── spring-boot.jar 中的 spring.factories
│ ├── spring-boot-autoconfigure.jar 中的 spring.factories
│ └── 项目自定义的 spring.factories
│
├── 缓存机制
│ ├── 第一次加载时遍历所有 jar
│ ├── 结果缓存在 SpringFactoriesLoader.cache
│ └── 后续直接读缓存
│
└── 加载的组件
├── ApplicationContextInitializer(7 个默认)
├── ApplicationListener(9 个默认)
└── 其他(SpringBootExceptionReporter 等)
三、run() 方法核心流程
这是整个启动最核心的方法,展开源码:
java
public ConfigurableApplicationContext run(String... args) {
// 1. 计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 2. 获取 RunListeners(通过 spring.factories)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
// 3. 准备 Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, args);
listeners.environmentPrepared(environment);
// 4. 创建 ApplicationContext
ConfigurableApplicationContext context = createApplicationContext();
// 5. 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 6. 刷新上下文(最核心!)
refreshContext(context);
// 7. 后置处理
afterRefresh(context, applicationArguments);
// 8. 停止计时
stopWatch.stop();
// 9. 发布就绪事件
listeners.started(context);
callRunners(context);
return context;
}
3.1 各阶段耗时分布
erlang
典型启动耗时分布(总计 ~2.5s)
│
├── starting + environment ~0.2s (8%)
├── createApplicationContext ~0.1s (4%)
├── prepareContext ~0.1s (4%)
├── refreshContext ~1.8s (72%) ← 大头!
│ ├── Bean 定义扫描 ~0.3s
│ ├── Bean 创建与依赖注入 ~1.0s
│ ├── 自动配置处理 ~0.3s
│ └── 其他 ~0.2s
├── afterRefresh ~0.1s (4%)
└── callRunners ~0.2s (8%)
四、Environment 准备
java
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners, ApplicationArguments arguments) {
// 1. 创建 Environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置 Environment
configureEnvironment(environment, arguments.getSourceArgs());
// 3. ConfigurationProperties 绑定
ConfigurationPropertySources.attach(environment);
// 4. 通知 Listener
listeners.environmentPrepared(environment);
// 5. 绑定到 SpringApplication
bindToSpringApplication(environment);
return environment;
}
Environment 加载的配置源
scss
配置源优先级(从高到低)
│
├── 1. 命令行参数 (--server.port=8080)
├── 2. JNDI 属性
├── 3. Java 系统属性 (System.getProperties())
├── 4. 操作系统环境变量 (PORT, JAVA_HOME)
├── 5. RandomValuePropertySource (${random.int})
├── 6. application-{profile}.yml (外部)
├── 7. application-{profile}.yml (jar 内)
├── 8. application.yml (外部)
└── 9. application.yml (jar 内)
关键事件 :ApplicationEnvironmentPreparedEvent,ConfigFileApplicationListener 在此时加载 application.yml。
五、创建 ApplicationContext
java
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextClass != null
? this.applicationContextClass
: (ConfigurableApplicationContext) switch (this.webApplicationType) {
case SERVLET -> new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE -> new AnnotationConfigReactiveWebServerApplicationContext();
default -> new AnnotationConfigApplicationContext();
};
}
css
三种上下文类型
│
├── SERVLET
│ └── AnnotationConfigServletWebServerApplicationContext
│ └── 内置 Tomcat/Jetty/Undertow
│
├── REACTIVE
│ └── AnnotationConfigReactiveWebServerApplicationContext
│ └── 内置 Netty
│
└── NONE
└── AnnotationConfigApplicationContext
└── 无 Web 容器
六、prepareContext:准备上下文
java
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, ...) {
// 1. 设置 Environment
context.setEnvironment(environment);
// 2. 后置处理 ApplicationContext
postProcessApplicationContext(context);
// 3. 执行所有 Initializer
applyInitializers(context);
// 4. 通知 Listener
listeners.contextPrepared(context);
// 5. 注册单例 Bean(args, banner 等)
context.getBeanFactory().registerSingleton("springApplicationArguments", arguments);
context.getBeanFactory().registerSingleton("springBootBanner", banner);
// 6. 加载 Bean 定义
Set<Object> sources = getAllSources();
load(context, sources.toArray(new Object[0]));
// 7. 通知 Listener
listeners.contextLoaded(context);
}
Initializer 执行顺序
lua
默认 Initializer(按 @Order 排序)
│
├── DelegatingApplicationContextInitializer
│ └── 委托给 context.initializer.classes 配置的 Initializer
│
├── SharedMetadataReaderFactoryContextInitializer
│ └── 注册 ConfigurationClassPostProcessor 需要的 MetadataReaderFactory
│
├── ContextIdApplicationContextInitializer
│ └── 设置 ApplicationContext 的 ID
│
├── ConfigurationWarningsApplicationContextInitializer
│ └── 检查常见的错误配置(如错误的包扫描)
│
├── RSocketPortInfoApplicationContextInitializer
│ └── RSocket 端口信息
│
├── ServerPortInfoApplicationContextInitializer
│ └── Web 服务器端口信息(local.server.port)
│
└── ConditionEvaluationReportLoggingListener
└── 自动配置报告(debug 模式可见)
七、refreshContext:核心刷新
refreshContext() 调用 AbstractApplicationContext.refresh(),这是 Spring 的核心方法。
java
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;
}
}
}
各步骤详解
yaml
refresh() 十二步详解
│
├── Step 1: prepareRefresh
│ ├── 记录启动时间
│ ├── 设置 active 标志
│ └── 初始化占位符属性源
│
├── Step 2: obtainFreshBeanFactory
│ ├── 刷新 BeanFactory(如有必要则重建)
│ └── 返回 BeanFactory
│
├── Step 3: prepareBeanFactory
│ ├── 设置 ClassLoader
│ ├── 注册 BeanExpressionResolver(SpEL)
│ ├── 注册 PropertyEditorRegistrar
│ ├── 注册 Environment、ApplicationContext 等
│ └── 配置自动装配忽略规则
│
├── Step 4: postProcessBeanFactory
│ └── Web 上下文注册 Servlet 相关 Bean
│
├── Step 5: invokeBeanFactoryPostProcessors ← 核心!
│ ├── 先执行 BeanDefinitionRegistryPostProcessor
│ │ └── ConfigurationClassPostProcessor 解析 @Configuration
│ │ ├── 处理 @ComponentScan
│ │ ├── 处理 @Import
│ │ ├── 处理 @Bean
│ │ └── 触发自动配置(AutoConfiguration)
│ └── 再执行普通 BeanFactoryPostProcessor
│
├── Step 6: registerBeanPostProcessors
│ ├── AutowiredAnnotationBeanPostProcessor
│ ├── CommonAnnotationBeanPostProcessor
│ └── 其他自定义 BeanPostProcessor
│
├── Step 7-8: MessageSource + EventMulticaster
│ └── 国际化 + 事件广播
│
├── Step 9: onRefresh
│ └── Web 服务器启动(Tomcat/Jetty)
│
├── Step 10: registerListeners
│ ├── 注册静态 Listener
│ └── 发布早期事件
│
├── Step 11: finishBeanFactoryInitialization ← 最耗时!
│ ├── 实例化 ConversionService
│ ├── 冻结 Bean 定义
│ └── 实例化所有非懒加载单例 Bean
│
└── Step 12: finishRefresh
├── 清除缓存
├── 初始化 LifecycleProcessor
├── 发布 ContextRefreshedEvent
└── 参与 LiveBeansView(JMX)
Step 5 详解:自动配置的触发
java
ConfigurationClassPostProcessor 处理链
│
├── 1. 解析 @SpringBootApplication
│ ├── @SpringBootConfiguration → 标记为配置类
│ ├── @EnableAutoConfiguration → 触发自动配置
│ │ ├── @Import(AutoConfigurationImportSelector.class)
│ │ └── AutoConfigurationImportSelector.selectImports()
│ │ ├── 加载 META-INF/spring.factories
│ │ ├── 过滤 @Conditional 条件
│ │ └── 排序(@AutoConfigureOrder)
│ └── @ComponentScan → 扫描当前包及子包
│
├── 2. 处理自动配置类
│ ├── 每个 @Conditional 条件判断
│ ├── 条件不满足 → 跳过
│ └── 条件满足 → 注册 Bean 定义
│
└── 3. 处理 @Bean 方法
└── 注册为 BeanDefinition
Step 9 详解:Web 服务器启动
scss
ServletWebServerApplicationContext.onRefresh()
│
├── createWebServer()
│ ├── 从 BeanFactory 获取 ServletWebServerFactory
│ │ ├── TomcatServletWebServerFactory(默认)
│ │ ├── JettyServletWebServerFactory
│ │ └── UndertowServletWebServerFactory
│ │
│ ├── factory.getWebServer()
│ │ ├── 创建 Tomcat 实例
│ │ ├── 配置 Connector(端口等)
│ │ └── 启动 Tomcat
│ │
│ └── 注册 WebServerGracefulShutdownLifecycle
│
└── 端口绑定完成
└── 发布 WebServerInitializedEvent
八、callRunners:启动后执行器
java
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : runners) {
if (runner instanceof ApplicationRunner) {
((ApplicationRunner) runner).run(args);
}
if (runner instanceof CommandLineRunner) {
((CommandLineRunner) runner).run(args.getSourceArgs());
}
}
}
css
Runner 执行顺序
│
├── 1. 按 @Order 排序
├── 2. ApplicationRunner 先执行
├── 3. CommandLineRunner 后执行
│
└── 典型用途
├── 数据初始化
├── 缓存预热
├── 健康检查
└── 通知服务就绪
九、完整启动事件序列
scss
Spring Boot 启动事件时间线
│
├── 1. starting
│ └── SpringApplicationRunListener.starting()
│
├── 2. environmentPrepared
│ └── Environment 创建完成,配置文件已加载
│
├── 3. contextPrepared
│ └── ApplicationContext 创建完成,Initializer 已执行
│
├── 4. contextLoaded
│ └── Bean 定义已加载,Listener 已注册
│
├── 5. [refresh 过程中]
│ ├── BeanDefinitionRegistryPostProcessor 执行
│ ├── Bean 创建与注入
│ └── Web 服务器启动
│
├── 6. started
│ └── ApplicationContext 刷新完成
│
├── 7. ready
│ └── ApplicationRunner / CommandLineRunner 执行完成
│
└── 完整耗时 = started - starting
十、启动优化 Checklist
| # | 优化手段 | 效果 | 说明 |
|---|---|---|---|
| 1 | 开启懒加载 | 启动提速 30-50% | spring.main.lazy-initialization=true,但首次请求变慢 |
| 2 | 排除不需要的自动配置 | 启动提速 10-20% | @SpringBootApplication(exclude={...}) |
| 3 | 减少组件扫描范围 | 启动提速 5-10% | 显式指定 scanBasePackages |
| 4 | 使用 spring.factories 索引 | 启动提速 5-10% | 添加 spring-boot-autoconfigure-processor 依赖 |
| 5 | 并行初始化 | 启动提速 15-30% | Spring Boot 3.2+ 的 spring.main.startup-mode=PARALLEL |
| 6 | 优化日志输出 | 微小提升 | 减少不必要的日志 |
| 7 | 使用 GraalVM Native Image | 启动提速 90%+ | 编译时优化,启动到毫秒级 |
懒加载 vs 急加载
| 对比项 | 急加载(默认) | 懒加载 |
|---|---|---|
| 启动时间 | 慢(创建所有 Bean) | 快(只创建必要的) |
| 首次请求 | 快 | 慢(需要创建 Bean) |
| 问题发现 | 启动时就能发现 | 运行时才发现 |
| 适用场景 | 生产环境 | 开发环境 |
| 内存占用 | 启动即占满 | 按需分配 |
十一、调试技巧
11.1 查看自动配置报告
bash
# 启动时加 --debug 或设置
debug=true
输出:
markdown
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
AopAutoConfiguration matched:
- @ConditionalOnProperty (spring.aop.auto=true) matched
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory'
11.2 查看启动耗时
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: startup
访问 /actuator/startup 可查看每个阶段的耗时。
11.3 自定义启动事件监听
java
@Component
public class StartupLogger implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
log.info("应用启动完成,耗时:{}ms",
event.getTimeTaken().toMillis());
}
}
十二、面试速答模板
Q:Spring Boot 启动流程?
SpringApplication.run() 分两大步:构造和运行。
构造时推断应用类型、加载 Initializer 和 Listener。
运行时按序执行:创建 Environment → 创建 ApplicationContext → prepareContext(执行 Initializer、加载 Bean 定义)→ refresh(核心!执行 BeanFactoryPostProcessor、注册 BeanPostProcessor、实例化单例 Bean、启动 Web 服务器)→ afterRefresh → 发布就绪事件。
其中 refresh 最耗时,占 70%+,主要是 Bean 创建和自动配置处理。
Q:refresh() 哪步最关键?
invokeBeanFactoryPostProcessors,触发 ConfigurationClassPostProcessor,解析 @Configuration、@ComponentScan、@Import,完成自动配置。没有这一步,所有 Bean 定义都不会被注册。
Q:自动配置怎么生效的?
@SpringBootApplication 包含 @EnableAutoConfiguration,它 @Import 了 AutoConfigurationImportSelector,从 spring.factories 加载自动配置类,再经 @Conditional 过滤,条件满足的配置类注册到容器。
下一篇我们聊 Spring Bean 生命周期:从实例化到销毁,Bean 到底经历了哪些回调?为什么InitializingBean 比 @PostConstruct 先执行?BeanPostProcessor 和 Aware 接口谁先谁后?
本文是 Java 技术系列第 8 篇,系列目录: