SpringBoot 的启动流程是怎样的?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官抛出这个问题,绝不仅仅是希望你复述几个步骤。其核心考察点在于:

  1. 对 Spring Boot “约定大于配置” 理念的理解:你是否清楚 Spring Boot 如何简化了传统 Spring 应用的繁杂配置?这直接关联到自动配置 (Auto-Configuration) 的核心机制。
  2. 对 Spring 框架 IOC 容器启动过程的掌握:Spring Boot 本质上是 Spring 框架的增强和封装。面试官想知道你是否了解其底层依然依赖 SpringApplication.run()AbstractApplicationContext.refresh() 这一核心生命周期。
  3. 对启动过程中关键扩展点的认知:你是否知道有哪些 “钩子”(如 ApplicationRunner, CommandLineRunner, 事件监听器 ApplicationListener)可以在启动的不同阶段介入,并理解它们的执行时机?
  4. 源码追踪和问题排查能力:能清晰描述启动流程的候选人,通常具备通过阅读启动日志和源码来分析和解决启动类失败、Bean 加载异常、配置不生效等实际问题的能力。

核心答案

Spring Boot 的启动流程可以概括为:初始化 Spring 应用实例,准备环境,创建并刷新 Spring IOC 容器,最终启动内嵌的 Web 服务器。

其核心入口是 SpringApplication.run(Class<?> primarySource, String... args) 静态方法。整个过程主要分为两大阶段:SpringApplication 实例的初始化运行

深度解析

让我们深入到关键步骤中,理解其原理和机制。

原理/机制

可以将整个流程分解为以下几个核心步骤:

1. 初始化阶段 (new SpringApplication(...))

  • 推断应用类型:根据类路径下是否存在特定的类(如 Servlet, DispatcherHandler),判断应用是普通的 Servlet 应用(Spring MVC)、响应式 Reactive Web 应用,还是非 Web 应用。这决定了后续创建的 ApplicationContext 类型(如 AnnotationConfigServletWebServerApplicationContext)。
  • 加载 ApplicationContextInitializer:利用 SpringFactoriesLoader 机制,从 META-INF/spring.factories 文件中加载并实例化所有声明的初始化器。它们用于在 ConfigurableApplicationContext refresh() 之前,对上下文进行自定义初始化。
  • 加载 ApplicationListener:同样通过 spring.factories 加载应用事件监听器,用于监听在整个启动过程中发布的各种事件(如 ApplicationStartedEvent, ApplicationReadyEvent)。

2. 运行阶段 (SpringApplication.run())

  • 准备环境 (prepareEnvironment):创建并配置应用环境 (ConfigurableEnvironment),这会加载所有的属性配置,包括 application.properties/yml、命令行参数、系统属性等,并激活指定的 profile
  • 创建并准备应用上下文 (createApplicationContext):根据第一步推断的应用类型,实例化对应的 ApplicationContext。然后,调用之前加载的所有 ApplicationContextInitializer 对其进行初始化,并将环境绑定到上下文。
  • 刷新应用上下文 (refreshContext - 最核心的一步):这是 Spring 框架容器启动的核心流程,由 AbstractApplicationContext.refresh() 方法定义。Spring Boot 在此基础上,通过 refresh(ApplicationContext) 方法进行了关键增强:
    • Bean 定义加载:通过 AnnotatedBeanDefinitionReaderClassPathBeanDefinitionScanner 扫描并注册所有 Bean(包括启动类上的 @SpringBootApplication 注解所涵盖的组件)。
    • 自动配置 (Auto-Configuration):这是 Spring Boot 的灵魂。@SpringBootApplication 包含了 @EnableAutoConfiguration,它会通过 SpringFactoriesLoader 加载 META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration 键下指定的所有自动配置类。这些配置类根据条件(@ConditionalOnClass, @ConditionalOnMissingBean 等)决定是否生效,从而自动创建并配置所需的 Bean(如 DataSource, DispatcherServlet 等)。
    • 启动内嵌 Web 服务器:如果是 Web 应用,在 refresh 过程的最后阶段,Spring Boot 会从容器中获取 WebServerFactory Bean(如 TomcatServletWebServerFactory),并调用其 getWebServer() 方法创建和启动内嵌的 Tomcat、Jetty 或 Undertow 服务器。
  • 执行 Runner (callRunners):容器刷新完成后,会从容器中获取 ApplicationRunnerCommandLineRunner 接口的实现类,并执行它们的 run 方法。这两个接口常用于在应用完全启动后,执行一些初始化业务逻辑。

代码示例

一个高度简化的 SpringApplication.run() 核心逻辑示意:

// 这是一个概念性伪代码,用于说明流程
public class SpringApplication {
    public ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        // 1. 初始化
        this.primarySources = Collections.singleton(primarySource);
        this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 推断类型
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 加载Initializer
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class)); // 加载Listener

        // 2. 运行
        ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); // 准备环境
        ConfigurableApplicationContext context = this.createApplicationContext(); // 创建上下文
        this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 准备上下文
        this.refreshContext(context); // <-- 核心:刷新上下文(执行自动配置,启动服务器)
        this.callRunners(context, args); // 执行Runner
        return context;
    }
}

最佳实践与注意事项

  • 自定义启动 Banner:在 resources 下创建 banner.txt 可以自定义启动 Logo。
  • 使用事件监听器:实现 ApplicationListener 接口(或使用 @EventListener 注解)来在特定启动阶段执行代码,例如在 ApplicationReadyEvent 事件后执行数据库检查,这比在 Runner 中执行更安全(确保所有 Bean 已就绪)。
  • 理解自动配置原理:当需要覆盖默认配置时,不是直接排除自动配置类,而是通过定义自己的 @Bean(利用 @ConditionalOnMissingBean 条件)或使用 application.properties 中的配置属性来定制。
  • 注意 Runner 的执行顺序:可以通过 @Order 注解来指定多个 ApplicationRunnerCommandLineRunner 的执行顺序。

常见误区

  • 混淆 “自动配置” 和 “组件扫描”@SpringBootApplication 包含了 @ComponentScan,它负责扫描当前包及其子包下的 @Component 类。而自动配置是通过 spring.factories 从 classpath 的 jar 包中加载的,范围是全局的。
  • 认为 main 方法执行完应用才启动:实际上,SpringApplication.run() 是一个阻塞方法,它会一直运行直到应用上下文被关闭(对于 Web 应用,就是内嵌服务器在运行)。
  • 忽视启动失败分析:启动失败时,Spring Boot 会打印非常详细的 FailureAnalyzer 报告。学会阅读这些报告是快速定位问题的关键。

总结

Spring Boot 的启动流程是一个精心设计的、事件驱动的过程,它通过 SpringApplication 类封装了传统 Spring 容器的初始化、refresh() 核心生命周期,并巧妙地利用 SpringFactoriesLoader 机制和条件注解实现了 “开箱即用” 的自动配置,最终通过内嵌 Web 服务器使得应用能够独立运行。理解这个过程,是掌握 Spring Boot 和高效排查启动问题的基石。