prepareEnvironment是SpringBoot启动过程中负责创建Environment、加载所有配置(本地+Nacos)、严格控制配置优先级、触发监听器读取配置文件的核心阶段。很多人搞不懂:配置从哪来?顺序怎么定?监听器何时触发?配置如何注入到@Configuration和@Component?
注意:prepareEnvironment 只负责加载配置,不负责将配置绑定到@Configuration/@Component类,绑定发生在容器刷新阶段。
本文基于SpringBoot 2.7.18源码 ,逐行拆解prepareEnvironment执行流程,结合配置加载顺序(本地+Nacos)、监听器触发机制、PropertySource优先级保障、配置绑定原理,搭配实战案例(如MyBatis配置绑定),彻底讲透环境准备阶段的底层逻辑。
一、先看整体定位:prepareEnvironment在启动流程中的位置
1. 源码入口(run方法)
java
public ConfigurableApplicationContext run(String... args) {
// 1. 加载运行监听器(SpringApplicationRunListener)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 2. 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 3. 【核心】环境准备(本文重点)
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 4. 创建容器、刷新上下文...
ConfigurableApplicationContext context = createApplicationContext();
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
// ...
} catch (Throwable ex) {
// ...
}
}
✅ 定位 :prepareEnvironment在容器创建前 执行,负责构建完整的配置环境,是连接「启动初始化」和「容器创建」的桥梁。
2. prepareEnvironment方法全貌(逐行源码)
java
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// 1. 创建或获取Environment对象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置基础环境:添加命令行参数、系统属性等
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 绑定配置源到Environment(内部封装PropertySource)
ConfigurationPropertySources.attach(environment);
// 4. 【关键】触发环境准备事件(所有监听器在此执行)
listeners.environmentPrepared(bootstrapContext, environment);
// 5. 调整默认配置优先级(移到最后)
DefaultPropertiesPropertySource.moveToEnd(environment);
// 6. 校验环境前缀(禁止通过配置设置)
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 7. 将Environment配置绑定到SpringApplication(属性映射)
bindToSpringApplication(environment);
// 8. 转换Environment类型(非自定义环境时)
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 9. 再次绑定配置源
ConfigurationPropertySources.attach(environment);
return environment;
}
✅ 核心入参:
listeners:运行监听器(唯一实现EventPublishingRunListener),用于触发事件;bootstrapContext:引导上下文(SpringCloud用,加载Nacos等远程配置);applicationArguments:封装后的命令行参数(--server.port=8080等)。
二、第一步:创建Environment(getOrCreateEnvironment)
1. 源码逻辑
java
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
// 根据应用类型创建对应Environment
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment(); // Web应用(默认)
case REACTIVE:
return new ApplicationReactiveEnvironment(); // 响应式Web
case NONE:
return new StandardEnvironment(); // 非Web应用
}
throw new IllegalStateException("Unknown web application type: " + this.webApplicationType);
}
2. Environment结构(核心:PropertySource集合)
ConfigurableEnvironment内部维护一个有序的PropertySource列表 ,每个PropertySource代表一个配置源(如命令行、application.yml、Nacos配置等):
java
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// 配置源集合(有序,后添加的优先级更高)
MutablePropertySources getPropertySources();
// ...
}
✅ 关键特性 :后添加的PropertySource覆盖先添加的同名属性------这是SpringBoot配置优先级的核心保障。
三、第二步:基础环境配置(configureEnvironment)
1. 源码逻辑
java
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 1. 添加默认配置(SpringApplication.setDefaultProperties设置的)
if (this.defaultProperties != null) {
environment.getPropertySources().addLast(
new MapPropertySource("springApplicationDefaultProperties", this.defaultProperties));
}
// 2. 配置命令行参数(--key=value)
configurePropertySources(environment, args);
// 3. 激活默认Profile(无spring.profiles.active时,默认default)
configureProfiles(environment, args);
}
2. 核心配置源添加(优先级从低到高)
- 默认配置 :
springApplicationDefaultProperties(代码设置,优先级最低); - 命令行参数 :
commandLineArgs(--server.port=8080,优先级高); - 系统属性 :
systemProperties(-Dspring.profiles.active=dev); - 环境变量 :
systemEnvironment(操作系统环境变量)。
✅ 此时状态 :基础配置源已添加,但未加载application.yml、Nacos等核心配置文件 ------这些配置由后续监听器加载。
四、第三步:触发监听器(listeners.environmentPrepared)------配置加载的核心
这是整个环境准备阶段最关键的一步 :所有配置文件(本地+Nacos)都在这一步加载 ,由监听器监听ApplicationEnvironmentPreparedEvent事件并执行。
1. 事件触发源码
java
// SpringApplicationRunListeners.java
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
// EventPublishingRunListener.java(唯一运行监听器)
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
// 发布环境准备事件
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
✅ 事件类型 :ApplicationEnvironmentPreparedEvent(环境准备完成事件)。
2. 监听器执行:哪些监听器响应此事件?
从spring.factories加载的应用监听器中,有2个核心监听器处理配置加载:
- ConfigDataEnvironmentPostProcessor (SpringBoot 2.4+,替代旧版
ConfigFileApplicationListener):加载本地配置文件 (application.yml、application-{profile}.yml等); - EnvironmentPostProcessorApplicationListener :代理所有
EnvironmentPostProcessor(如Nacos的NacosPropertySourcePostProcessor),加载远程配置(Nacos、Apollo等)。
(1)本地配置加载:ConfigDataEnvironmentPostProcessor
本地配置文件内部优先级(从高到低):
1. jar 外部 application-{profile}.properties/yml
2. jar 外部 application.properties/yml
3. jar 内部 application-{profile}.properties/yml
4. jar 内部 application.properties/yml
5. jar 内部 bootstrap-{profile}.properties/yml
6. jar 内部 bootstrap.properties/yml
同文件优先级 :properties > yml > yaml(同名配置,properties覆盖yml)。
带profile优先级 :xxx-{profile}.yml > xxx.yml。
(2)Nacos远程配置加载:NacosPropertySourcePostProcessor
SpringCloud场景下,Nacos配置加载由NacosPropertySourcePostProcessor响应事件执行:
Nacos内部优先级(从高到低):
1. Nacos {application}-{profile}.yml
2. Nacos {application}.yml
3. Nacos shared-configs 共享配置
✅ 最终全局配置优先级(从高到低,后覆盖前):
1. 命令行参数(--key=value)
2. JVM系统属性(-Dkey=value)
3. 操作系统环境变量
4. Nacos 远程配置(profile → 应用名 → 共享)
5. 本地jar外配置(file:./config → file:./)
6. 本地jar内 application-{profile}
7. 本地jar内 application 默认
8. 本地jar内 bootstrap-{profile}
9. 本地jar内 bootstrap 默认
10. SpringApplication 默认配置(最低)
3. 监听器执行方式:同步还是异步?
同步执行 !源码中SimpleApplicationEventMulticaster默认无线程池 ,监听器在主线程串行执行:
java
// SimpleApplicationEventMulticaster.java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 无Executor时,同步执行
if (executor != null) {
executor.execute(() -> this.invokeListener(listener, event));
} else {
this.invokeListener(listener, event); // 主线程直接调用
}
}
✅ 结论 :配置加载是同步串行的,加载顺序严格可控,避免并发导致的配置混乱。
五、第四步:PropertySource优先级保障(moveToEnd+bindToSpringApplication)
1. DefaultProperties移到最后(moveToEnd)
java
DefaultPropertiesPropertySource.moveToEnd(environment);
- 作用:将默认配置 (优先级最低)移到
PropertySources列表末尾,确保所有其他配置覆盖默认配置。
2. 配置绑定到SpringApplication(bindToSpringApplication)
java
bindToSpringApplication(environment);
- 作用:将
Environment中的配置绑定到SpringApplication的属性 (如spring.main.web-application-type、spring.main.banner-mode),实现「配置驱动启动行为」。 - 注意 :这一步只绑定SpringApplication自身 ,不绑定任何业务Bean。
六、第五步:Environment类型转换与最终绑定
1. 转换Environment类型(convertEnvironmentIfNecessary)
java
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
- 作用:统一
Environment实现类(如Web应用统一为ApplicationServletEnvironment),确保后续容器使用时类型一致。
2. 最终绑定配置源(ConfigurationPropertySources.attach)
java
ConfigurationPropertySources.attach(environment);
- 作用:将所有
PropertySource封装为配置属性源 ,供后续@Value、@ConfigurationProperties绑定使用。
七、配置如何保存、获取并绑定到配置类?
1. 配置保存位置
所有配置最终存储在 ConfigurableEnvironment 的 MutablePropertySources 集合 中,每个配置源是一个PropertySource实现类(如MapPropertySource、ResourcePropertySource、NacosPropertySource)。
2. 配置获取方式
(1)代码中获取(Environment接口)
java
@Autowired
private Environment environment;
// 获取单个配置
String port = environment.getProperty("server.port");
// 获取带默认值的配置
String dbUrl = environment.getProperty("spring.datasource.url", "jdbc:mysql://localhost:3306/test");
(2)配置类绑定(@ConfigurationProperties)
以MyBatis配置 为例,将application.yml中的MyBatis配置绑定到配置类:
yaml
# application.yml
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.entity
configuration:
map-underscore-to-camel-case: true
java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "mybatis") // 绑定前缀为mybatis的配置
public class MyBatisProperties {
private String mapperLocations;
private String typeAliasesPackage;
private Configuration configuration;
// getter/setter
public static class Configuration {
private boolean mapUnderscoreToCamelCase;
// getter/setter
}
}
八、@ConfigurationProperties 原理
1. 说明
prepareEnvironment 只加载配置,不绑定Bean!
Bean的配置绑定 = 容器刷新时执行!
2. 绑定时机(执行到哪一步开始绑)
run()
→ prepareEnvironment() 【加载配置】
→ createApplicationContext() 【创建容器】
→ prepareContext()
→ refreshContext()
→ refresh()
→ finishBeanFactoryInitialization()【创建bean】
→ 创建 @ConfigurationProperties Bean
→ 执行绑定 ←------------------ 这里才开始绑!
3. 绑定源码在哪里
核心绑定处理器 :
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
真正绑定方法:
java
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(bean, beanName); // ← 配置绑定入口
return bean;
}
真正绑定逻辑:
java
private void bind(Object bean, String beanName) {
ConfigurationProperties annotation = getAnnotation(bean.getClass());
if (annotation != null) {
Bindable<?> target = Bindable.ofInstance(bean);
this.binder.bind(annotation.prefix(), target);
}
}
4. 怎么绑上去的
- 扫描
@ConfigurationProperties(prefix="xxx") - 通过
Binder从Environment.getPropertySources()取值 - 通过反射调用set方法注入字段
- 完成配置绑定
九、完整流程总结
1. 执行链路
run() → prepareEnvironment()
├─ getOrCreateEnvironment() → 创建ConfigurableEnvironment
├─ configureEnvironment() → 添加命令行、系统属性、环境变量
├─ ConfigurationPropertySources.attach()
├─ listeners.environmentPrepared() → 触发事件
│ └─ 加载本地配置(application > bootstrap)
│ └─ 加载Nacos远程配置
├─ DefaultPropertiesPropertySource.moveToEnd()
├─ bindToSpringApplication()
├─ convertEnvironmentIfNecessary()
└─ ConfigurationPropertySources.attach()
容器刷新后 → 才执行 @ConfigurationProperties 绑定
2. 全局配置优先级
1. 命令行参数
2. JVM系统属性(-D)
3. 操作系统环境变量
4. Nacos远程配置
5. 外部application-{profile}
6. 外部application默认
7. 内部application-{profile}
8. 内部application默认
9. 内部bootstrap-{profile}
10. 内部bootstrap默认
11. 默认配置
同名文件 :.properties > .yml > .yaml
带profile :xxx-{profile} > xxx默认
十、面试高频问题
-
prepareEnvironment阶段做了什么?
答:创建Environment、加载所有配置、触发监听器、控制配置优先级、绑定SpringApplication自身属性。不绑定业务Bean。
-
配置优先级是什么?
答:命令行 > 系统属性 > 环境变量 > Nacos > 本地application > 本地bootstrap。
-
监听器是同步还是异步?
答:主线程同步串行执行。
-
@ConfigurationProperties在哪绑的?
答:容器刷新
finishBeanFactoryInitialization阶段,由ConfigurationPropertiesBindingPostProcessor执行绑定。
十一、总结
prepareEnvironment是SpringBoot启动的配置中枢:
- 只负责加载配置,不负责绑定Bean
- 所有配置存在
Environment.getPropertySources() - 配置优先级严格有序、后加载覆盖先加载
- 本地/远程配置统一由监听器加载
- Bean配置绑定在容器刷新阶段执行