Spring Framework源码解析——ConfigurableEnvironment


版权声明


一、引言

在 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 :增加 acceptsProfilesgetActiveProfiles 等只读 Profile 查询;
  • ConfigurablePropertyResolver :增加 setPlaceholderPrefixsetRequiredProperties 等解析器配置;
  • 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);
}

优先级顺序(高 → 低)

  1. ServletConfig init-param
  2. ServletContext init-param
  3. JNDI
  4. JVM 系统属性 (-D)
  5. 操作系统环境变量

四、核心机制一:PropertySourceMutablePropertySources

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 激活流程

  1. 用户调用 setActiveProfiles("dev") → 直接设置 activeProfiles
  2. 或通过属性 spring.profiles.active=dev → 在 AbstractEnvironment 初始化后被 ConfigurationPropertySources 解析并设置;
  3. 若未激活任何 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 基础上做了大量扩展:

  1. 自动配置 PropertySource

    • RandomValuePropertySourcerandom.int
    • ConfigurationPropertySources@ConfigurationProperties
    • DevToolsPropertyDefaultsPostProcessor(开发时默认值)
  2. Profile 激活增强

    • 支持 application-{profile}.properties 自动加载;
    • 支持 spring.profiles.group 定义 Profile 组。
  3. 环境后处理器

    • 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 最佳实践

  1. 避免直接修改 systemProperties :应通过添加自定义 PropertySource 实现;
  2. Profile 命名规范 :使用小写、连字符(如 prod-us-east);
  3. 优先使用 @Profile 而非硬编码:提升配置可维护性;
  4. 测试时使用 MockEnvironment:便于隔离外部依赖。
相关推荐
毕设源码-郭学长4 小时前
【开题答辩全过程】以 基于SpringBoot的宠物医院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
利剑 -~4 小时前
设计java高并安全类
java·开发语言
CoderYanger4 小时前
D.二分查找-基础——744. 寻找比目标字母大的最小字母
java·开发语言·数据结构·算法·leetcode·职场和发展
柯南二号4 小时前
【后端】【Java】一文详解Spring Boot 统一日志与链路追踪实践
java·开发语言·数据库
CoderYanger5 小时前
贪心算法:2.将数组和减半的最少操作次数
java·算法·leetcode·贪心算法·1024程序员节
爱学java的ptt5 小时前
面试手撕排序
java·面试
柯南二号5 小时前
【后端】【Java】RESTful书面应该如何写
java·开发语言·restful
JIngJaneIL5 小时前
基于Java+ vueOA工程项目管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
linsa_pursuer5 小时前
回文链表算法
java·算法·链表