一个最简单的 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.properties或application.yml; -
环境变量;
-
JVM 系统属性;
-
命令行参数;
-
通过其他机制添加的配置源。
随后发布 ApplicationEnvironmentPreparedEvent。此时环境已经准备完成,但 ApplicationContext 尚未创建。
3. 打印 Banner
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 容器真正完成初始化的关键步骤。其内部工作很多,可以重点记住以下内容:
-
准备 BeanFactory;
-
执行
BeanFactoryPostProcessor; -
注册
BeanPostProcessor; -
初始化事件广播器等基础设施;
-
创建非懒加载单例 Bean,并完成依赖注入;
-
执行 Bean 生命周期回调;
-
完成上下文刷新并发布相应事件。
对于 Servlet Web 应用,内嵌 Tomcat 等 Web 服务器的创建和启动也与 Web 上下文刷新过程相结合。也就是说,Web 服务器不是在所有 Bean 初始化完成很久以后才单独启动,而是整个上下文刷新生命周期的重要组成部分。
7. 执行刷新后的扩展逻辑
容器刷新完成后,Spring Boot 会执行内部的启动后处理,并发布 ApplicationStartedEvent。注意,此时 ApplicationRunner 和 CommandLineRunner 还没有执行。
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。