Srping Boot 中我们使用 EnvironmentAware 注入 Environment 对象后,可以在 Environment 中获得系统参数,命令行采参数,文件配置等信息。
Environment 是如何存储,管理这些值的呢?变量发生冲突怎么办呢?我们可以扩展 Environment 的行为吗?本文结合 Spring Boot 启动时 Environment 的初始化过程,了解 Environment 的配置方式、优先级、配置源与扩展方式。
Spring 中的 Environment 指什么?
Spring 中的 Environment 是什么呢?了解 Environment 之前,不得不提到 Property 和 Profile。
我们在写项目的时候,经常会抽取一些配置项,在 Java 中通常叫做属性,也就是 Property,本质是一组键值对配置信息。使用配置项的好处在于修改起来很容易,只需修改下配置文件或命令行参数,然后重启一下就可以了。
开发过程中,大多数项目都有多套配置对应多个环境,一般来说有开发环境、测试环境和生产环境。这里的“环境”就叫做 Profile。程序可以读取到 Profile 的值,根据 Profile 的不同展示不同的特性。其实从本质上讲,“环境”也是一个“配置”,只是这个配置太重要了,也比较特殊,所以作为一个单独的概念来处理。
Environment = Property + Profile
Spring Boot 中,默认使用的 Environment 的实现类是 StandardServletEnvironment,我们可以通过它的类图了解 Spring Boot 中的 Environment 是如何管理的。
PropertyResolver 接口负责 Property 的获取(通过 key 获得 value),Environment 继承了这个接口,加入获得 Profile 的内容。ConfigurablePropertyResolver 继承了 PropertyResolver,为了解决 Property 的获取过程中涉及到的数据类型的转换和${…}表达式的解析问题。ConfigurableEnvironment 在此基础上,加入了 Profile 的设置功能。ConfigurableWebEnvironment 扩展了 web 功能,将 servlet 上下文作为配置源。
AbstractEnvironment,StandardEnvironment,StandardServletEnvironment 都是 Spring 对上述功能的实现。
Spring Boot 中的配置来自哪里?
Spring Boot 中的配置来自不同的地方,最常见的来自于 application.properties、application.yaml、环境变量和命令行参数。我们可以在 Spring Boot 的官方文档看到各种各样的配置方式。
官方一共给出了 14 中配置方式,并且给出了配置的优先级。数字越大优先级越高。
- 通过硬编码的方式(SpringApplication.setDefaultProperties)进行配置。
- 在 Spring Boot 的配置类上使用 @PropertySource 注解指定配置文件。
- 使用配置文件 (比如 application.properties 文件)。
- 通过 random.* 配置的随机属性。
- 操作系统中的环境变量。
- Java 的系统属性,可通过 System.getProperties() 获得相关内容。
- java:comp/env 中 JNDI 属性。
- ServletContext 初始化参数(web 环境)
- ServletConfig 初始化参数(web 环境)
- SPRING_APPLICATION_JSON 属性,该属性以 JSON 形式存储在系统环境变量中。
- 命令行参数,类似于 java -jar -Denv=DEV 之类。
- @SpringBootTest 注解,仅在测试中使用。
- @TestPropertySource 注解,仅在测试中使用。
- 激活 devtools 时,位于 $HOME/.config/spring-boot 下的配置。
如此之多的配置方式,且配置项的来源是多样化的,如何对用户暴露这些配置呢?一种方式就是将所有来源都暴露给用户,用户可以从任意配置源中获得配置。还有一种方式就是由 Spring 管理这些配置源,内部排好优先级,对外暴露统一的 get 方法,用户不需要知道其中的细节。Spring 显然使用了后者。
Spring 使用 PropertySource 来表示一个配置源,PropertySource 有很多子类,比如 SystemEnvironmentPropertySource,PropertiesPropertySource 等等。Spring 将这些 PropertySource 维护在一个列表中,当用户想要获得一个配置的时候,Spring 会遍历这些配置源,依次判断是否有匹配的配置。配置源在列表中的数据其实就代表了优先级。
如下是 Spring 从 propertySources 中获得 Property 的方式。
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
综上,Spring Boot 中的 Environment 中维护了若干个 PropertySource,也就是配置源,所有的配置到来自于这些配置源。
java 通过System.getProperties()获取系统参数
java.version Java 运行时环境版本
java.vendor Java 运行时环境供应商
java.vendor.url Java 供应商的 URL
java.home Java 安装目录
java.vm.specification.version Java 虚拟机规范版本
java.vm.specification.vendor Java 虚拟机规范供应商
java.vm.specification.name Java 虚拟机规范名称
java.vm.version Java 虚拟机实现版本
java.vm.vendor Java 虚拟机实现供应商
java.vm.name Java 虚拟机实现名称
java.specification.version Java 运行时环境规范版本
java.specification.vendor Java 运行时环境规范供应商
java.specification.name Java 运行时环境规范名称
java.class.version Java 类格式版本号
java.class.path Java 类路径
java.library.path 加载库时搜索的路径列表
java.io.tmpdir 默认的临时文件路径
java.compiler 要使用的 JIT 编译器的名称
java.ext.dirs 一个或多个扩展目录的路径
os.name 操作系统的名称
os.arch 操作系统的架构
os.version 操作系统的版本
file.separator 文件分隔符(在 UNIX 系统中是“/”)
path.separator 路径分隔符(在 UNIX 系统中是“:”)
line.separator 行分隔符(在 UNIX 系统中是“/n”)
user.name 用户的账户名称
user.home 用户的主目录
user.dir 用户的当前工作目录