环境准备阶段
概述
SpringApplication.run() 中,环境准备是启动流程的关键中间阶段——它在 BootstrapContext 创建和 RunListener 启动之后、ApplicationContext 创建之前执行。环境准备的质量直接影响后续所有 Bean 的创建和配置绑定。
Spring Boot 将环境准备拆解为 7+ 个子步骤,每个步骤都有精细的控制逻辑。本文逐步骤深入源码。
本文基于 Spring Boot 3.x 源码分析。
1. 整体流程
// SpringApplication.java
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
// 1. 创建 Environment 实例
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置 Environment(profile + conversion service + property sources)
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 发布 ApplicationEnvironmentPreparedEvent
listeners.environmentPrepared(bootstrapContext, environment);
// 4. 将 spring.main.* 绑定回 SpringApplication
bindToSpringApplication(environment);
// 5. 非 Web 环境下转换 Environment 类型
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 6. 追加 ConfigurationPropertySources 适配层
ConfigurationPropertySources.attach(environment);
return environment;
}2. getOrCreateEnvironment() 创建 ApplicationEnvironment
2.1 三种 Environment 的选择
// SpringApplication.java
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
// 根据 WebApplicationType 选择对应的 Environment 实现
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationEnvironment();
// 注意:Spring Boot 3.x 使用 ApplicationEnvironment
// 而非之前的 StandardServletEnvironment
case REACTIVE:
return new ApplicationReactiveEnvironment();
default:
return new ApplicationEnvironment();
}
}2.2 类层次结构
Environment(接口)
└── ConfigurableEnvironment(接口)
├── StandardEnvironment(通用)
│ └── ApplicationEnvironment(Spring Boot 3.x 新增,SERVLET 模式)
└── StandardReactiveWebEnvironment
└── ApplicationReactiveEnvironment(REACTIVE 模式)2.3 ApplicationEnvironment 与 StandardServletEnvironment 的差异
| 特性 | StandardServletEnvironment(2.x) | ApplicationEnvironment(3.x) |
|---|---|---|
| 所属框架 | Spring 内置 | Spring Boot 3.x 新增 |
| PropertySource 预置 | servletConfigInitParams / servletContextInitParams / jndiProperties | 无 Servlet 相关源(延迟到 WebServer 初始化后追加) |
| 兼容性 | 强依赖 Servlet API | 不需要 Servlet API 即可创建(AOT 友好) |
| 适用场景 | 传统 WAR 部署 | 嵌入式 WebServer + AOT 编译 |
2.4 为什么 Spring Boot 3.x 改用 ApplicationEnvironment
// ApplicationEnvironment.java(简化示意)
public class ApplicationEnvironment extends StandardEnvironment {
// 不再重写 customizePropertySources() 添加 servlet 相关源
// Servlet 相关的 PropertySource 在 WebServer 启动后按需添加
}主要动机:
- AOT 编译兼容:在 GraalVM AOT 编译阶段,Servlet API 可能不在 classpath 上
- 解耦 Servlet 依赖:
StandardServletEnvironment的构造需要javax.servlet/jakarta.servlet存在,违反了最小依赖原则
3. configureEnvironment() 处理 3 件事
// SpringApplication.java
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
// 1. 配置 PropertySources(追加命令行参数)
configurePropertySources(environment, args);
// 2. 配置 Profiles(激活/默认)
configureProfiles(environment, args);
// 3. 添加 ConversionService(类型转换服务)
if (this.conversionService != null) {
environment.setConversionService(this.conversionService);
}
}3.1 三件事的执行顺序
configureEnvironment()
│
├─ 1. configurePropertySources() ── 先追加命令行参数
│ (命令行应覆盖 application.yml)
├─ 2. configureProfiles() ── 再激活 profile
│ (此时 property sources 已就绪)
└─ 3. setConversionService() ── 最后设置类型转换
(由 SpringApplication 外部配置)顺序的理由:第 2 步 configureProfiles() 可能读取属性中的 spring.profiles.active,因此需要第 1 步的 PropertySources 先就绪。
4. configurePropertySources() 追加 CommandLinePropertySource
4.1 源码
// SpringApplication.java
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
// 如果有 defaultProperties(通过 SpringApplication.setDefaultProperties 设置)
if (this.defaultPropertiesPropertySource != null) {
// 将 DefaultPropertiesPropertySource 追加到末端(优先级最低)
sources.addLast(this.defaultPropertiesPropertySource);
}
// 如果有命令行参数
if (args.length > 0) {
CommandLinePropertySource<?> source;
if (this.addCommandLineProperties && args.length > 0) {
// 使用 SimpleCommandLinePropertySource
source = new SimpleCommandLinePropertySource(args);
} else {
// 使用 DefaultPropertiesPropertySource(不解析 -- 前缀)
source = null;
}
if (source != null) {
// 将命令行参数添加到 PropertySource 链**开头**(最高优先级,除特殊源外)
sources.addFirst(source);
}
}
}4.2 SimpleCommandLinePropertySource vs DefaultPropertiesPropertySource
| 特性 | SimpleCommandLinePropertySource | DefaultPropertiesPropertySource |
|---|---|---|
解析 --key=value | ✅ 自动解析为 key=value | ❌ 作为字面值 |
| 位置 | addFirst() — 高优先级 | addLast() — 最低优先级 |
| 典型来源 | main(String[] args) | SpringApplication.setDefaultProperties() |
支持 no- 前缀(--server.port=-1) | ✅ | ❌ |
4.3 --key=value 解析细节
// SimpleCommandLinePropertySource 解析逻辑
// 输入: args = ["--server.port=8080", "--debug", "--spring.profiles.active=dev"]
// 解析后内部存储:
// { "server.port": "8080", "debug": "", "spring.profiles.active": "dev" }
// 注意: --debug 不带值,值为空字符串5. configureProfiles() 激活 profile 的细节
5.1 三种 profile 配置方式
// SpringApplication.java
protected void configureProfiles(ConfigurableEnvironment environment,
String[] args) {
// 从环境属性中读取 spring.profiles.active
String[] activeProfiles = environment.getProperty("spring.profiles.active", String[].class);
if (activeProfiles != null) {
environment.setActiveProfiles(activeProfiles);
}
// 从环境属性中读取 spring.profiles.include
String[] includeProfiles = environment.getProperty("spring.profiles.include", String[].class);
if (includeProfiles != null) {
environment.addActiveProfile(StringUtils.trimArrayElements(includeProfiles));
}
// 从环境属性中读取 spring.profiles.default
String[] defaultProfiles = environment.getProperty("spring.profiles.default", String[].class);
if (defaultProfiles != null) {
environment.setDefaultProfiles(defaultProfiles);
}
}5.2 三者的优先级
| 配置项 | 优先级 | 说明 |
|---|---|---|
spring.profiles.active | 最高 | 显式激活,覆盖任何其他方式 |
spring.profiles.include | 中间 | 无条件追加(即使 profile 已激活也追加) |
spring.profiles.default | 最低 | 仅在 active 未设置且 default 已设置时生效 |
优先级规则(AbstractEnvironment 源码):
// AbstractEnvironment.java
@Override
public void setActiveProfiles(String... profiles) {
Assert.notNull(profiles, "Profiles must not be null");
// 1. 清除所有已激活的 profile
this.activeProfiles.clear();
// 2. 按传入顺序设置新的 active profiles
for (String profile : profiles) {
validateProfile(profile);
this.activeProfiles.add(profile);
}
}
@Override
public void addActiveProfile(String profile) {
validateProfile(profile);
// 如果该 profile 已被设置为 active,则跳过
if (!this.activeProfiles.contains(profile)) {
this.activeProfiles.add(profile);
}
}5.3 激活流程示例
假设 application.yml:
spring:
profiles:
active: dev
include: common
profiles:
default: local
执行结果:
激活 profiles = ["dev", "common"]
default = "local" 不会生效(因为 active 已有值)6. environmentPrepared() 事件发布
6.1 事件发布链
// SpringApplicationRunListeners.java
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
// 遍历所有 SpringApplicationRunListener,调用其 environmentPrepared()
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(bootstrapContext, environment);
}
}EventPublishingRunListener.environmentPrepared() 内部:
// EventPublishingRunListener.java
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
// 发布 ApplicationEnvironmentPreparedEvent
this.multicaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext,
this.application, this.args, environment));
}6.2 事件处理链
ApplicationEnvironmentPreparedEvent
│
├── EnvironmentPostProcessorApplicationListener
│ └── 遍历所有 EnvironmentPostProcessor
│ ├─ ConfigDataEnvironmentPostProcessor(加载 application.yml)
│ ├─ RandomValuePropertySourceEnvironmentPostProcessor(追加 random.*)
│ ├─ SpringApplicationJsonEnvironmentPostProcessor(解析 SPRING_APPLICATION_JSON)
│ └─ 用户自定义 EnvironmentPostProcessor
│
└── 其他 ApplicationListener(如 LoggingApplicationListener)
└── 读取环境配置中的 logging.* 属性7. EnvironmentPostProcessor 的 SPI 加载
7.1 加载源码
EnvironmentPostProcessorApplicationListener 监听 ApplicationEnvironmentPreparedEvent,在事件处理中加载所有 EnvironmentPostProcessor:
// EnvironmentPostProcessorApplicationListener.java
public class EnvironmentPostProcessorApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// 从 spring.factories 加载所有 EnvironmentPostProcessor
List<EnvironmentPostProcessor> processors =
SpringFactoriesLoader.loadFactories(
EnvironmentPostProcessor.class,
ClassUtils.getDefaultClassLoader());
// 排序
AnnotationAwareOrderComparator.sort(processors);
// 逐个调用 postProcessEnvironment()
for (EnvironmentPostProcessor processor : processors) {
processor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
}7.2 内置 EnvironmentPostProcessor 列表
| EnvironmentPostProcessor | 排序值 | 功能 |
|---|---|---|
ConfigDataEnvironmentPostProcessor | Ordered.LOWEST_PRECEDENCE | 加载 application.yml / application.properties |
RandomValuePropertySourceEnvironmentPostProcessor | Ordered.LOWEST_PRECEDENCE - 10 | 追加 random.* 属性源 |
SpringApplicationJsonEnvironmentPostProcessor | Ordered.LOWEST_PRECEDENCE - 20 | 解析 SPRING_APPLICATION_JSON 环境变量 |
CloudFoundryEnvironmentPostProcessor | Ordered.HIGHEST_PRECEDENCE | Cloud Foundry 平台配置覆盖 |
SpringDevToolsPropertySourcePostProcessor | - | DevTools 默认属性 |
7.3 自定义 EnvironmentPostProcessor
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 在环境准备阶段注入自定义属性
Map<String, Object> custom = new HashMap<>();
custom.put("my.custom.property", "value");
environment.getPropertySources()
.addLast(new MapPropertySource("my-custom", custom));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 5;
}
}注册方式:
# META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.MyEnvironmentPostProcessor8. RandomValuePropertySource 的追加
8.1 实现原理
RandomValuePropertySourceEnvironmentPostProcessor 负责将 RandomValuePropertySource 追加到 PropertySource 链中:
// RandomValuePropertySourceEnvironmentPostProcessor.java
public class RandomValuePropertySourceEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered {
public static final String RANDOM_PROPERTY_SOURCE_NAME = "random";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// 将 RandomValuePropertySource 添加到 PropertySource 链中
if (!environment.getPropertySources().contains(RANDOM_PROPERTY_SOURCE_NAME)) {
environment.getPropertySources().addAfter(
"systemProperties",
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
}8.2 RandomValuePropertySource 支持的属性格式
| 表达式 | 示例值 | 说明 |
|---|---|---|
random.int | -731482902 | 随机 int(整个 int 范围) |
random.int(100) | 57 | 0(含)~ 100(不含)随机 int |
random.int(50, 100) | 72 | 50(含)~ 100(不含)随机 int |
random.long | 536871234567L | 随机 long |
random.uuid | f47ac10b-58cc-4372-a567-0e02b2c3d479 | 随机 UUID(java.util.UUID.randomUUID()) |
8.3 源码实现
// RandomValuePropertySource.java
public class RandomValuePropertySource extends PropertySource<Random> {
@Override
public Object getProperty(String name) {
if (!name.startsWith("random.")) {
return null;
}
if (name.equals("random.int")) {
return getSource().nextInt();
}
if (name.startsWith("random.int(")) {
return getRandomInt(name);
}
if (name.equals("random.long")) {
return getSource().nextLong();
}
if (name.equals("random.uuid")) {
return UUID.randomUUID().toString();
}
return null;
}
private int getRandomInt(String name) {
// 解析 random.int(100) 或 random.int(50,100)
String range = name.substring("random.int(".length(), name.length() - 1);
String[] parts = range.split(",", 2);
if (parts.length == 1) {
return getSource().nextInt(Integer.parseInt(parts[0]));
}
int min = Integer.parseInt(parts[0]);
int max = Integer.parseInt(parts[1]);
return min + getSource().nextInt(max - min);
}
}8.4 在 PropertySource 链中的位置
PropertySource 链(添加 Random 后):
1. commandLineArgs ← 最高优先级
2. ...
3. systemProperties
4. random ← 在 systemProperties 之后、application.yml 之前
5. application.yml
6. ...9. bindToSpringApplication() 绑定
9.1 作用
将 spring.main.* 配置绑定回 SpringApplication 自身的字段,使得 application.yml 中的配置可以覆盖 SpringApplication 的默认行为。
9.2 源码
// SpringApplication.java
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
// 使用 Spring Boot 的 Binder API 将 spring.main.* 绑定到当前对象
Binder.get(environment)
.bind("spring.main", Bindable.ofInstance(this));
} catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}9.3 可绑定的 spring.main.* 属性
| 属性 | 绑定目标字段 | 默认值 | 说明 |
|---|---|---|---|
spring.main.web-application-type | webApplicationType | 自动推断 | 覆盖 Web 应用类型 |
spring.main.banner-mode | bannerMode | CONSOLE | Banner 输出模式 |
spring.main.log-startup-info | logStartupInfo | true | 是否日志启动信息 |
spring.main.add-command-line-properties | addCommandLineProperties | true | 是否添加命令行参数 |
spring.main.headless | headless | true | Headless 模式 |
spring.main.register-shutdown-hook | registerShutdownHook | true | 是否注册 JVM 关闭钩子 |
spring.main.sources | sources | - | 额外的配置源 |
spring.main.lazy-initialization | lazyInitialization | false | 是否启用懒初始化 |
9.4 示例
# application.yml
spring:
main:
web-application-type: none # 关闭 Web 容器
banner-mode: off # 关闭 Banner
lazy-initialization: true # 开启懒初始化
log-startup-info: false # 关闭启动日志等效代码:
SpringApplication app = new SpringApplication(MyApp.class);
app.setWebApplicationType(WebApplicationType.NONE);
app.setBannerMode(Banner.Mode.OFF);
app.setLazyInitialization(true);
app.setLogStartupInfo(false);9.5 Binder 绑定的内部流程
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this))
1. Binder 从 Environment 的 PropertySources 中查找以 spring.main 为前缀的属性
2. 通过 JavaBean 属性名匹配规则找到对应的 setter 方法
3. 使用 ConversionService 进行类型转换(如 "none" → WebApplicationType.NONE)
4. 调用 setter 方法完成绑定10. 后续步骤
环境准备完成后,prepareEnvironment() 还会执行两个收尾步骤:
10.1 Environment 类型转换
// 非自定义 Environment 时,根据 deduceEnvironmentClass() 转换
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}当 webApplicationType = NONE 时,将 ApplicationEnvironment 转换为 StandardEnvironment(去掉不必要的 Servlet PropertySources)。
10.2 ConfigurationPropertySources.attach()
// 追加 ConfigurationPropertySources 适配层
ConfigurationPropertySources.attach(environment);将原始的 PropertySource 包装为 ConfigurationPropertySource,使后续的 Binder 和 @ConfigurationProperties 绑定能够正常工作。
总结
| 细节点 | 核心要点 |
|---|---|
① getOrCreateEnvironment() 三种选择 | SERVLET → ApplicationEnvironment、REACTIVE → ApplicationReactiveEnvironment、NONE → ApplicationEnvironment |
② configureEnvironment() 三件事 | configurePropertySources() → configureProfiles() → setConversionService() |
③ CommandLinePropertySource 差异 | SimpleCommandLinePropertySource 解析 --key=value 高优先级;DefaultPropertiesPropertySource 字面值低优先级 |
| ④ Profile 激活优先级 | spring.profiles.active(最高) → spring.profiles.include(追加) → spring.profiles.default(兜底) |
⑤ environmentPrepared() 事件 | EventPublishingRunListener → ApplicationEnvironmentPreparedEvent → EnvironmentPostProcessorApplicationListener |
⑥ EnvironmentPostProcessor SPI | spring.factories 加载,AnnotationAwareOrderComparator 排序 |
⑦ RandomValuePropertySource | 支持 random.int / random.int(N) / random.int(M,N) / random.long / random.uuid |
⑧ bindToSpringApplication() | Binder.get(env).bind("spring.main", Bindable.ofInstance(this)) 绑定 8 种配置 |