《Spring Boot 3:高级与架构设计》第 2 章 IOC容器的高级机制 Environment 个人理解 4

《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 后,应用运行的大多数位置都可以获取这些配置属性值,从而支撑应用运行。

相关推荐
海鸥-w1 小时前
什么是AOP
java
全栈弄潮儿2 小时前
Python实战第2期: 变量与数据类型
后端·python·agent
Cc.Y2 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
启观川2 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
paopaokaka_luck2 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_926787052 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev2 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha2 小时前
Lambda 表达式和函数式接口
java·开发语言
计算机毕业设计杰瑞2 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
一木 之林2 小时前
Dify学习笔记 01 · 平台入门与实战:部署、五类应用、RAG与MCP(第1-16集)
人工智能·笔记·学习·计算机视觉