Spring Boot 启动流程源码浅析:从 main 方法到容器初始化
Spring Boot 启动流程源码浅析:从 main 方法到容器初始化
引言
Spring Boot 凭借“一键启动”的特性极大简化了 Spring 应用的开发,但其背后隐藏着复杂的容器初始化逻辑。@SpringBootApplication 注解仅是入口,真正的启动流程涉及应用上下文创建、自动配置加载、Bean 实例化、嵌入式服务器启动等核心环节。本文将从 main 方法的 SpringApplication.run() 出发,逐行拆解源码,揭示 Spring Boot 如何从一行代码到完整运行的 Web 容器。
技术背景
传统 Spring 启动痛点
早期 Spring 应用需手动配置 web.xml 或 JavaConfig,通过 new ClassPathXmlApplicationContext("beans.xml") 初始化容器,步骤繁琐且易出错:
- 需显式指定配置文件路径;
- 依赖外部服务器(如 Tomcat)部署 WAR 包;
- 组件扫描范围需手动控制,易遗漏或冗余。
Spring Boot 的革新
Spring Boot 提出“约定优于配置”,通过 SpringApplication 类封装启动逻辑,实现:
- 自动推断应用类型(Servlet/Reactive);
- 内嵌服务器(Tomcat/Jetty),无需外部部署;
- 自动配置(基于类路径依赖动态加载 Bean);
- 零 XML 配置,纯注解驱动。
应用使用场景
- 日常开发调试:理解启动流程可快速定位“Bean 未加载”“配置冲突”等问题。
- 性能优化:通过调整启动参数(如
@SpringBootApplication(scanBasePackages = "..."))减少扫描范围,提升启动速度。 - 定制化启动:修改
SpringApplication行为(如自定义 Banner、环境变量)满足特殊需求。 - 框架扩展:开发 Spring Boot Starter 时需遵循启动流程规范(如
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件加载)。
不同场景下详细代码实现
场景 1:标准启动流程(最简形式)
启动类(SimpleApplication.java)
package com.example.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SimpleApplication {
public static void main(String[] args) {
// 核心:调用 SpringApplication.run() 启动容器
SpringApplication.run(SimpleApplication.class, args);
}
}
依赖配置(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>boot-startup-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Web starter:自动引入 Tomcat、Spring MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
场景 2:定制化 SpringApplication(动态调整启动行为)
定制启动类(CustomSpringApplication.java)
package com.example.boot;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.env.ConfigurableEnvironment;
@SpringBootApplication
public class CustomSpringApplication {
public static void main(String[] args) {
// 方式 1:通过 SpringApplication 构造器定制
SpringApplication app = new SpringApplication(CustomSpringApplication.class);
app.setBannerMode(Banner.Mode.OFF); // 关闭 Banner
app.setAdditionalProfiles("dev"); // 激活 dev 环境
ConfigurableEnvironment env = app.run(args).getEnvironment();
printEnvInfo(env);
// 方式 2:通过 SpringApplicationBuilder 流式 API 定制(更简洁)
new SpringApplicationBuilder(CustomSpringApplication.class)
.bannerMode(Banner.Mode.CONSOLE)
.profiles("prod")
.run(args);
}
private static void printEnvInfo(ConfigurableEnvironment env) {
System.out.println("\n=== 环境变量 ===");
System.out.println("激活环境:" + env.getProperty("spring.profiles.active"));
System.out.println("服务器端口:" + env.getProperty("server.port", "8080"));
}
}
场景 3:非 Web 应用启动(如批处理任务)
批处理启动类(BatchApplication.java)
package com.example.batch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class BatchApplication {
public static void main(String[] args) {
// 强制指定为非 Web 应用(即使依赖了 web starter)
SpringApplication app = new SpringApplication(BatchApplication.class);
app.setWebApplicationType(WebApplicationType.NONE); // 关键:禁用 Web 环境
ConfigurableApplicationContext context = app.run(args);
// 执行批处理逻辑
BatchTask task = context.getBean(BatchTask.class);
task.execute();
context.close(); // 非 Web 应用需手动关闭上下文
}
}
// 批处理任务组件
@Component
public class BatchTask {
public void execute() {
System.out.println("执行批处理任务...");
}
}
原理解释
Spring Boot 启动核心阶段
SpringApplication.run() 方法可拆分为两大阶段:初始化 SpringApplication 实例 → 执行 run 方法启动容器。
阶段 1:初始化 SpringApplication(构造器逻辑)
源码位置:org.springframework.boot.SpringApplication#SpringApplication(Class<?>... primarySources)
核心步骤:
- 推断应用类型:通过类路径是否存在
Servlet/Reactive相关类,判断是SERVLET(传统 Web)、REACTIVE(响应式 Web)还是NONE(非 Web)。this.webApplicationType = WebApplicationType.deduceFromClasspath(); - 加载初始化器和监听器:从
META-INF/spring.factories或META-INF/spring/org.springframework.boot.SpringApplicationRunListener.imports加载SpringApplicationRunListener和ApplicationContextInitializer。setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); - 推断主启动类:通过堆栈跟踪找到包含
main方法的类(即标注@SpringBootApplication的类)。this.mainApplicationClass = deduceMainApplicationClass();
阶段 2:执行 run 方法(容器启动核心)
源码位置:org.springframework.boot.SpringApplication#run(String... args)
核心步骤(对应流程图):
| 步骤 | 方法 | 作用 |
|---|---|---|
| 1 | prepareEnvironment | 准备环境:加载配置文件(application.yml)、激活 Profile、设置系统属性。 |
| 2 | createApplicationContext | 创建应用上下文:根据 Web 应用类型选择 AnnotationConfigServletWebServerApplicationContext(Servlet)或 AnnotationConfigReactiveWebServerApplicationContext(Reactive)。 |
| 3 | prepareContext | 预处理上下文:注册主启动类为配置源、应用初始化器(ApplicationContextInitializer)。 |
| 4 | refreshContext | 刷新上下文:核心!触发 Spring 容器的 AbstractApplicationContext.refresh(),完成 Bean 定义加载、自动配置、Bean 实例化、嵌入式服务器启动。 |
| 5 | afterRefresh | 刷新后钩子:空方法,供子类扩展(如执行自定义初始化逻辑)。 |
| 6 | callRunners | 执行 CommandLineRunner/ApplicationRunner 接口的 Bean(如启动时执行数据初始化)。 |
关键源码片段解析
1. 环境准备(prepareEnvironment)
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 1. 创建环境(根据 Web 类型选择 StandardServletEnvironment 或 StandardEnvironment)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置环境:绑定命令行参数(如 --server.port=8081)、激活 Profile
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 通知监听器环境准备完成(如打印激活的 Profile)
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
return environment;
}
2. 上下文刷新(refreshContext)
最终调用 AbstractApplicationContext.refresh(),其中与 Spring Boot 强相关的步骤:
invokeBeanFactoryPostProcessors:执行BeanFactoryPostProcessor,包括ConfigurationClassPostProcessor(解析@Configuration类,触发@ComponentScan和@EnableAutoConfiguration)。onRefresh:在ServletWebServerApplicationContext中重写此方法,调用createWebServer()启动嵌入式 Tomcat。
核心特性
| 特性 | 说明 |
|---|---|
| 自动推断应用类型 | 无需手动指定 Web 环境,通过类路径智能判断(如存在 DispatcherServlet 则为 SERVLET)。 |
| 事件驱动架构 | 通过 SpringApplicationRunListener 发布启动阶段事件(如 ApplicationStartingEvent、ApplicationReadyEvent),支持自定义监听器扩展。 |
| 环境隔离 | 支持多 Profile 配置(application-dev.yml、application-prod.yml),通过 --spring.profiles.active 切换。 |
| 嵌入式服务器 | 内嵌 Tomcat/Jetty,无需外部部署,通过 ServerProperties 自动配置端口、上下文路径。 |
原理流程图
环境准备
开发环境
- JDK 17+、Maven 3.8+、IntelliJ IDEA
- 依赖:
spring-boot-starter-web(自动引入 Tomcat、Spring MVC)
实际详细应用代码示例实现
示例 1:监听启动事件(自定义监听器)
自定义监听器(StartupListener.java)
package com.example.boot.listener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
// 需通过 META-INF/spring/org.springframework.boot.SpringApplicationRunListener.imports 注册
public class StartupListener implements SpringApplicationRunListener {
// 构造器必须接收 SpringApplication 和 String[] args(Spring Boot 反射调用)
public StartupListener(SpringApplication application, String[] args) {}
// 环境准备完成后触发
@Override
public void environmentPrepared(ConfigurableEnvironment environment,
org.springframework.boot.DefaultBootstrapContext bootstrapContext) {
System.out.println("\n=== 自定义监听器:环境准备完成 ===");
// 动态添加环境变量(优先级高于配置文件)
Map<String, Object> customProps = new HashMap<>();
customProps.put("app.custom.key", "custom-value-from-listener");
environment.getPropertySources().addLast(new MapPropertySource("customProps", customProps));
}
// 上下文准备完成后触发
@Override
public void contextPrepared(ConfigurableApplicationContext context,
org.springframework.boot.DefaultBootstrapContext bootstrapContext) {
System.out.println("=== 自定义监听器:上下文准备完成 ===");
}
}
注册监听器(META-INF/spring/org.springframework.boot.SpringApplicationRunListener.imports)
在 src/main/resources/META-INF/spring/ 目录下创建文件 org.springframework.boot.SpringApplicationRunListener.imports,内容为:
com.example.boot.listener.StartupListener
测试控制器(TestController.java)
package com.example.boot.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Value("${app.custom.key:default}")
private String customKey;
@GetMapping("/custom-key")
public String getCustomKey() {
return "自定义环境变量值:" + customKey; // 输出 "custom-value-from-listener"
}
}
示例 2:自定义 CommandLineRunner(启动时执行逻辑)
CommandLineRunner 实现类(InitRunner.java)
package com.example.boot.runner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Order(1) // 执行顺序(值越小越先执行)
public class InitRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("\n=== CommandLineRunner 执行:初始化数据 ===");
System.out.println("命令行参数:" + Arrays.toString(args));
// 模拟初始化逻辑(如加载缓存、创建初始用户)
}
}
@Component
@Order(2)
public class AnotherRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("=== AnotherRunner 执行:发送启动通知 ===");
}
}
运行结果
示例 1 运行日志
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.1.0)
=== 自定义监听器:环境准备完成 ===
=== 自定义监听器:上下文准备完成 ===
2023-07-20T15:00:00.000+08:00 INFO 12345 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
...(省略 Tomcat 启动日志)
=== CommandLineRunner 执行:初始化数据 ===
命令行参数:[]
=== AnotherRunner 执行:发送启动通知 ===
2023-07-20T15:00:05.000+08:00 INFO 12345 --- [ main] c.e.boot.SimpleApplication : Started SimpleApplication in 5.678 seconds (process running for 6.012)
示例 2 访问 /custom-key 接口
curl http://localhost:8080/custom-key
# 响应:自定义环境变量值:custom-value-from-listener
测试步骤以及详细代码
测试 1:验证启动事件触发顺序
测试类(StartupListenerTest.java)
package com.example.boot.listener;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("test")
class StartupListenerTest {
@Test
void testListenerTriggered() {
// 测试通过日志观察事件触发顺序(实际开发中可通过日志断言或 Mock 监听器验证)
System.out.println("测试启动事件触发顺序");
}
}
测试 2:验证 CommandLineRunner 执行
测试类(CommandLineRunnerTest.java)
package com.example.boot.runner;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
class CommandLineRunnerTest {
@Autowired
private ApplicationContext context;
@Test
void testRunnerBeansExist() {
// 验证 CommandLineRunner Bean 被 Spring 管理
InitRunner initRunner = context.getBean(InitRunner.class);
AnotherRunner anotherRunner = context.getBean(AnotherRunner.class);
assertTrue(initRunner != null && anotherRunner != null);
}
}
部署场景
1. 本地开发部署
- IDE 直接运行
main方法,或使用mvn spring-boot:run。
2. JAR 包部署(生产环境)
# 打包(跳过测试)
mvn clean package -DskipTests
# 运行(指定生产环境和端口)
java -jar target/boot-startup-demo-0.0.1-SNAPSHOT.jar \
--spring.profiles.active=prod \
--server.port=80
3. Docker 部署
Dockerfile
FROM eclipse-temurin:17-jre-alpine
VOLUME /tmp
COPY target/boot-startup-demo-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=prod"]
构建与运行
docker build -t boot-startup-demo .
docker run -d -p 80:80 --name boot-app boot-startup-demo
疑难解答
问题 1:启动时报 ApplicationContextInitializer 类找不到
- 原因:自定义的
ApplicationContextInitializer未在META-INF/spring/org.springframework.boot.context.initializer.Initializer.imports中注册。 - 解决:在该文件中添加全类名(Spring Boot 2.7+ 推荐格式,替代旧版
spring.factories)。
问题 2:嵌入式 Tomcat 端口被占用
- 现象:启动时报
Port 8080 is already in use。 - 解决:
- 临时修改端口:
--server.port=8081或配置文件server.port=8081; - 杀死占用进程:
lsof -i:8080→kill -9 <PID>。
- 临时修改端口:
问题 3:非 Web 应用启动后仍尝试启动 Tomcat
- 原因:未通过
setWebApplicationType(WebApplicationType.NONE)禁用 Web 环境,或依赖了spring-boot-starter-web但未排除 Tomcat。 - 解决:显式设置 Web 类型,或在依赖中排除 Tomcat:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>
未来展望
技术趋势
- AOT 编译与 GraalVM 原生镜像:Spring Boot 3.x 支持 AOT 编译,将启动时的反射逻辑提前处理,生成原生可执行文件,启动时间从秒级降至毫秒级(如从 2s → 50ms)。
- 虚拟线程(Project Loom):JDK 21+ 虚拟线程可优化 Spring Boot 的并发处理能力,减少线程上下文切换开销,提升高并发场景下的吞吐量。
- 配置外部化与动态刷新:结合 Spring Cloud Config 或 Nacos,实现配置实时推送,无需重启应用。
挑战
- 启动性能与复杂度平衡:AOT 编译虽提升速度,但增加了构建复杂度(需处理反射、资源文件的静态分析)。
- 云原生适配:Serverless 场景要求应用“冷启动”极快,需进一步优化容器初始化逻辑(如按需加载自动配置类)。
- 多运行时支持:Spring Boot 需兼容 Quarkus、Micronaut 等轻量级框架的生态,同时保持自身“约定优于配置”的优势。
总结
Spring Boot 的启动流程是“初始化 SpringApplication → 准备环境 → 创建上下文 → 刷新容器 → 执行 Runner”的闭环,核心通过 SpringApplication.run() 串联起事件驱动、自动配置、嵌入式服务器等关键能力。理解这一流程,不仅能快速定位启动问题,更能为定制化开发(如 Starter 编写、性能优化)提供理论支撑。未来,随着 AOT、虚拟线程等技术的成熟,Spring Boot 的启动效率与应用性能将迎来新一轮突破,但其“简化开发”的核心价值将始终引领 Java 生态。
更多推荐


所有评论(0)