Spring 源码系列(23): SpringApplication.run() 启动全流程

📌 阅读前提示:第 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 推断:

  • DispatcherServletAnnotationConfigServletWebServerApplicationContext
  • 有 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 事件并清理上下文

🧪 面试题自测

  1. SpringApplication.run() 的主要阶段有哪些?
  2. 内嵌 Tomcat 在什么时机启动?
  3. prepareEnvironment 加载配置的优先级?
  4. createApplicationContext 如何决定容器类型?
  5. ApplicationRunnerCommandLineRunner 的执行时机?
  6. refresh() 在 run 流程中处于第几步?它和第 5 篇的关系?

🔧 Debug 小技巧

SpringApplication.run 第一行和 refreshContext 处打断电,单步跟 prepareEnvironment → createApplicationContext → prepareContext → refreshContext;再在 callRunners 断点,看你的 ApplicationRunner 实现如何被按 @Order 调用。


下一篇预告

第 24 篇:starter 机制与自定义 starter——为什么引入一个 spring-boot-starter-xxx 就自动配好一切?如何自己写一个 starter 给团队复用?


如果这篇对你有帮助,欢迎 点赞 · 收藏 · 关注 三连支持。

Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不才不才不不才

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值