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:便于隔离外部依赖。
相关推荐
Boilermaker19921 小时前
[Java 并发编程] Synchronized 锁升级
java·开发语言
Cherry的跨界思维1 小时前
28、AI测试环境搭建与全栈工具实战:从本地到云平台的完整指南
java·人工智能·vue3·ai测试·ai全栈·测试全栈·ai测试全栈
alonewolf_992 小时前
JDK17新特性全面解析:从语法革新到模块化革命
java·开发语言·jvm·jdk
一嘴一个橘子2 小时前
spring-aop 的 基础使用(啥是增强类、切点、切面)- 2
java
sheji34162 小时前
【开题答辩全过程】以 中医药文化科普系统为例,包含答辩的问题和答案
java
恋爱绝缘体13 小时前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
xiaolyuh1233 小时前
Spring 框架 核心架构设计 深度详解
spring·设计模式·spring 设计模式
wszy18093 小时前
新文章标签:让用户一眼发现最新内容
java·python·harmonyos
wszy18093 小时前
顶部标题栏的设计与实现:让用户知道自己在哪
java·python·react native·harmonyos
程序员小假4 小时前
我们来说一下无锁队列 Disruptor 的原理
java·后端