Spring Boot 启动流程与 @SpringBootApplication 核心原理详解

一个最简单的 Spring Boot 应用通常只有一个启动类和一行 SpringApplication.run()。但这行代码背后完成了环境准备、配置加载、容器创建、Bean 初始化、自动装配、Web 服务器启动以及事件发布等一系列工作。

本文以 Spring Boot 3.x 为背景,从应用入口出发,梳理 Spring Boot 的启动流程,并解析 @SpringBootApplication 的三个核心组成部分。

一、Spring Boot 的启动入口

典型的启动类如下:

复制代码
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

这里包含两个关键点:

  • @SpringBootApplication 声明主配置类,并开启组件扫描和自动配置;

  • SpringApplication.run() 创建并启动 Spring 应用,最终返回 ConfigurableApplicationContext

二、SpringApplication 初始化阶段

创建 SpringApplication 对象时,主要完成的是启动前的元数据准备,而不是立即创建所有 Bean。

1. 保存主配置类

启动类 DemoApplication.class 会被保存为主要配置源,后续作为 Bean 定义加载入口。

2. 推断应用类型

Spring Boot 会根据类路径判断应用属于哪种类型:

  • SERVLET:传统 Spring MVC Servlet 应用;

  • REACTIVE:Spring WebFlux 响应式应用;

  • NONE:非 Web 应用。

应用类型会影响后续创建哪一种 ApplicationContext。例如 Servlet Web 应用需要创建适用于 Servlet 环境的上下文,并启动内嵌 Web 服务器。

3. 准备初始化器与监听器

Spring Boot 会收集启动过程中需要使用的 ApplicationContextInitializer、应用监听器等扩展组件。初始化器用于在容器刷新前调整上下文,监听器则用于接收不同阶段的启动事件。

不同 Spring Boot 版本在内部加载和组织这些组件的具体实现可能有所调整,但其职责基本不变:为正式启动准备配置源、应用类型和扩展点。

三、SpringApplication.run() 核心流程

run() 是整个启动过程的核心。省略异常处理和部分扩展逻辑后,可以将其理解为:

1. 发布开始启动事件

应用刚开始运行时会发布 ApplicationStartingEvent。此时 Spring 容器尚未创建,因此需要监听非常早期事件的组件,不能只依赖普通 Bean 注册。

2. 准备 Environment

Spring Boot 会创建并配置 Environment,统一管理应用配置。常见配置来源包括:

  • application.propertiesapplication.yml

  • 环境变量;

  • JVM 系统属性;

  • 命令行参数;

  • 通过其他机制添加的配置源。

随后发布 ApplicationEnvironmentPreparedEvent。此时环境已经准备完成,但 ApplicationContext 尚未创建。

Spring Boot 根据配置决定是否输出 Banner。Banner 本身不会影响容器功能,只是启动阶段的展示能力,可通过配置关闭或替换。

4. 创建 ApplicationContext

Spring Boot 根据此前推断出的应用类型选择合适的上下文实现。可以简单理解为:

应用类型 上下文职责
Servlet Web 管理 Spring MVC 组件及 Servlet Web 环境
Reactive Web 管理 WebFlux 响应式 Web 环境
非 Web 管理普通 Spring Bean,不启动 Web 服务器

上下文创建后,Spring Boot 会调用 ApplicationContextInitializer,并发布 ApplicationContextInitializedEvent。此时初始化器已经执行,但 Bean 定义尚未全部加载。

5. 准备上下文并加载 Bean 定义

接下来会把启动参数、Environment 和主配置类等信息关联到上下文,并将启动类作为配置源加载。Spring 会解析启动类上的注解,注册用户组件以及符合条件的自动配置。

Bean 定义加载完成、容器正式刷新之前,会发布 ApplicationPreparedEvent

6. 调用 refresh() 刷新容器

refresh() 是 Spring 容器真正完成初始化的关键步骤。其内部工作很多,可以重点记住以下内容:

  1. 准备 BeanFactory;

  2. 执行 BeanFactoryPostProcessor

  3. 注册 BeanPostProcessor

  4. 初始化事件广播器等基础设施;

  5. 创建非懒加载单例 Bean,并完成依赖注入;

  6. 执行 Bean 生命周期回调;

  7. 完成上下文刷新并发布相应事件。

对于 Servlet Web 应用,内嵌 Tomcat 等 Web 服务器的创建和启动也与 Web 上下文刷新过程相结合。也就是说,Web 服务器不是在所有 Bean 初始化完成很久以后才单独启动,而是整个上下文刷新生命周期的重要组成部分。

7. 执行刷新后的扩展逻辑

容器刷新完成后,Spring Boot 会执行内部的启动后处理,并发布 ApplicationStartedEvent。注意,此时 ApplicationRunnerCommandLineRunner 还没有执行。

8. 执行 Runner

项目中可以声明启动后任务:

复制代码
@Component
public class DataWarmUpRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {
        System.out.println("执行缓存预热或必要的数据检查");
    }
}

9. 发布应用就绪事件

所有 Runner 执行完成后,Spring Boot 发布 ApplicationReadyEvent,随后应用进入可接收流量的就绪状态。

如果启动过程中出现未处理异常,则会尝试发布 ApplicationFailedEvent,并执行失败分析、上下文关闭等处理。

四、@SpringBootApplication 核心解析

@SpringBootApplication 不是一个全新的底层机制,而是一个组合注解,主要整合了三个能力:

复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
}

1. @SpringBootConfiguration

@SpringBootConfiguration 本质上是 Spring Boot 对 @Configuration 的进一步封装,用于标记当前类是应用的主配置类。

因此启动类中也可以声明 Bean:

复制代码
@SpringBootApplication
public class DemoApplication {

    @Bean
    public Clock clock() {
        return Clock.systemDefaultZone();
    }
}

一般一个应用只保留一个明确的主启动配置类,避免测试和配置查找时产生歧义。

2. @ComponentScan

@ComponentScan 负责扫描并注册用户编写的组件,例如:

  • @Component

  • @Service

  • @Repository

  • @Controller@RestController

  • 其他配置类。

默认扫描范围从启动类所在包开始,向下扫描其子包。因此推荐把启动类放在项目的根包中:

复制代码
com.example.demo
├── DemoApplication.java
├── controller
├── service
└── repository

如果把启动类放得太深,同级或上级包中的组件可能无法被扫描,最终表现为 Bean 找不到。

3. @EnableAutoConfiguration

@EnableAutoConfiguration 开启 Spring Boot 自动配置。它会导入自动配置选择逻辑,获取候选自动配置类,再根据条件决定哪些配置生效。

Spring Boot 3.x 中,自动配置候选类通常声明在:

复制代码
META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports

候选配置不会全部无条件生效,而是由条件注解筛选:

|--------------------------------|---------------|
| 条件注解 | 含义 |
| @ConditionalOnClass | 类路径中存在指定类 |
| @ConditionalOnMissingBean | 容器中不存在指定 Bean |
| @ConditionalOnBean | 容器中存在指定 Bean |
| @ConditionalOnProperty | 配置属性满足条件 |
| @ConditionalOnWebApplication | 当前属于 Web 应用 |

例如引入 Web Starter 后,类路径具备 Spring MVC 和 Servlet 相关类,Web MVC 自动配置才可能生效;如果用户自己声明了某些 Bean,带有 @ConditionalOnMissingBean 的默认配置便会退让。

因此,自动配置的本质可以概括为:

根据类路径、已有 Bean、配置属性和应用类型等条件,按需注册一组预定义 Bean。

参考资料

相关推荐
fatcoder1 小时前
玩转 Redis · Set 篇
前端·redis·后端
许彰午1 小时前
11-rawSql占位符替换
java·低代码·架构
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 6 章 Spring Boot 最佳实践 阅读笔记 12
android·spring boot·笔记
掘金者阿豪1 小时前
你的公网 IP 是专线还是动态变化的?一文讲透动态 IP 与固定 IP 的那些事
前端·后端
超超不吵吵1 小时前
Java AI转型实战(八):RAG完整链路实战
后端
码匠许师傅1 小时前
【C++ 面试真题】聊聊 C++ 的类型特征( type_traits)
java·c++·面试
java1234_小锋1 小时前
Spring框架的创始人开发了一个Java AI智能体框架
java·人工智能·spring
夏天拐跑了西瓜1 小时前
Eureka注册中心——单机与高可用集群搭建
java·spring cloud·微服务·eureka
Caster_Z1 小时前
IntelliJ IDEA 安装 Qoder CN (Formerly Lingma)
java·ide·intellij-idea