📌 阅读前提示:第 20–22 篇讲了 Boot 的注解与条件机制。本篇把镜头拉到
main方法的SpringApplication.run()——从一行代码到容器就绪,中间到底发生了什么。这是 Boot 源码的「主干」,也是把前面 IoC(第 5 篇 refresh)和 Boot 装配串起来的关键。
一、引子:一行 run 背后是十几个阶段
SpringApplication.run(DemoApplication.class, args);
这行代码干的事,远比「启动一个 Spring 容器」多:推断应用类型(Servlet/Reactive/None)、准备 Environment、加载 ApplicationListener、创建并刷新 ApplicationContext、调用 ApplicationRunner/CommandLineRunner、最后发布 Started 事件。
二、源码追踪:run 的主干
// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
// ① 计时 + 启动监听器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// ② 配置 Headless 模式、获取 SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// ③ 封装参数 + 准备 Environment(环境变量/配置文件)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// ④ 打印 Banner
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// ⑤ 根据 classpath 推断并创建 ApplicationContext(类型)
context = createApplicationContext();
// ⑥ 准备容器:设置 environment、加载 bean 源、应用初始化器
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// ⑦ ★ 刷新容器(即第5篇的 refresh() 十二步!)
refreshContext(context);
// ⑧ 刷新后处理(空钩子,可扩展)
afterRefresh(context, applicationArguments);
// ⑨ 调用 Runner(ApplicationRunner / CommandLineRunner)
callRunners(context, applicationArguments);
// ⑩ 发布 Started / Ready 事件
listeners.started(context);
listeners.running(context);
return context;
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners); // 失败兜底
throw new IllegalStateException(ex);
}
}
三、三个最容易被忽略的阶段
3.1 prepareEnvironment:配置从哪来
prepareEnvironment 会按优先级加载:系统属性 → 环境变量 → 命令行参数 → application.properties/yml → 默认。这就是为什么 application.yml 里的配置能注入到 @Value / @ConfigurationProperties。
3.2 createApplicationContext:类型怎么定
根据 classpath 推断:
- 有
DispatcherServlet→AnnotationConfigServletWebServerApplicationContext - 有 WebFlux → Reactive 容器
- 都没有 → 普通
AnnotationConfigApplicationContext
💡 重点:创建的是 Web 容器,所以它的
onRefresh还会启动内嵌 Tomcat(第 5 篇 refresh 第 12 步里的onRefresh被 Web 容器重写,触发ServletWebServerApplicationContext启动服务器)。这就解释了「Boot 应用为什么不用外置 Tomcat」。
3.3 callRunners:启动后钩子
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());
// 按 @Order 排序后依次调用
for (Object runner : runners) { /* runner.run(...) */ }
}
📌 结论:
ApplicationRunner/CommandLineRunner是「容器完全就绪后、应用对外提供服务前」的执行钩子,常用于缓存预热、数据初始化。
四、SpringApplication.run 启动时序图

五、常见误区
| 误区 | 正解 |
|---|---|
| run() 只做 refresh() | 不,还含环境准备、Banner、Runner、事件发布等十几阶段 |
| 内嵌 Tomcat 在 refresh 之后启动 | 在 refresh 的 onRefresh(第12步)由 Web 容器重写触发 |
| CommandLineRunner 在 Bean 初始化前执行 | 不,在 refresh 完成后、容器 ready 时调用 |
| Environment 只包含 application.yml | 还含系统属性、环境变量、命令行参数,yml 仅是其中一层 |
| 应用启动失败无兜底 | handleRunFailure 会发布 Failed 事件并清理上下文 |
🧪 面试题自测
SpringApplication.run()的主要阶段有哪些?- 内嵌 Tomcat 在什么时机启动?
prepareEnvironment加载配置的优先级?createApplicationContext如何决定容器类型?ApplicationRunner和CommandLineRunner的执行时机?refresh()在 run 流程中处于第几步?它和第 5 篇的关系?
🔧 Debug 小技巧
在 SpringApplication.run 第一行和 refreshContext 处打断电,单步跟 prepareEnvironment → createApplicationContext → prepareContext → refreshContext;再在 callRunners 断点,看你的 ApplicationRunner 实现如何被按 @Order 调用。
下一篇预告
第 24 篇:starter 机制与自定义 starter——为什么引入一个 spring-boot-starter-xxx 就自动配好一切?如何自己写一个 starter 给团队复用?
如果这篇对你有帮助,欢迎 点赞 · 收藏 · 关注 三连支持。
Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。
: SpringApplication.run 启动全流程&spm=1001.2101.3001.5002&articleId=163918702&d=1&t=3&u=7f53562e857c4b89a9e02825f96cf71a)
374

被折叠的 条评论
为什么被折叠?



