《Spring Boot 3:高级与架构设计》第 2 章 IOC容器的高级机制 Environment 个人理解 4
1 Environment 是干什么的
在入门篇中讲解过使用 properties 文件、YML 文件进行外部化配置,使用注解 @PropertySource 加载到 IOC 容器。
这些配置项的键值对都保存到 IOC 容器的什么区域?使用 @Value 完成属性赋值时又是如何从 IOC 容器中获取的?
这些都跟 Environment 有关。
虽然 Environment 也属于 IOC 容器的一个Bean,但是将 Environment 划分在 IOC 容器的组件以外,是考虑到 Environment 的地位非常特殊,Environment 对组件 Bean 有提供配置属性值的功能,所以图中有一个从 Environment 指向组件 Bean 的箭头。

Environment 的 javadoc 比较长,本节仅讲解关键信息。读者可以借助 IDE 自行阅读完整的 javadoc。

简单总结,Environment 是外部化配置相关的组件。即与 Properties YAML 配置文件的使用相关。
2 分析 Environment 的结构
借助 IDEA 查看 Environment 的继承结构,简单得出两个结论:
(1)Environment 不是顶层根接口;
(2)Environment 的落地实现有多种方式,分别适用于不同的场景。


2.1 PropertyResolver
从源码中可以看到 Environment 继承了 PropertyResolver 接口。

从类名上看,PropertyResolver 是一个属性解析器。
它可以处理 XML 配置文件、注解配置类、普通 Bean 中使用到的属性配置占位符。
Environment 继承了 PropertyResolver,并且 Environment 中存放了 profiles 和 properties 属性配置,
这意味着 Environment 可以解析属性配置占位符,并提取出 Environment 中的 properties 配置以完成赋值操作。
属性配置占位符即 ${}
查看 PropertyResolver 源码

2.2 ConfigurableEnvironment
ConfigurableEnvironment 接口继承了 Environment 接口。

查看 ConfigurableEnvironment 的源码,可以看到跟 ActiveProfiles DefaultProfiles PropertySources 相关的方法。即激活的配置,默认的配置,配置属性值相关的方法。

2.3 AbstractEnvironment
AbstractEnvironment 是抽象父类。

查看 AbstractEnvironment 的源码,
代码所列的是 AbstractEnvironment 的部分核心成员,其存储默认的 profile 和声明激活的所有 profile,以及所有的 PropertySource。
此外,另外一个很重要的成员,即 PropertySourcesPropertyResolver,它是一个 PropertyResolver 的实现类。
AbstractEnvironment 在此处组合了一个 PropertyResolver 的实现类,意图也很明显:只要是 PropertyResolver 接口下的方法要做的工作,全部交予 PropertySourcesPropertyResolver 代替完成。
这种设计称为"委派",它与代理、装饰者不同:
委派仅仅是将工作转移给另一个对象,而代理、装饰者可能会做额外的处理。

3 Environment 的基本使用
不建议直接在应用程序中使用 Environment。
此处先了解 Environment 的简单使用方式。
3.1 获取 Environment
既然 Environment 存在于 IOC 容器中,那么获取 Environment 的方式之一就是使用 @Autowired。
下面通过一个简单示例来获取 Environment。先编写一个类当 Environment 的持有器,使用 @Autowired 声明注入 Environment,随后使用注解驱动的 IOC 容器进行组件扫描并获取 EnvironmentHolder,如代码所示。

java
package com.yangjunbo.spring.environment.examplea.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class EnvironmentHolder {
@Autowired
private Environment environment;
public void printEnvironment() {
System.out.println(environment);
}
}

java
package com.yangjunbo.spring.environment.examplea;
import com.yangjunbo.spring.environment.examplea.bean.EnvironmentHolder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* ClassName: environmentAApplication
* Package: com.yangjunbo.spring.environment.examplea
* Description:
*
* @Author 杨钧博
* @Create 2026/9/8 9:20
* @Version 1.0
*/
public class environmentAApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.yangjunbo.spring.environment.examplea.bean");
EnvironmentHolder environmentHolder = ctx.getBean(EnvironmentHolder.class);
environmentHolder.printEnvironment();
}
}
执行 main 方法后,控制台打印了 Environment 的基本信息,从输出的内容可以直观地了解到,Environment 中存放的基本信息包括 profiles 的信息、PropertySource 的信息。

3.2 使用 Environment 获取配置属性的值
编写一个 Properties 配置文件。

jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.driver-class-name=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=123456
编写一个配置类,加载配置文件。

java
package com.yangjunbo.spring.environment.exampleb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.yangjunbo.spring.environment.exampleb.bean")
@PropertySource("environment/exampleb/jdbc.properties")
public class EnvironmentPropertyConfiguration {
}
随后从 EnvironmentHolder 中获取 Environment,并打印 jdbc.url 的值,如代码所示。

java
package com.yangjunbo.spring.environment.exampleb.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Component
public class EnvironmentHolder {
@Autowired
private Environment environment;
public void printEnvironment() {
System.out.println(environment.getProperty("jdbc.url"));
}
}
重新驱动 IOC 容器,并取出 EnvironmentHolder,执行 printEnvironment 方法,控制台可以正确打印 properties 文件中 jdbc.url 对应的值,这就是 Environment 的基本使用方式。

java
package com.yangjunbo.spring.environment.exampleb;
import com.yangjunbo.spring.environment.exampleb.bean.EnvironmentHolder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* ClassName: EnvironmentBApplication
* Package: com.yangjunbo.spring.environment.exampleb
* Description:
*
* @Author 杨钧博
* @Create 2026/9/8 9:34
* @Version 1.0
*/
public class EnvironmentBApplication {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.yangjunbo.spring.environment.exampleb");
EnvironmentHolder environmentHolder = ctx.getBean(EnvironmentHolder.class);
environmentHolder.printEnvironment();
}
}
4 Environment 深入探讨
4.1 Environment 的默认 profiles
打印 Environment 可以看到,defaultProfiles 属性的默认值是 default。

在 Environment 的抽象实现类 AbstractEnvironment 中有一个 getDefaultProfiles 方法获取defaultProfiles 属性的值,getDefaultProfiles 方法内部调用了 doGetDefaultProfiles 方法。doGetDefaultProfiles 方法内部获取默认 profiles 的值,取的是常量RESERVED_DEFAULT_PROFILE_NAME,即 "default"。



在应用启动时主动声明 spring.profiles.default 参数的配置属性值,可以覆盖 Spring Framework 中原有的默认 profiles。
一个比较常用的方法是在 JVM 的启动项中添加该参数,如图在 IDEA 的启动配置中声明 VMoptions。同理,指定激活的 profiles 也可以使用相同的方式,只需配置属性名为 spring.profiles.active。

4.2 Environment 解析 properties 的底层
AbstractEnvironment 中组合了一个 PropertySourcesPropertyResolver,以实现 "委派" 的效果。
委派即把工作转交给别的对象处理。

结合源码片段可以看出,凡是 Environment 要处理配置属性获取的动作,就会转交给其内部的 PropertySourcesPropertyResolver 来完成。

4.3 Environment 在开发中的应用场景
由于 Environment 本身保存了配置属性,因此它可被看作一个应用级配置库,用于存储一些与应用运行相关的配置参数(业务无关参数,如线程池大小、全局响应最大时间等),以及一些在应用运行期间通常不会改变的业务参数(如当前应用的版权信息、网站信息等)。
由于大多数 Web 应用开发的业务场景下都能直接或间接地获取 IOC 容器,自然也就可以获取 Environment,因此将这些配置属性存放到Environment 后,应用运行的大多数位置都可以获取这些配置属性值,从而支撑应用运行。