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

相关推荐
zl_dfq2 小时前
Java学习1 之 【核心机制、程序基础结构、I/O】
java
名字还没想好☜2 小时前
Go 实现指数退避重试:context 取消、抖动 jitter 与什么时候别重试
后端·golang·go
青山木2 小时前
RocketMQ 入门到原理(六):可靠性全景
java·后端·中间件·架构·rocketmq
青山木2 小时前
RocketMQ 入门到原理(五):特殊消息类型
java·后端·中间件·架构·rocketmq
第五页的你2 小时前
SpringBoot 源码理解
后端
遨游DATA2 小时前
Spring 接口返回 errorMessage=null:如何排查二次异常覆盖原始业务错误
spring boot·异常处理·接口排障
RISCV_Explorer2 小时前
RISC-V处理器性能优化:从指令集到微架构的协同设计
后端·risc-v
她的男孩2 小时前
数据库改了配置,说好的 30 秒自动刷新根本没跑:那条 @Scheduled 是注释状态
java·后端·架构
君顾12 小时前
西安24小时自助健身房系统软件开发实战:需求分析与技术落地指南
java·开发语言·健身房