SpringBoot 的启动流程是怎样的?

#Java#面试八股

SpringBoot 的启动流程是怎样的?

结论先行:SpringApplication.run() 是一条编排好的流水线:准备环境 → 创建 ApplicationContext → 准备上下文(加载主配置类、执行 Initializer、广播事件)→ refresh(IoC 容器的主线,内嵌 Web 容器在此启动)→ 收尾(执行 Runner、发布 ready 事件)。面试讲清两段即可:prepareContext 做了什么,refresh() 里 onRefresh 与 finishBeanFactoryInitialization 的先后。

一、主流程

SpringApplication.run(主类, args)
 ├─ 1. 构造 SpringApplication:推断 WebApplicationType、加载 Initializer 与 Listener
 ├─ 2. run()
 │    ├─ 启动计时、发布 ApplicationStartingEvent
 │    ├─ 准备 Environment(加载 application.yml/properties、命令行参数、profile)
 │    ├─ 打印 Banner、创建 ApplicationContext(按 Web 类型选实现类)
 │    ├─ prepareContext
 │    │     ├─ 设置 environment、注册主配置类(primarySources)
 │    │     ├─ 执行 ApplicationContextInitializer
 │    │     └─ 发布 ApplicationContextInitializedEvent / PreparedEvent
 │    ├─ refreshContext(ctx)          ← 核心
 │    │     └─ AbstractApplicationContext#refresh()
 │    │           ├─ obtainFreshBeanFactory
 │    │           ├─ invokeBeanFactoryPostProcessors   ← 解析配置类、自动配置
 │    │           ├─ registerBeanPostProcessors        ← AOP、@Autowired 处理器
 │    │           ├─ onRefresh                          ← 内嵌 Tomcat 创建并启动
 │    │           ├─ finishBeanFactoryInitialization     ← 实例化所有单例
 │    │           └─ finishRefresh                       ← 发布 ContextRefreshedEvent
 │    ├─ afterRefresh:调用 ApplicationRunner / CommandLineRunner
 │    ├─ 发布 ApplicationReadyEvent(此时才算可接流量)
 │    └─ 异常 → 发布 ApplicationFailedEvent,进程退出

二、关键阶段与产物

阶段关键方法产物/事件
环境准备prepareEnvironmentEnvironment(含 profile、配置源)
上下文创建createApplicationContextAnnotationConfigServletWebServerApplicationContext
上下文准备prepareContext主配置类注册、Initializer 执行
容器刷新refreshContext全部单例 Bean、内嵌 Web 容器
收尾afterRefresh / callRunnersRunner 执行、ready 事件

三、用代码观察启动过程

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(DemoApplication.class);

        // 1. 自定义启动监听器:观察各阶段
        app.addListeners((ApplicationListener<ApplicationEvent>) event ->
                System.out.println("[event] " + event.getClass().getSimpleName()));

        // 2. 设置默认属性(优先级低于命令行与配置文件)
        app.setDefaultProperties(Map.of("server.port", "8081"));

        // 3. 关闭 Banner、控制懒加载
        app.setBannerMode(Banner.Mode.OFF);
        app.setLazyInitialization(false);

        app.run(args);
    }
}
// Runner 执行时机:容器 refresh 完成、ready 事件之前
@Component
public class StartupTask implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        System.out.println("Runner 执行,此时 Bean 与 Web 容器均已就绪");
    }
}

四、两个高频追问点

① 自动配置发生在哪一步? 在 invokeBeanFactoryPostProcessors:ConfigurationClassPostProcessor 解析 @SpringBootApplication,其中 @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 拿到候选配置类,经 @Conditional 过滤后注册 BeanDefinition。此时还没有任何业务 Bean 被实例化。

② 内嵌 Tomcat 在哪一步启动? 在 refresh() 的 onRefresh():ServletWebServerApplicationContext#onRefresh → createWebServer() → TomcatServletWebServerFactory#getWebServer 创建并启动 Tomcat,随后 finishRefresh 里 WebServerStartStopLifecycle 完成端口绑定与 Connector 启动。

五、常见追问

  • 问:CommandLineRunner 与 ApplicationRunner 区别? 前者拿原始 String[],后者拿解析后的 ApplicationArguments(支持 --key=value 形式);执行顺序可用 @Order 控制。
  • 问:启动失败怎么定位? 看 ApplicationFailedEvent 与 FailureAnalyzer 输出的报告;--debug 可打印自动配置条件评估结果。
  • 问:refresh() 能调两次吗? 不能,AbstractApplicationContext 有 active 标志,重复 refresh 会抛 IllegalStateException。
  • 问:Spring Cloud 的 bootstrap 上下文在哪? 在 prepareEnvironment 阶段由 BootstrapApplicationListener 触发,父上下文先创建,用于拉取配置中心数据,所以配置中心的值能在主上下文初始化前生效。

小结

把启动流程压成五个词:环境 → 上下文 → 准备 → 刷新 → 收尾。再加两个锚点——自动配置在 invokeBeanFactoryPostProcessors、Tomcat 在 onRefresh——足以应对大部分追问,也能解释"为什么配置类里能拿到 Environment,但 Bean 还没创建"。

笔记加载中…