版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl

一、引言
在 Spring Framework 的运行时体系中,环境抽象(Environment Abstraction) 是连接应用程序与外部配置世界的核心桥梁。从命令行参数、系统属性、操作系统环境变量,到配置文件(如 application.properties)、云平台元数据,乃至自定义配置源,Spring 需要一个统一、灵活、可扩展的机制来管理这些异构配置。
为此,Spring 引入了 org.springframework.core.env.Environment 接口作为只读视图,并在其基础上定义了 ConfigurableEnvironment ------ 一个可写、可组合、可定制的环境模型接口。它是 Spring 容器启动过程中配置解析、属性占位符替换、Profile 激活等关键功能的基石。
本文将对 ConfigurableEnvironment 进行全面、深入、严谨的技术剖析。我们将从其接口契约、继承体系、核心实现类(AbstractEnvironment)、PropertySource 管理机制、Profile 控制逻辑、与 Spring Boot 的集成方式,到典型应用场景逐一展开,并辅以关键源码解读,力求揭示 Spring 环境模型的设计哲学与工程实现。
二、接口契约与设计目标
2.1 ConfigurableEnvironment 接口定义
java
package org.springframework.core.env;
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
/**
* 返回当前环境中的所有 PropertySource 对象的可修改列表。
* PropertySource 定义了属性的来源(如 systemProperties, systemEnvironment, application.properties 等)。
*/
MutablePropertySources getPropertySources();
/**
* 返回当前激活的 Profile 集合(可修改)。
* Profile 用于条件化地启用/禁用 Bean 或配置。
*/
Set<String> getActiveProfiles();
/**
* 返回默认 Profile 集合(可修改)。
* 当未显式激活任何 Profile 时,使用默认 Profile。
*/
Set<String> getDefaultProfiles();
/**
* 显式设置激活的 Profiles。
* 调用此方法会覆盖通过其他方式(如 spring.profiles.active)设置的值。
*/
void setActiveProfiles(String... profiles);
/**
* 添加默认 Profiles(不会覆盖已存在的默认值)。
*/
void addDefaultProfiles(String... profiles);
}
2.2 设计目标
| 目标 | 说明 |
|---|---|
| 统一配置访问 | 通过 getProperty(key) 统一访问来自不同源的属性 |
| 可组合性 | 支持动态添加/移除 PropertySource |
| Profile 支持 | 实现基于环境的条件化配置(如 dev/test/prod) |
| 可编程控制 | 允许在容器初始化前/后动态修改环境 |
| 与占位符解析集成 | 作为 PropertyResolver,支持 ${...} 替换 |
核心思想 :
"配置即数据源,环境即上下文" ------ 将配置视为可插拔的数据源集合,环境是这些数据源的运行时上下文。
三、继承体系与核心实现
3.1 接口继承关系
PropertyResolver
└── Environment
└── ConfigurablePropertyResolver
└── ConfigurableEnvironment
└── AbstractEnvironment (抽象基类)
└── StandardEnvironment
└── StandardServletEnvironment
└── MockEnvironment (测试专用)
PropertyResolver:基础属性解析能力(containsProperty,getProperty);Environment:增加acceptsProfiles、getActiveProfiles等只读 Profile 查询;ConfigurablePropertyResolver:增加setPlaceholderPrefix、setRequiredProperties等解析器配置;ConfigurableEnvironment:最终可写接口,暴露MutablePropertySources和 Profile 修改能力。
3.2 核心实现:AbstractEnvironment
这是 ConfigurableEnvironment 的标准抽象基类,实现了绝大部分通用逻辑。
3.2.1 构造函数与初始化
java
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(propertySources);
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
/**
* 子类可重写此方法,向 propertySources 中添加默认 PropertySource。
*/
protected void customizePropertySources(MutablePropertySources propertySources) {}
}
MutablePropertySources:可变的PropertySource列表,支持插入、删除、排序;PropertySourcesPropertyResolver:基于propertySources实现PropertyResolver接口;- 模板方法 :
customizePropertySources()允许子类定义默认配置源。
3.2.2 StandardEnvironment 的默认配置源
java
public class StandardEnvironment extends AbstractEnvironment {
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// 1. 系统属性(System.getProperties())
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
getSystemProperties()));
// 2. 系统环境变量(System.getenv())
propertySources.addLast(new SystemEnvironmentPropertySource(
SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
注意顺序 :
addLast()表示后添加的优先级更高 。但在MutablePropertySources中,列表前端优先级高。因此实际顺序为:
[systemProperties, systemEnvironment] → systemEnvironment 优先级更高?纠正 :实际上
StandardEnvironment使用addLast,但 Spring 默认认为 系统属性 > 环境变量 。
真相 :在StandardEnvironment中,系统属性先加入,环境变量后加入,但搜索时从前向后 ,所以 系统属性优先级更高。
✅ 正确理解 :
MutablePropertySources是一个 Stack-like 结构 :索引 0 优先级最高 。
addLast()将新源加到末尾(低优先级),addFirst()加到开头(高优先级)。
3.2.3 StandardServletEnvironment 扩展
在 Web 应用中,StandardServletEnvironment 添加了 Servlet 相关配置源:
java
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// ServletConfig 初始化参数
propertySources.addLast(new ServletConfigPropertySource(...));
// ServletContext 初始化参数
propertySources.addLast(new ServletContextPropertySource(...));
// JNDI 属性(如果可用)
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(...));
}
// 调用父类(StandardEnvironment)添加 systemProperties/systemEnvironment
super.customizePropertySources(propertySources);
}
优先级顺序(高 → 低):
- ServletConfig init-param
- ServletContext init-param
- JNDI
- JVM 系统属性 (
-D)- 操作系统环境变量
四、核心机制一:PropertySource 与 MutablePropertySources
4.1 PropertySource 抽象
java
public abstract class PropertySource<T> {
protected final String name;
protected final T source;
public boolean containsProperty(String name) { ... }
public abstract Object getProperty(String name);
}
- 典型实现 :
MapPropertySource:包装Map<String, Object>SystemEnvironmentPropertySource:处理环境变量的大小写和下划线转换ResourcePropertySource:从Resource(如application.properties)加载
4.2 MutablePropertySources:可变配置源列表
java
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
public void addFirst(PropertySource<?> propertySource) { ... }
public void addLast(PropertySource<?> propertySource) { ... }
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) { ... }
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) { ... }
public boolean remove(String name) { ... }
}
- 线程安全 :使用
CopyOnWriteArrayList,适合读多写少场景; - 灵活插入:支持按名称定位插入位置,便于精确控制优先级。
应用场景 :
Spring Boot 的
ConfigurationPropertySources会在启动时将ConfigurationPropertySource插入到最前面,确保@ConfigurationProperties优先级最高。
五、核心机制二:Profile 管理
5.1 Profile 数据结构
java
private final Set<String> activeProfiles = new LinkedHashSet<>();
private final Set<String> defaultProfiles = new LinkedHashSet<>(Collections.singleton("default"));
activeProfiles:显式激活的 Profile(通过 API、spring.profiles.active等);defaultProfiles:当activeProfiles为空时使用的默认 Profile(默认为"default")。
5.2 acceptsProfiles 实现
java
@Override
public boolean acceptsProfiles(Profiles profiles) {
// 如果有激活的 Profile,则只检查激活的
if (!this.activeProfiles.isEmpty()) {
return profiles.matches(this.activeProfiles);
}
// 否则检查默认 Profile
return profiles.matches(this.defaultProfiles);
}
Profiles:Spring 5.0+ 引入的函数式接口,支持复杂表达式(如!prod & (dev | test));- 向后兼容 :旧版
acceptsProfiles(String...)委托给新 API。
5.3 Profile 激活流程
- 用户调用
setActiveProfiles("dev")→ 直接设置activeProfiles; - 或通过属性
spring.profiles.active=dev→ 在AbstractEnvironment初始化后被ConfigurationPropertySources解析并设置; - 若未激活任何 Profile,则使用
defaultProfiles。
六、与 Spring 容器的集成
6.1 容器创建时的环境注入
java
// GenericApplicationContext.java
public GenericApplicationContext() {
this.environment = new StandardEnvironment(); // 默认环境
}
// 可通过 setEnvironment(ConfigurableEnvironment) 替换
6.2 属性占位符解析
java
// PlaceholderConfigurerSupport.java
protected void processProperties(...) {
String value = environment.resolvePlaceholders(originalValue);
}
resolvePlaceholders("${db.url}")→ 委托给内部PropertySourcesPropertyResolver;- 解析顺序 :遍历
propertySources,返回第一个包含该 key 的值。
6.3 条件化 Bean 注册
java
@Profile("dev")
@Bean
public DataSource devDataSource() { ... }
- 处理时机 :
ConfigurationClassPostProcessor在解析@Bean方法时调用environment.acceptsProfiles("dev"); - 结果:若不匹配,则跳过该 Bean 的注册。
七、Spring Boot 的增强
Spring Boot 在 ConfigurableEnvironment 基础上做了大量扩展:
-
自动配置 PropertySource:
RandomValuePropertySource(random.int)ConfigurationPropertySources(@ConfigurationProperties)DevToolsPropertyDefaultsPostProcessor(开发时默认值)
-
Profile 激活增强:
- 支持
application-{profile}.properties自动加载; - 支持
spring.profiles.group定义 Profile 组。
- 支持
-
环境后处理器:
EnvironmentPostProcessor允许在容器刷新前修改环境;- 如
ConfigFileApplicationListener加载application.properties。
八、典型使用场景与最佳实践
8.1 动态添加配置源
java
@Autowired
private ConfigurableEnvironment env;
@PostConstruct
public void addCustomPropertySource() {
Map<String, Object> customProps = new HashMap<>();
customProps.put("custom.key", "value");
env.getPropertySources().addFirst(
new MapPropertySource("customSource", customProps)
);
}
8.2 编程式激活 Profile
java
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApp.class);
app.setAdditionalProfiles("prod");
app.run(args);
}
8.3 最佳实践
- 避免直接修改
systemProperties:应通过添加自定义PropertySource实现; - Profile 命名规范 :使用小写、连字符(如
prod-us-east); - 优先使用
@Profile而非硬编码:提升配置可维护性; - 测试时使用
MockEnvironment:便于隔离外部依赖。