Environment 与 PropertySource - 属性源链、占位符解析与多环境配置
一、概述
Spring Framework 的 Environment 接口是容器中配置管理的核心抽象,它将属性源(PropertySource) 与 Profile 机制 统一起来,为应用程序提供运行时环境配置的统一访问入口。PropertySource 是属性存储的抽象,而 PropertySourcesPlaceholderConfigurer 则负责将占位符 ${…} 解析为实际属性值。
本文基于 Spring Framework 5.3.x 源码,深入分析 Environment 接口体系、PropertySource 链设计、占位符解析原理、@PropertySource 注解处理流程、Profile 机制,以及多环境配置的最佳实践。
二、Environment 接口体系
2.1 接口层次结构
Spring 的 Environment 类型体系分为三个主要层级:
Environment // 最上层接口
└── ConfigurableEnvironment // 可配置扩展
└── ConfigurableWebEnvironment // Web 环境扩展
└── StandardEnvironment // 非 Web 环境默认实现
└── StandardServletEnvironment // Servlet 环境实现
└── StandardReactiveWebEnvironment // Reactive 环境实现2.2 Environment 接口
org.springframework.core.env.Environment 是属性解析和 Profile 判断的总入口:
// 摘自 Spring 5.3.x: org.springframework.core.env.Environment
public interface Environment extends PropertyResolver {
// --- Profile 相关 ---
String[] getActiveProfiles();
String[] getDefaultProfiles();
boolean acceptsProfiles(String... profiles);
boolean acceptsProfiles(Profiles profiles);
}Environment 继承自 PropertyResolver,后者定义了属性解析的基础方法:
// 摘自 Spring 5.3.x: org.springframework.core.env.PropertyResolver
public interface PropertyResolver {
boolean containsProperty(String key);
String getProperty(String key);
String getProperty(String key, String defaultValue);
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
String resolvePlaceholders(String text); // 解析 ${...} 占位符
String resolveRequiredPlaceholders(String text);
}核心职责:
- 属性查找:遍历所有已注册的 PropertySource,按顺序查找键值
- 类型转换:利用 Spring 的类型转换服务(TypeConverter)将字符串转换为目标类型
- 占位符解析:递归解析字符串中的
${…}占位符
2.3 ConfigurableEnvironment 接口
ConfigurableEnvironment 提供了对 PropertySource 链和 Profile 的写操作能力,通常由容器在启动阶段使用:
// 摘自 Spring 5.3.x: org.springframework.core.env.ConfigurableEnvironment
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// PropertySource 操作
MutablePropertySources getPropertySources();
void merge(ConfigurableEnvironment parent);
// Profile 操作
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
// ConversionService
void setConversionService(ConfigurableConversionService conversionService);
}关键点:
MutablePropertySources是PropertySource的有序列表,支持addFirst、addLast、addBefore、addAfter、replace等操作merge(ConfigurableEnvironment parent)用于父子容器的属性源合并
2.4 ConfigurableWebEnvironment
ConfigurableWebEnvironment 是 Web 场景下的扩展接口,允许在容器刷新前将 Servlet 上下文参数和 Servlet 配置属性注入 PropertySource 链:
// 摘自 Spring 5.3.x: org.springframework.web.context.ConfigurableWebEnvironment
public interface ConfigurableWebEnvironment extends ConfigurableEnvironment {
void initPropertySources(ServletContext servletContext, ServletConfig servletConfig);
}2.5 默认实现
StandardEnvironment 是非 Web 应用的默认实现,其 PropertySource 链包含:
- systemProperties —
System.getProperties(),JVM 系统属性(-Dkey=value) - systemEnvironment —
System.getenv(),操作系统环境变量
StandardServletEnvironment 是 Servlet 容器的默认实现,在 StandardEnvironment 基础上增加:
- servletConfigInitParams —
ServletConfig初始化参数(作用域最小) - servletContextInitParams —
ServletContext初始化参数(web.xml中的<context-param>) - jndiProperties — JNDI 属性
- systemProperties — JVM 系统属性
- systemEnvironment — 操作系统环境变量
搜索顺序:排在越前面的 PropertySource 优先级越高。
三、PropertySource 链的设计
3.1 PropertySource 抽象
org.springframework.core.env.PropertySource 是一个名值对源的抽象基类:
// 摘自 Spring 5.3.x: org.springframework.core.env.PropertySource
public abstract class PropertySource<T> {
protected final String name; // 属性源名称,必须唯一
protected final T source; // 底层数据源,如 Properties、Map 等
public PropertySource(String name, T source) {
this.name = name;
this.source = source;
}
// 抽象方法:根据名称获取属性值
public abstract Object getProperty(String name);
// 是否包含指定属性
public boolean containsProperty(String name) {
return getProperty(name) != null;
}
// --- 两个常用子类 ---
public static class MapPropertySource extends PropertySource<Map<String, Object>> { ... }
public static class PropertiesPropertySource extends PropertySource<Properties> { ... }
}重要子类:
| 子类 | 底层数据源 | 典型用途 |
|---|---|---|
MapPropertySource | Map<String, Object> | 内存中的键值对 |
PropertiesPropertySource | java.util.Properties | .properties 文件加载 |
ResourcePropertySource | 封装资源文件 | 从 classpath 资源加载 |
CommandLinePropertySource | 命令行参数 | --key=value 格式 |
SimpleCommandLinePropertySource | 命令行参数 | 解析应用启动参数 |
JndiPropertySource | JNDI 上下文 | java:comp/env/ 查找 |
ServletConfigPropertySource | ServletConfig | Servlet 初始化参数 |
ServletContextPropertySource | ServletContext | 上下文初始化参数 |
MockPropertySource | 测试用 Map | 单元测试 |
3.2 MutablePropertySources — 属性源链的核心容器
MutablePropertySources 实现了 PropertySources 接口,内部维护一个 LinkedList<PropertySource> 来存储属性源的搜索顺序:
// 摘自 Spring 5.3.x: 核心数据结构
public class MutablePropertySources implements PropertySources {
// 使用 LinkedList 维护顺序:第一个被搜索(优先级最高)
private final List<PropertySource<?>> propertySourceList =
new CopyOnWriteArrayList<>(); // 5.3 后改为线程安全实现
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 void replace(String name, PropertySource<?> propertySource) { ... }
public void remove(String name) { ... }
public boolean contains(String name) { ... }
// 按顺序迭代所有 PropertySource
@Override
public Iterator<PropertySource<?>> iterator() {
return this.propertySourceList.iterator();
}
}属性查找过程(PropertySourcesPropertyResolver 中的实现):
// 摘自 Spring 5.3.x: org.springframework.core.env.PropertySourcesPropertyResolver
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
@Nullable
@Override
protected Object getPropertyAsRawObject(String key) {
// 按顺序遍历所有 PropertySource,找到即返回
for (PropertySource<?> propertySource : this.propertySources) {
Object value = propertySource.getProperty(key);
if (value != null) {
return value;
}
}
return null; // 所有源都找不到则返回 null
}
}搜索策略:遍历 propertySourceList 中的每个 PropertySource,一旦找到非 null 值立即返回。这意味着排第一的 PropertySource 优先级最高。
3.3 PropertySource 链的典型构成
以 StandardServletEnvironment 为例,启动后的链结构(按搜索优先级):
┌──────────────────────────────────────────┐
│ servletConfigInitParams (优先级最高) │
├──────────────────────────────────────────┤
│ servletContextInitParams │
├──────────────────────────────────────────┤
│ jndiProperties │
├──────────────────────────────────────────┤
│ systemProperties (-D 参数) │
├──────────────────────────────────────────┤
│ systemEnvironment (环境变量) │
└──────────────────────────────────────────┘当调用 environment.getProperty("server.port") 时,查找顺序为:
ServletConfig初始化参数 → 2.ServletContext初始化参数 → 3. JNDI 属性 → 4. JVM 系统属性 → 5. 环境变量
四、@PropertySource 注解的加载流程
4.1 @PropertySource 注解定义
// 摘自 Spring 5.3.x: org.springframework.context.annotation.PropertySource
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(PropertySources.class)
@Documented
public @interface PropertySource {
String name() default ""; // 属性源名称(默认为资源文件名)
String[] value(); // 资源位置,如 "classpath:config/app.properties"
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}4.2 PropertySourceProcessor 处理流程
@PropertySource 注解的处理由 PropertySourceProcessor 完成,它是 BeanDefinitionRegistryPostProcessor 的实现,在容器启动的早期阶段执行。
处理时序(位于 AbstractApplicationContext.refresh() 的 prepareEnvironment 之后、Bean 实例化之前):
AbstractApplicationContext.refresh()
│
├── prepareEnvironment() // 创建并准备 Environment
│
├── obtainFreshBeanFactory() // 创建 BeanFactory
│
├── invokeBeanFactoryPostProcessors() // ← PropertySourceProcessor 在此执行
│ │
│ └── PropertySourceProcessor.postProcessBeanDefinitionRegistry()
│ │
│ └── processPropertySources()
│ │
│ ├── 解析 @PropertySource 注解
│ ├── 创建 ResourcePropertySource
│ └── 添加到 MutablePropertySources(addFirst)
│
└── finishBeanFactoryInitialization() // 实例化单例 Bean核心源码分析:
// 摘自 Spring 5.3.x: org.springframework.context.annotation.PropertySourceProcessor
class PropertySourceProcessor implements BeanDefinitionRegistryPostProcessor {
private final Environment environment;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (String beanName : registry.getBeanDefinitionNames()) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
String beanClassName = beanDef.getBeanClassName();
if (beanClassName != null) {
// 加载配置类的 @PropertySource 注解
processPropertySources(beanClassName);
}
}
}
private void processPropertySources(String className) {
try {
Class<?> clazz = ClassUtils.forName(className, ...);
// 直接查找 @PropertySource 注解
PropertySource[] annotations =
clazz.getAnnotationsByType(PropertySource.class);
for (PropertySource ps : annotations) {
// 1. 解析 ${...} 占位符(支持 SpEL)
String location = this.environment.resolvePlaceholders(ps.value());
// 2. 创建 PropertySourceFactory
PropertySourceFactory factory = ps.factory().newInstance();
// 3. 加载资源文件,创建 PropertySource
Resource resource = new DefaultResourceLoader().getResource(location);
PropertySource<?> propertySource = factory.createPropertySource(ps.name(), resource);
// 4. 添加到 Environment 的 PropertySource 链(优先级最高)
((ConfigurableEnvironment) this.environment)
.getPropertySources().addFirst(propertySource);
}
} catch (Exception ex) {
if (!ps.ignoreResourceNotFound()) {
throw new IllegalStateException("Failed to load @PropertySource from " + ps.value(), ex);
}
}
}
}关键执行流程:
PropertySourceProcessor在invokeBeanFactoryPostProcessors()阶段被触发- 遍历所有已注册的 BeanDefinition,查找配置类上的
@PropertySource注解 - 对注解中的
value进行占位符解析(如${config.path:default}) - 通过
ResourceLoader加载资源文件 - 使用
PropertySourceFactory创建PropertySource实例 - 调用
addFirst将该属性源添加到链的最前面,确保最高优先级
4.3 属性源的优先级管理——addFirst 的深远影响
由于 PropertySourceProcessor 使用 addFirst 添加属性源,这意味着通过 @PropertySource 导入的属性文件优先级高于 JVM 系统属性和环境变量。
但这也意味着后处理的 @PropertySource(如被 @Import 间接导入的配置类)会覆盖先处理的同名属性值。如果需要精细控制属性源顺序,可以通过 @PropertySources 组合注解或编程方式调整:
// 编程方式管理属性源优先级
@Configuration
public class AppConfig {
@Autowired
private Environment environment;
@PostConstruct
public void init() {
MutablePropertySources sources =
((ConfigurableEnvironment) environment).getPropertySources();
// 添加到指定位置:在 systemProperties 之前
sources.addBefore("systemProperties",
new ResourcePropertySource("my-config", "classpath:my.properties"));
// 替换已有的
sources.replace("servletContextInitParams",
new MockPropertySource("servletContextInitParams"));
}
}五、占位符解析 PropertySourcesPlaceholderConfigurer
5.1 从 PropertyPlaceholderConfigurer 到 PropertySourcesPlaceholderConfigurer
在 Spring 3.1 之前,占位符解析由 PropertyPlaceholderConfigurer 实现。Spring 3.1 引入了 Environment 抽象后,推荐使用 PropertySourcesPlaceholderConfigurer,它直接基于 Environment 的 PropertySource 链进行解析。
<!-- 传统 XML 配置 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 等价于注册 Bean -->
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}重要区别:
| 特性 | PropertyPlaceholderConfigurer | PropertySourcesPlaceholderConfigurer |
|---|---|---|
| 引入版本 | 2.x (Spring 3.1 前) | Spring 3.1+ |
| 属性源 | 独立管理 locations | 直接使用 Environment 的 PropertySource 链 |
| 与 Environment 集成 | 不集成 | 深度集成 |
| @Value 支持 | 不支持 | 完全支持 |
| 推荐度 | 不推荐(遗留) | 推荐 |
5.2 生命周期与处理流程
PropertySourcesPlaceholderConfigurer 实现了 BeanFactoryPostProcessor 接口,其核心处理位于 postProcessBeanFactory() 方法中:
// 摘自 Spring 5.3.x: org.springframework.context.support.PropertySourcesPlaceholderConfigurer
public class PropertySourcesPlaceholderConfigurer
extends PlaceholderConfigurerSupport
implements EnvironmentAware, PriorityOrdered {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// 1. 将 @PropertySource 加载的属性源和环境属性源合并
PropertySources mergedPropertySources = getMergedPropertySources(beanFactory);
// 2. 创建 StringValueResolver 用于占位符解析
StringValueResolver valueResolver =
new PropertySourcesPropertyResolver(mergedPropertySources)::resolvePlaceholders;
// 3. 将解析器注册到 BeanFactory(供 BeanPostProcessor 使用)
beanFactory.addEmbeddedValueResolver(valueResolver);
// 4. 创建并注册 PropertySourcesPlaceholderResolver
PropertySourcesPropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(mergedPropertySources);
// 5. 遍历所有 BeanDefinition,解析其中的占位符
visitBeanDefinitions(beanFactory, propertyResolver);
}
}详细执行流程:
postProcessBeanFactory()
│
├── 1. getMergedPropertySources()
│ ├── 收集 @PropertySource 加载的属性源
│ ├── 收集 Environment 中已有的属性源(systemProperties、systemEnvironment 等)
│ └── 如果设置了 locations,也加载合并
│
├── 2. 创建 StringValueResolver
│ └── 基于合并后的 PropertySources 链
│
├── 3. 注册到 BeanFactory
│ └── beanFactory.addEmbeddedValueResolver(resolver)
│
├── 4. visitBeanDefinitions()
│ ├── 遍历所有 BeanDefinition
│ ├── 解析 BeanDefinition 中的 ${...} 占位符
│ │ ├── beanClass 名称
│ │ ├── constructor-arg 值
│ │ ├── property 值
│ │ └── @Value 注解值(通过 AutowiredAnnotationBeanPostProcessor)
│ └── 替换为解析后的实际值
│
└── 5. 完成,Bean 随后进入实例化阶段5.3 StringValueResolver 解析链
Spring 的占位符解析体系由多层 StringValueResolver 组合而成:
// 摘自 Spring 5.3.x: PropertySourcesPropertyResolver.resolvePlaceholders 实现
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
@Override
public String resolvePlaceholders(String text) {
// 使用递归解析,支持嵌套占位符
return parseStringValue(text, placeholderPrefix, placeholderSuffix, new HashSet<>());
}
protected String parseStringValue(
String value, String prefix, String suffix, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
int startIndex = value.indexOf(prefix);
while (startIndex != -1) {
int endIndex = value.indexOf(suffix, startIndex + prefix.length());
if (endIndex != -1) {
String placeholder = value.substring(startIndex + prefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "'");
}
// 递归:先解析占位符内部(支持嵌套 ${${key}})
placeholder = parseStringValue(placeholder, prefix, suffix, visitedPlaceholders);
// 从 PropertySource 链中查找属性值
String propVal = resolvePlaceholder(placeholder);
if (propVal == null) {
// 尝试默认值语法 ${key:defaultValue}
int separatorIndex = placeholder.indexOf(':');
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + 1);
propVal = resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 递归:解析值中可能包含的占位符
propVal = parseStringValue(propVal, prefix, suffix, visitedPlaceholders);
result.replace(startIndex, endIndex + suffix.length(), propVal);
}
// else: 保留未解析的占位符(取决于 setIgnoreUnresolvablePlaceholders)
visitedPlaceholders.remove(originalPlaceholder);
startIndex = result.indexOf(prefix, startIndex + propVal.length());
} else {
break;
}
}
return result.toString();
}
}解析能力总结:
| 语法 | 示例 | 说明 |
|---|---|---|
key | ${db.url} | 基本占位符 |
key:default | ${db.port:3306} | 带默认值 |
| 嵌套 | ${${env}.db.url} | 先解析内部占位符 |
| 多重 | ${host}:${port} | 一次解析多个占位符 |
| 递归值 | 值中包含占位符 | 递归解析直到不可再分 |
5.4 BeanFactory 中的嵌入式值解析
ConfigurableBeanFactory 维护了一个 StringValueResolver 列表:
// 摘自 Spring 5.3.x: org.springframework.beans.factory.config.ConfigurableBeanFactory
public interface ConfigurableBeanFactory {
// 添加嵌入式值解析器
void addEmbeddedValueResolver(StringValueResolver valueResolver);
// 获取已注册的解析器
StringValueResolver getEmbeddedValueResolver();
// 解析字符串中的占位符(使用已注册的解析器链)
String resolveEmbeddedValue(String value);
}resolveEmbeddedValue 的实现会将解析任务依次交给所有已注册的 StringValueResolver:
// AbstractBeanFactory 中的实现
@Override
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
if (result == null) {
return null;
}
}
return result;
}六、EnvironmentAware 与 @Value(${…}) 的底层原理
6.1 EnvironmentAware 回调机制
EnvironmentAware 是 Spring 提供的一个 Aware 回调接口,用于让 Bean 获得 Environment 的引用:
// 摘自 Spring 5.3.x: org.springframework.context.EnvironmentAware
public interface EnvironmentAware extends Aware {
void setEnvironment(Environment environment);
}回调过程由 ApplicationContextAwareProcessor 在 Bean 初始化阶段执行:
// 摘自 Spring 5.3.x: org.springframework.context.support.ApplicationContextAwareProcessor
class ApplicationContextAwareProcessor implements BeanPostProcessor {
private final ConfigurableEnvironment environment;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof EnvironmentAware) {
((EnvironmentAware) bean).setEnvironment(this.environment);
}
// ... 其他 Aware 回调
return bean;
}
}执行时序:
Bean 生命周期
│
├── 实例化(构造函数)
│
├── 属性填充(依赖注入)
│
├── BeanPostProcessor#postProcessBeforeInitialization
│ └── ApplicationContextAwareProcessor
│ ├── EnvironmentAware.setEnvironment() ← 在此注入
│ ├── EmbeddedValueResolverAware.setEmbeddedValueResolver()
│ └── ResourceLoaderAware.setResourceLoader()
│
├── @PostConstruct / InitializingBean
│
└── BeanPostProcessor#postProcessAfterInitialization6.2 @Value(${…}) 的注入过程
@Value 注解的处理由 AutowiredAnnotationBeanPostProcessor 完成,它会在 Bean 属性填充阶段解析占位符:
// 摘自 Spring 5.3.x: org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
public class AutowiredAnnotationBeanPostProcessor
extends InstantiationAwareBeanPostProcessorAdapter {
// 属性注入的核心方法
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 获取需要注入的 @Value 字段和方法
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 执行注入
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(...);
}
return pvs;
}
}在 inject 阶段,@Value 的值解析过程如下:
// AutowiredFieldElement / AutowiredMethodElement 中的内部实现
// 在 inject() 方法中涉及 @Value 解析的核心逻辑:
// 简化后的核心逻辑
if (this.beanFactory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory bf = (ConfigurableBeanFactory) this.beanFactory;
// 1. 获取 @Value 的字符串值(如 "${db.url}")
String value = element.getAnnotation(Value.class).value();
// 2. 通过 BeanFactory 的嵌入式值解析器解析占位符
// (内部会调用前面注册的 StringValueResolver 链)
String resolvedValue = bf.resolveEmbeddedValue(value);
// resolvedValue = "jdbc:mysql://localhost:3306/mydb"
// 3. 类型转换(将字符串转换为目标类型)
Object convertedValue = bf.getTypeConverter().convertIfNecessary(
resolvedValue, targetType, element.getAnnotation(Value.class).annotations());
// 4. 设置到字段或方法参数
field.set(bean, convertedValue);
}完整的解析链路:
@Value("${db.url:jdbc:mysql://localhost:3306/default}")
│
├── 1. AutowiredAnnotationBeanPostProcessor 解析 @Value 注解
│
├── 2. beanFactory.resolveEmbeddedValue("${db.url:jdbc:mysql://...}")
│ └── 遍历 embeddedValueResolvers 链
│ └── PropertySourcesPlaceholderConfigurer 注册的 StringValueResolver
│ └── PropertySourcesPropertyResolver.resolvePlaceholders()
│ └── parseStringValue() 递归解析
│ ├── 查找 "db.url"
│ ├── 遍历 PropertySource 链
│ │ ├── @PropertySource 加载的属性文件
│ │ ├── systemProperties (-Ddb.url=...)
│ │ └── systemEnvironment
│ └── 未找到 → 使用默认值 jdbc:mysql://localhost:3306/default
│
├── 3. TypeConverter 类型转换
│ └── String → 目标类型(String/Integer/Boolean 等)
│
└── 4. ReflectionUtils 反射设置字段值七、Profile 机制
7.1 Profile 的基本概念
Profile 是 Spring 提供的环境隔离方案,允许根据运行环境(开发、测试、生产)注册不同的 Bean:
@Configuration
@Profile("dev")
public class DevDataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
@Configuration
@Profile("prod")
public class ProdDataSourceConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(env.getProperty("db.url"));
ds.setUsername(env.getProperty("db.user"));
ds.setPassword(env.getProperty("db.password"));
return ds;
}
}7.2 Profile 的设置方式
方式一:编程式(在配置类中设置)
@Configuration
public class AppConfig {
@Bean
public static BeanFactoryPostProcessor profileActivator() {
return beanFactory -> {
ConfigurableEnvironment env = (ConfigurableEnvironment)
((ConfigurableListableBeanFactory) beanFactory).getEnvironment();
env.setActiveProfiles("dev", "h2");
env.setDefaultProfiles("default");
};
}
}方式二:属性配置
# application.properties
spring.profiles.active=dev,embedded-db
spring.profiles.default=default方式三:JVM 启动参数
-Dspring.profiles.active=staging -Dspring.profiles.default=default方式四:环境变量
export SPRING_PROFILES_ACTIVE=dev,test
export SPRING_PROFILES_DEFAULT=default7.3 @Profile 注解的处理原理
@Profile 注解的处理由 ProfileCondition 配合 ConditionEvaluator 完成:
// 摘自 Spring 5.3.x: org.springframework.context.annotation.ProfileCondition
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs =
metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
// 调用 Environment.acceptsProfiles 判断
if (context.getEnvironment().acceptsProfiles(
Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true; // 没有 @Profile 注解 → 匹配所有环境
}
}Environment.acceptsProfiles() 的实现逻辑:
// StandardEnvironment 中的实现
@Override
public boolean acceptsProfiles(Profiles profiles) {
// 1. 获取当前激活的 Profile
Set<String> activeProfiles = getActiveProfilesAsSet();
if (activeProfiles.isEmpty()) {
// 2. 如果没有显式激活,使用默认 Profile
activeProfiles = getDefaultProfilesAsSet();
}
// 3. 调用 Profiles 接口的匹配方法
return profiles.matches(activeProfile -> activeProfiles.contains(activeProfile));
}Profile 匹配逻辑:
// spring-profiles-expression 语法示例
@Component
@Profile("dev | staging") // 开发或预发环境
public class DevOnlyService { }
@Component
@Profile("!prod") // 非生产环境
public class InternalService { }
@Component
@Profile("dev & postgresql") // 同时满足多个 Profile
public class DevPostgresService { }7.4 Profile 在 BeanDefinition 注册阶段的过滤
Profile 的过滤发生在 ConfigurationClassParser 解析配置类时:
// 摘自 Spring 5.3.x: org.springframework.context.annotation.ConfigurationClassParser
class ConfigurationClassParser {
// 判断配置类是否应被跳过
private boolean shouldSkip(@Nullable String className, ConfigurationPhase phase) {
// 1. 检查 @Conditional 注解(@Profile 是 @Conditional 的一种)
if (hasConditionalAnnotation(metadata)) {
// 2. 获取所有 Condition 实现(包括 ProfileCondition)
Condition[] conditions = getConditionAnnotations(metadata);
// 3. 评估条件
for (Condition condition : conditions) {
if (!condition.matches(this.conditionContext, metadata)) {
return true; // 条件不满足,跳过此配置类
}
}
}
return false;
}
}整个处理流程:
AbstractApplicationContext.refresh()
│
├── prepareEnvironment()
│ └── 读取 spring.profiles.active / default
│
├── obtainFreshBeanFactory()
│ └── BeanDefinition 加载(包括 @Configuration 类)
│
├── invokeBeanFactoryPostProcessors()
│ └── ConfigurationClassPostProcessor
│ └── ConfigurationClassParser
│ └── processConfigurationClass()
│ └── shouldSkip() ← @Profile 过滤
│ ├── 获取 @Profile 的 value
│ ├── ProfileCondition.matches()
│ └── environment.acceptsProfiles()
│ ├── 匹配 → 注册 BeanDefinition
│ └── 不匹配 → 跳过
│
└── finishBeanFactoryInitialization()
└── 只实例化已注册的 BeanDefinition八、AbstractApplicationContext.refresh() 中 prepareEnvironment 详解
8.1 prepareEnvironment 的完整流程
prepareEnvironment() 是 AbstractApplicationContext.refresh() 的早期步骤之一,负责初始化容器的 Environment 对象:
// 摘自 Spring 5.3.x: org.springframework.context.support.AbstractApplicationContext
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备工作:记录启动时间、设置标志位、检查必需属性
prepareRefresh();
// 2. ★★★ 创建并准备 Environment ★★★
prepareEnvironment();
// 3. 创建 BeanFactory 并加载 BeanDefinition
obtainFreshBeanFactory();
// 4. 准备 BeanFactory(设置 ClassLoader、后处理器等)
prepareBeanFactory(beanFactory);
try {
// 5. 允许子类后置处理 BeanFactory
postProcessBeanFactory(beanFactory);
// 6. 调用 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 7. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 8. 国际化初始化
initMessageSource();
// 9. 应用事件多播器初始化
initApplicationEventMulticaster();
// 10. 初始化特殊 Bean(onRefresh())
onRefresh();
// 11. 注册监听器
registerListeners();
// 12. 实例化所有非懒加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 13. 完成 refresh(发布事件)
finishRefresh();
} catch (BeansException ex) {
// ... 回滚处理
}
}
}8.2 prepareEnvironment 方法源码详解
// 摘自 Spring 5.3.x: org.springframework.context.support.AbstractApplicationContext
protected ConfigurableEnvironment createEnvironment() {
// 创建 StandardEnvironment 或子类(如 StandardServletEnvironment)
return new StandardEnvironment();
}
protected void prepareEnvironment() {
// 1. 获取 Environment(优先使用自定义的,否则创建默认的)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 将 ApplicationContext 的 activeProfiles 和 defaultProfiles
// 设置到 Environment 上
configureEnvironment(environment, this.activeProfiles, this.defaultProfiles);
// 3. ★ 重要:将 Environment 绑定到 ApplicationContext
// (后续所有代码可以通过 context.getEnvironment() 获取)
this.environment = environment;
// 4. 对 Web 环境进行额外初始化
// (ServletContext 参数、ServletConfig 参数注入到 PropertySource 链)
if (environment instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) environment)
.initPropertySources(this.servletContext, this.servletConfig);
}
// 5. ★ 将 PropertySource 链包装为 PropertySource 迭代器
// 存到 BeanFactory 中供后续使用
if (this.beanFactory != null) {
this.beanFactory.setTempPropertySources(
environment.getPropertySources().iterator());
}
}8.3 configureEnvironment 方法
// 摘自 Spring 5.3.x
protected void configureEnvironment(ConfigurableEnvironment environment,
@Nullable String[] activeProfiles, @Nullable String[] defaultProfiles) {
if (activeProfiles != null) {
// 设置显式激活的 Profile
environment.setActiveProfiles(activeProfiles);
}
if (defaultProfiles != null) {
// 设置默认 Profile
environment.setDefaultProfiles(defaultProfiles);
}
// 重要:allowBeanDefinitionOverriding 和 allowCircularReferences
// 已从 5.3 移除此处的配置,挪到了 refresh() 中的 prepareRefresh()
}8.4 属性源加载的完整时间线
时间线 阶段 PropertySource 链的变化
────── ────────────────────────────── ─────────────────────────────────
T1 AbstractApplicationContext 创建默认 StandardEnvironment
构造函数 → systemProperties
→ systemEnvironment
T2 prepareEnvironment() Web 环境添加:
→ servletConfigInitParams (addFirst)
→ servletContextInitParams (addFirst)
T3 invokeBeanFactoryPostProcessors PropertySourceProcessor 处理 @PropertySource
→ PropertySourceProcessor → 加载的配置文件 (addFirst)
T4 PropertySourcesPlaceholder- PropertySourcesPlaceholderConfigurer
Configurer.postProcessBeanFactory 合并所有 PropertySource
T5 finishBeanFactoryInitialization @Value("${...}") 注入
→ AutowiredAnnotationBeanPostProcessor最终 PropertySource 链示例:
(优先级从高到低)
┌── 1. @PropertySource("classpath:application.yml") (T3 添加)
├── 2. @PropertySource("classpath:db.properties") (T3 添加)
├── 3. servletContextInitParams (T2 添加)
├── 4. servletConfigInitParams (T2 添加)
├── 5. systemProperties (-D 参数) (T1 即有)
└── 6. systemEnvironment (环境变量) (T1 即有)九、多环境配置最佳实践
9.1 环境配置文件命名策略
Spring Boot 推荐的 profile-specific 命名方式也适用于 Spring Framework 项目:
application.properties # 公共配置
application-dev.properties # 开发环境
application-test.properties # 测试环境
application-staging.properties # 预发环境
application-prod.properties # 生产环境9.2 配置管理方案
方案一:基于 Profile 的配置隔离
// 公共配置
@Configuration
@PropertySource("classpath:config/common.properties")
public class CommonConfig { }
// 开发环境特定配置
@Configuration
@Profile("dev")
@PropertySource("classpath:config/dev.properties")
public class DevConfig { }
// 生产环境特定配置
@Configuration
@Profile("prod")
@PropertySource("classpath:config/prod.properties")
public class ProdConfig {
@Bean
public DataSource dataSource(@Value("${db.url}") String url,
@Value("${db.user}") String user,
@Value("${db.password}") String password) {
// 生产环境使用连接池
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl(url);
ds.setUsername(user);
ds.setPassword(password);
ds.setMaximumPoolSize(20);
return ds;
}
}方案二:按优先级分层配置
利用 PropertySource 链的优先级,实现配置的覆盖机制:
@Configuration
public class LayeredConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
ConfigurableEnvironment env) {
MutablePropertySources sources = env.getPropertySources();
// 1. 加载通用配置(最低优先级)
try {
sources.addLast(new ResourcePropertySource(
"common", "classpath:config/common.properties"));
} catch (IOException e) {
// common.properties 为可选配置
}
// 2. 根据当前 Profile 加载环境特定配置
String[] activeProfiles = env.getActiveProfiles();
if (activeProfiles.length == 0) {
activeProfiles = env.getDefaultProfiles();
}
for (String profile : activeProfiles) {
String resource = "classpath:config/application-" + profile + ".properties";
try {
// 在 common 之前、systemProperties 之后的位置插入
sources.addBefore("systemProperties",
new ResourcePropertySource(profile + "-config", resource));
} catch (IOException e) {
// 忽略不存在的 profile 配置文件
}
}
// 3. 加载外部配置(最高优先级,覆盖内部配置)
String externalConfig = System.getProperty("external.config.path");
if (externalConfig != null) {
try {
sources.addFirst(new ResourcePropertySource(
"external", "file:" + externalConfig));
} catch (IOException e) {
throw new IllegalStateException(
"Cannot load external config: " + externalConfig, e);
}
}
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
configurer.setPropertySources(sources);
return configurer;
}
}方案三:配置服务器的集成
在生产环境中,建议使用配置中心:
@Configuration
public class ExternalConfigCenter {
@Bean
public static PropertySourcesPlaceholderConfigurer configCenterPlaceholder(
ConfigurableEnvironment env) {
// 模拟从配置中心拉取配置
Properties props = new Properties();
String appName = env.getProperty("spring.application.name", "my-app");
String profile = StringUtils.arrayToCommaDelimitedString(env.getActiveProfiles());
// HttpClient 调用配置中心 API
// props = configCenterClient.getConfig(appName, profile);
MutablePropertySources sources = env.getPropertySources();
// 添加到最前面(最高优先级)
sources.addFirst(new PropertiesPropertySource("config-center", props));
PropertySourcesPlaceholderConfigurer configurer =
new PropertySourcesPlaceholderConfigurer();
configurer.setPropertySources(sources);
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
}9.3 配置分层模型
推荐使用四层配置模型:
优先级 配置层 示例 覆盖范围
高 外部化配置 命令行参数、环境变量、JNDI 全局覆盖
├── @PropertySource application-{profile}.yml 环境特定
├── Profile 配置 application-dev.yml 开发/生产
低 公共配置 application.yml 默认值9.4 配置安全最佳实践
@Configuration
public class SecureConfig {
// 敏感信息使用环境变量传递(不落入代码仓库)
@Value("${DB_PASSWORD}") // 来自 systemEnvironment
private String dbPassword;
// 设置合理的默认值以防配置缺失
@Value("${server.port:8080}")
private int serverPort;
// 使用 Required 语义强制检查关键配置
@Value("${PAYMENT_API_KEY}")
private String paymentApiKey;
// 验证关键配置是否存在
@PostConstruct
public void validateConfig() {
Assert.hasText(paymentApiKey,
"PAYMENT_API_KEY must be set via environment variable");
}
}9.5 常见陷阱与注意事项
陷阱 1:占位符解析顺序
// 错误:这两个 Bean 会在 @PropertySource 加载前实例化
// 导致占位符无法解析
@Component
public class UserService {
@Value("${user.default.name}")
private String defaultName;
}
@Configuration
@PropertySource("classpath:user.properties") // 晚于 AutowiredAnnotationBeanPostProcessor
public class UserConfig { }
// 正确:使用 @PropertySource 的配置类必须被 ComponentScan 扫描到,
// 且 PropertySourceProcessor 会在 autowired 之前执行陷阱 2:静态字段 @Value 注入无效
@Component
public class StaticConfig {
// @Value 不能注入静态字段
@Value("${app.version}")
private static String appVersion; // 始终为 null
// 正确做法:使用非静态 setter 方法
private static String appVersion;
@Value("${app.version}")
public void setAppVersion(String version) {
StaticConfig.appVersion = version;
}
}陷阱 3:PropertySource 名称冲突
// 如果两个 @PropertySource 使用相同 name(或默认相同的文件名)
// 后注册的会覆盖先注册的
@Configuration
@PropertySource(name = "myConfig", value = "classpath:config-v1.properties")
public class ConfigV1 { }
@Configuration
@PropertySource(name = "myConfig", value = "classpath:config-v2.properties")
public class ConfigV2 { }
// → 最终只有 config-v2.properties 生效陷阱 4:PropertySourcesPlaceholderConfigurer 必须是 static 方法
@Configuration
public class AppConfig {
// 正确:必须是 static 方法
@Bean
public static PropertySourcesPlaceholderConfigurer ppc() {
return new PropertySourcesPlaceholderConfigurer();
}
// 错误:非 static 方法会导致 PropertySourcesPlaceholderConfigurer
// 在普通 Bean 实例化阶段注册,错过 BeanFactoryPostProcessor 的执行时机
// @Bean
// public PropertySourcesPlaceholderConfigurer ppcWrong() { ... }
}十、总结
Spring Framework 的 Environment 与 PropertySource 体系是整个 IoC 容器配置管理的基石:
| 组件 | 职责 | 关键实现 |
|---|---|---|
Environment | 统一属性访问 & Profile 查询 | StandardEnvironment, StandardServletEnvironment |
PropertySource | 单个属性源的抽象 | MapPropertySource, ResourcePropertySource |
MutablePropertySources | 有序的属性源链管理 | CopyOnWriteArrayList 维护顺序 |
@PropertySource | 声明式引入属性文件 | PropertySourceProcessor (BFPP) |
PropertySourcesPlaceholderConfigurer | 占位符解析引擎 | BFPP + StringValueResolver |
@Value(${…}) | 字段/方法级别属性注入 | AutowiredAnnotationBeanPostProcessor |
@Profile | 环境隔离 | ProfileCondition + ConditionEvaluator |
prepareEnvironment() | 容器启动时 Environment 初始化 | AbstractApplicationContext |
核心设计思想:通过统一的 PropertySource 链,将分散在不同来源(配置文件、环境变量、JVM 参数、JNDI、配置中心)的配置整合为一个有序的、可覆盖的配置层,为应用程序提供一致且灵活的属性解析与多环境管理能力。