[Java] 深入理解:Spring Environment
1 概述:Spring Environment
1.0 简介
-
Spring Framework 3.1 开始引入
Environment抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)。 -
ConfigurableEnvironment 是什么?
- ConfigurableEnvironment 是 Environment接口的一个实现,它提供了更多的配置选项。这意味着,除了可以读取和查询属性外,您还可以添加、删除或修改 PropertySource(属性源)。
-
PropertySource 是什么?
- PropertySource 是属性的来源。它可以是配置文件(如 application.properties 或 application.yml)、系统环境变量、命令行参数等。每个 PropertySource 都有一个唯一的名称,并且包含了一组键值对。
-
Environment 接口使用场景
-
⽤于属性占位符处理。
-
用于转换 Spring 配置属性类型。
-
用于存储 Spring 配置属性源(PropertySource)。
-
用于 Profiles 状态的维护。
-
-
Environment 的标准实现类图

1.1 spring 环境配置与管理的核心API
- spring 环境配置与管理的核心API,位于
spring-core项目中,这些核心API包括但不限于:- (abstract class)
org.springframework.core.env.PropertySource- 表示名称/值属性对的源的抽象基类。底层的{@linkplain #getSource() 源对象}可以是封装属性的任何类型{@code T}。示例包括{@link java.util.Properties}对象,{@link java.util.Map}对象,{@code ServletContext}和{@code ServletConfig}对象(用于访问初始化参数)。探索{@code PropertySource}类型层次结构以查看提供的实现。
- (interface)
org.springframework.core.env.PropertySourcesextends Iterable<PropertySource<?>> - (class)
org.springframework.core.env.MutablePropertySourcesimplements PropertySources - (Interface)
org.springframework.core.env.PropertyResolver - (Interface)
org.springframework.core.env.Environmentextends PropertyResolver - (Interface)
org.springframework.core.env.ConfigurableEnvironmentextends Environment, ConfigurablePropertyResolver - (abstract class)
org.springframework.core.env.AbstractEnvironmentimplements ConfigurableEnvironment - (class)
org.springframework.core.env.StandardEnvironmentextends AbstractEnvironment - (Interface)
org.springframework.core.env.Profiles - (final class)
org.springframework.core.env.ProfilesParser - ...
- (abstract class)

AbstractEnvironment 的具体实现类
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<!-- 5.2.15.RELEASE / ... -->
<version>${spring.version}</version>
<scope>compile</scope>
</dependency>
1.2 env 的 profiles :对不同环境配置进行分组
1.2.1 env 的 profiles 基本使用
首先,我们知道,整个spring应用运行的环境信息:profiles + properties
先看profiles,在代码中的配置为:
spring.profiles.active=prd/test/dev...
profiles作用:对 spring bean 逻辑分组
- 先定义一个ProfileService类,包含一个私有属性profile,如下:

- 新建一个ProfileConfiguration配置类,将新建的ProfileService类的实例bean定义在这里,等到springboot项目启动的时候,将可以将ProfileService实例bean装载的ioc容器中去了,整个如下:

- 在ProfileConfiguraiton配置类的各个bean上,加上
@Profile注解,模拟设置两种不同环境下的bean,声明不同环境的bean

使用
@Profile对不同的bean逻辑分布 xml 和注解都可以
- 现在我们不使用
SpringbootApplication类启动了,直接写一个main方法启动即可。例如如下,在启动之前,在Environment里面设置profile,setActiveProfile() 参数是数组,可以设置多个。- 这里测试之用,先设置“prd”,最后context.getBean可以取出ioc容器中的bean,打印到控制台就好。

一般来说,
getBean和@Autowired2种方式都可以取出 spring ioc容器中的 bean.
- 运行 springboot 工程,如果打印出来的ioc容器中的bean,prd环境中的bean,就可以了。

至此,对于env 的 profiles设置成功。
1.2.2 配置环境信息的方法
-
我们能够配置环境的地方不止一个,不仅像上面在启动类中可以配置,还可以在 application.properties/yaml 配置文件中配置,还可以在 程序启动参数(args) 或 JVM 参数等。
-
application.*等本地配置文件 -
程序启动参数(
args) -
JVM 参数
-
远程配置中心(nacos) 或 远程第三方服务
-
...
-


1.2.3 setActiveProfiles(...) : 入参是可设置多个 profile 的数组
setActiveProfiles() 参数是数组,可以设置多个,如下:


至此,对于 env 的 profiles 的讲解完成。
1.3 env 的 properties:存放环境配置
1.3.1 用 @Value 获取配置
用 @Value 取出普通配置
- 新建一个UserController类,打印出application.properties配置文件中的env属性即可,如下:

application.properties配置文件中的env属性为“hello world”,配置如下:

- 运行成功,取出来了,如下:

所以,我们看到,properties存放属性的环境或文件信息。
用 @Value 取出系统配置(如: java.version)
实际上,@Value注解还可以访问到系统环境变量的信息,如java.version,取出当前jdk版本,试一试:

看,打印出来了。

1.3.2 用 Environment 实例 获取配置
用 Environment 实例 bean 取出系统配置(如: java.version)
我们用@Autowired取出spring ioc容器中的Environment实例bean,然后直接用Environment实例bean(不用@Value)来取,如下:
取出spring ioc容器中的实例bean有两种方式,getBean方法 或 @Autowired注解。

看,也打印出来了。

1.3.3 为什么可以用 Environment 实例bean取出java.version这种系统配置?
那问题来了,为什么可以用Environment取出java.version这种系统配置呢?一起来看看Spring中的Environment接口,如下:

理由很简单,因为Environment实例bean已经在ioc容器中了,所以要取出系统配置java.version很简单,因为这个系统配置java.version就是写在Environment的实例bean里面的。
其他的,如BeanFactory、ApplicationContext实例bean也是在ioc容器里的,还实现了Environment接口,如下:

其实,作为Spring容器的内置接口,Environment中的配置有很多来源:系统的环境变量(如上面提到的java.version)、系统变量 System.propreties等。
大体上来说,Environment中的配置来源大体包括两大类:系统属性源 + springboot属性源,如下:

这个图很重要,本文接下来就讲这个图,讲Environment是如何处理 “系统属性源” 和 “springboot属性源” 的。
springboot启动类自动扫描所在包及其子包下spring+springmvc注解,但是不会扫描ibatis的@Mapper注解

1.4 Environment 综合应用
获取方式
- 在 spring 启动过程已经对 Environment 进行初始化了,那么我们是如何获取/使用该对象的? 我们可以通过:
- 实现接口
EnvironmentAware- 直接
@Autowired
均可以很方便的得到当前应用的环境:Environment 对象。
- 实现了此接口的类都应该有一个Environment类型 的环境,并且可以通过
getEnvironment方法取得。
我们熟知的所有的Spring应用上下文都实现了这个接口,因为
ApplictionContext就实现了这个接口,表示每个应用上下文都是有自己的运行时环境的。
EnvironmentCapable : 对外提供 Environment ———— ApplicationContext 及其接口实现类
- 完整源码
package org.springframework.core.env;
public interface EnvironmentCapable {
Environment getEnvironment();
}
- 集成、或实现此接口的类

主要是 ApplicationContext 及其接口实现类
EnvironmentAware : 应用程序获取 Environment
在类中通过实现该接口,spring 容器会自动帮我们注入 Environment 对象;这样子我们就可以拿到这个对象进行我们想要的操作。
在 Spring 中,EnvironmentAware 接口是一个回调接口,它提供了一个用于设置 Bean 所在环境的方法。
当一个 Bean 实现了 EnvironmentAware 接口时,在该 Bean 实例被实例化后,Spring 容器会调用 setEnvironment 方法,并将该 Bean 所在的环境作为参数传递进去。
-
主要作用:
- EnvironmentAware 主要用于获取加载当前 Bean 的环境(Environment)的引用,使得 Bean 能够在运行时获取到关于自身所在环境的信息。
-
Demo
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
@Component
public class GetEnvDemoBean implements EnvironmentAware {
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment; //从外部获取到 environment 对象
System.out.println("【EnvironmentAware】Bean 所在环境是:" + environment);
}
}
基于 env.resolvePlaceholders(strVal) 解析占位符变量
[interface] StringValueResolver
StringValueResolver 本身,Spring对它的定义为:一个处理字符串的简单策略接口。
// @since 2.5 该接口非常简单,就是个函数式接口~
@FunctionalInterface
public interface StringValueResolver {
@Nullable
String resolveStringValue(String strVal);
}
唯一 public 实现类为:EmbeddedValueResolver
[class] EmbeddedValueResolver implements StringValueResolver : 处理占位符
帮助ConfigurableBeanFactory 处理 placeholders 占位符的。
ConfigurableBeanFactory#resolveEmbeddedValue 处理占位符真正干活的,间接的就是它。
// @since 4.3 这个类出现得还是蛮晚的 因为之前都是用内部类的方式实现的,这个实现类是最为强大的 只是 SpEL
public class EmbeddedValueResolver implements StringValueResolver {
// BeanExpressionResolver 它支持的是SpEL 可以说非常的强大
// 并且它有 BeanExpressionContext 就能拿到BeanFactory工厂,就能使用它的`resolveEmbeddedValue`来处理占位符
// 双重功能都有了~~~拥有了和@Value一样的能力,非常强大
private final BeanExpressionContext exprContext;
@Nullable
private final BeanExpressionResolver exprResolver;
public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
this.exprContext = new BeanExpressionContext(beanFactory, null);
this.exprResolver = beanFactory.getBeanExpressionResolver();
}
@Override
@Nullable
public String resolveStringValue(String strVal) {
// step1 先使用 Bean工厂 处理占位符 resolveEmbeddedValue
String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
// step2 再使用 el 表达式参与计算
if (this.exprResolver != null && value != null) {
Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
value = (evaluated != null ? evaluated.toString() : null);
}
return value;
}
}
[abstract class] AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory
关于Bean工厂 resolveEmbeddedValue 的实现,我们这里也顺带看看:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
} else {
String result = value;
// embeddedValueResolvers 是个复数:因为我们可自定义处理器添加到 bean 工厂来,增强它的能力
Iterator var3 = this.embeddedValueResolvers.iterator();
do {
if (!var3.hasNext()) {
return result;
}
StringValueResolver resolver = (StringValueResolver) var3.next();
result = resolver.resolveStringValue(result); //解析字符串的值
} while(result != null); // 只要处理结果不为 null,所有的处理器都会执行到
return null;
}
}
...
}
AbstractApplicationContext extends DefaultResourceLoader
而Bean工厂的处理器都怎么添加进去的呢????
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
...
// 如果从来没有注册过,Spring容器默认会给注册一个这样的内部类
// 可以看到,它最终还是委托给了 Environment 去干这件事~~~~~~
// 显然它最终就是调用 PropertyResolver#resolvePlaceholders
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
...
}
}
由此可见,解析占位符最终都返璞归真 ,真正最终的处理类,处理方法方法是:AbstractPropertyResolver#resolvePlaceholders ,这就是我们非常熟悉了,上面也有详细讲解,最终都是委托给了PropertyPlaceholderHelper 去处理的。
由此可见,若我们通过实现感知接口EmbeddedValueResolverAware 得到一个StringValueResolver 来处理我们的占位符、SpEL计算。
根本原因是:
class ApplicationContextAwareProcessor implements BeanPostProcessor {
// 这里new的一个 EmbeddedValueResolver ,它持有对beanFactory的引用~~~
// 所以调用者直接使用的是 EmbeddedValueResolver:它支持解析占位符(依赖于Enviroment上面有说到)并且支持SpEL的解析 非常强的
public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
}
}
2 Spring Environment 源码解析 I
Environment 接口定义:extends PropertyResolver
// 它继承自 PropertyResolver ,所以是对属性的一个扩展~
public interface Environment extends PropertyResolver {
// 就算被激活 也是支持同时激活多个profiles的~
// 设置的key是:spring.profiles.active
String[] getActiveProfiles();
// 默认的也可以有多个 key为:spring.profiles.default
String[] getDefaultProfiles();
// 看看传入的profiles是否是激活的~~~~ 支持!表示不激活
@Deprecated
boolean acceptsProfiles(String... profiles);
// Spring5.1后提供的 用于替代上面方法 Profiles是Spring5.1才有的一个函数式接口~
boolean acceptsProfiles(Profiles profiles);
}
PropertyResolver
org.springframework.core.env.PropertyResolver此接口用于在底层源码 之上解析一系列 的属性值。
例如properties文件,yaml文件,甚至是一些nosql(因为nosql也是k-v形式)。
- 接口中定义了一系列读取,解析,判断是否包含指定属性的方法:
// @since 3.1 出现得还是相对较晚的 毕竟SpEL也3.0之后才出来嘛
public interface PropertyResolver {
// 查看规定指定的key是否有对应的value 注意:若对应值是null的话 也是返回false
boolean containsProperty(String key);
// 如果没有则返回null
@Nullable
String getProperty(String key);
// 如果没有则返回defaultValue
String getProperty(String key, String defaultValue);
// 返回指定key对应的value,会解析成指定类型。如果没有对应值则返回null(而不是抛错~)
@Nullable
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 若不存在就不是返回null了 而是抛出异常~ 所以不用担心返回值是null
String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 解析${...}这种类型的占位符,把他们替换为使用getProperty方法返回的结果,解析不了并且没有默认值的占位符会被忽略(原样输出)
String resolvePlaceholders(String text);
// 解析不了就抛出异常~
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
- 它的继承链,大致如下:
PropertyResolver ->ConfigurablePropertyResolver ->AbstractPropertyResolver ->PropertySourcesPropertyResolver
ConfigurablePropertyResolver : extends PropertyResolver
顾名思义,它是一个可配置的处理器。这个方法不仅有父接口所有功能,还扩展定义类型转换、属性校验、前缀、后缀、
分隔符 等一些列的功能,这个在具体实现类里有所体现~
public interface ConfigurablePropertyResolver extends PropertyResolver {
// 返回在解析属性时使用的ConfigurableConversionService。此方法的返回值可被用户定制化set
// 例如可以移除或者添加Converter cs.addConverter(new FooConverter());等等
ConfigurableConversionService getConversionService();
// 全部替换ConfigurableConversionService的操作(不常用) 一般还是get出来操作它内部的东东
void setConversionService(ConfigurableConversionService conversionService);
// 设置占位符的前缀 后缀 默认是${}
void setPlaceholderPrefix(String placeholderPrefix);
void setPlaceholderSuffix(String placeholderSuffix);
// 默认值的分隔符 默认为冒号:
void setValueSeparator(@Nullable String valueSeparator);
// 是否忽略解析不了的占位符,默认是false 表示不忽略~~~(解析不了就抛出异常)
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
/**
* Specify which properties must be present, to be verified by
* {@link #validateRequiredProperties()}.
*/
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}
ConfigurableXXX 成了Spring的一种命名规范,或者说是一种设计模式。它表示可配置的,所以都会提供大量的set方法。
Spring很多接口都是读写分离的,最顶层接口一般都只会提供只读方法,这是Spring框架设计的一般规律之一
AbstractPropertyResolver : implements ConfigurablePropertyResolver
它是对ConfigurablePropertyResolver 的一个抽象实现,实现了了所有的接口方法,并且只提供一个抽象方法给子类去实现。
// @since 3.1
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
@Nullable
private volatile ConfigurableConversionService conversionService;
// PropertyPlaceholderHelper是一个极其独立的类,专门用来解析占位符 我们自己项目中可拿来使用 因为它不依赖于任何其他类
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;
@Nullable
private PropertyPlaceholderHelper strictHelper;
private boolean ignoreUnresolvableNestedPlaceholders = false;
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
private final Set<String> requiredProperties = new LinkedHashSet<>();
// 默认值使用的DefaultConversionService
@Override
public ConfigurableConversionService getConversionService() {
// Need to provide an independent DefaultConversionService, not the
// shared DefaultConversionService used by PropertySourcesPropertyResolver.
ConfigurableConversionService cs = this.conversionService;
if (cs == null) {
synchronized (this) {
cs = this.conversionService;
if (cs == null) {
cs = new DefaultConversionService();
this.conversionService = cs;
}
}
}
return cs;
}
... // 省略get/set
@Override
public void setRequiredProperties(String... requiredProperties) {
for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
}
// 校验这些key~
@Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
... //get/set property等方法省略 直接看处理占位符的方法即可
@Override
public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
}
return doResolvePlaceholders(text, this.nonStrictHelper);
}
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
}
// 此处:最终都是委托给PropertyPlaceholderHelper去做 而getPropertyAsRawString是抽象方法 根据key返回一个字符串即可~
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
}
最终,处理占位的核心逻辑在
PropertyPlaceholderHelper 身上,这个类不可小觑,是一个与业务无关非常强大的工具类,我们可以直接拿来使用
PropertyPlaceholderHelper
将字符串里的占位符内容,用我们配置的properties里的替换。这个是一个单纯的类,没有继承没有实现,而且简单无依赖,没有依赖Spring框架其他的任何类。
// @since 3.0 Utility class for working with Strings that have placeholder values in them
public class PropertyPlaceholderHelper {
// 这里保存着 通用的熟悉的 开闭的符号们~~~
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
private final String placeholderPrefix;
private final String placeholderSuffix;
private final String simplePrefix;
@Nullable
private final String valueSeparator;
private final boolean ignoreUnresolvablePlaceholders; // 是否采用严格模式~~
// 从properties里取值 若你有就直接从Properties里取值了~~~
public String replacePlaceholders(String value, final Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, properties::getProperty);
}
// @since 4.3.5 抽象类提供这个类型转换的方法~ 需要类型转换的会调用它
// 显然它是委托给了ConversionService,而这个类在前面文章已经都重点分析过了~
@Nullable
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
if (targetType == null) {
return (T) value;
}
ConversionService conversionServiceToUse = this.conversionService;
if (conversionServiceToUse == null) {
// Avoid initialization of shared DefaultConversionService if
// no standard type conversion is needed in the first place...
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
conversionServiceToUse = DefaultConversionService.getSharedInstance();
}
return conversionServiceToUse.convert(value, targetType);
}
// 这里会使用递归,解析占位符如:${}
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { ... }
}
PropertySourcesPropertyResolver
从上面知道
AbstractPropertyResolver 封装了解析占位符的具体实现。
PropertySourcesPropertyResolver 作为它的子类它只需要提供数据源,所以它主要是负责提供数据源。
// @since 3.1 PropertySource:就是我们所说的数据源,它是Spring一个非常重要的概念,比如可以来自Map,来自命令行、来自自定义等等~~~
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
// 数据源们~
@Nullable
private final PropertySources propertySources;
// 唯一构造函数:必须制定数据源~
public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
this.propertySources = propertySources;
}
@Override
public boolean containsProperty(String key) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource.containsProperty(key)) {
return true;
}
}
}
return false;
}
...
//最终依赖的都是propertySource.getProperty(key);方法拿到如果是字符串的话
//就继续交给 value = resolveNestedPlaceholders((String) value);处理
@Nullable
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) {
// 若值是字符串,那就处理一下占位符~~~~~~ 所以我们看到所有的PropertySource都是支持占位符的
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
...
}
ConfigurableEnvironment : extends Environment, ConfigurablePropertyResolver
扩展出了
修改 和配置profiles的一系列方法,包括用户自定义的和系统相关的属性。所有的 环境实现类也都是它的实现~
// @since 3.1
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
void setActiveProfiles(String... profiles);
void addActiveProfile(String profile);
void setDefaultProfiles(String... profiles);
// 获取到所有的属性源~ MutablePropertySources表示可变的属性源们~~~ 它是一个聚合的 持有List<PropertySource<?>>
// 这样获取出来后,我们可以add或者remove我们自己自定义的属性源了~
MutablePropertySources getPropertySources();
// 系统环境属性~~~
Map<String, Object> getSystemProperties();
Map<String, Object> getSystemEnvironment();
// 合并两个环境配置信息~ 此方法唯一实现在AbstractEnvironment上
void merge(ConfigurableEnvironment parent);
}
AbstractEnvironment : implements ConfigurableEnvironment
该抽象类完成了对active、default等相关方法的复写处理。它内部持有一个
MutablePropertySources 引用来管理属性源。 So,留给子类的活就不多了, 只需要把你的属性源注册给我就OK了。
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
// 保留的默认的profile值 protected final属性,证明子类可以访问
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
private final Set<String> activeProfiles = new LinkedHashSet<>();
// 显然这个里面的值 就是default这个profile了~~~~
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
// 这个很关键,直接new了一个 MutablePropertySources来管理属性源们
// 并且是用的PropertySourcesPropertyResolver来处理里面可能的占位符~~~~~
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
// 唯一构造方法 customizePropertySources是空方法,交由子类去实现,对属性源进行定制~
// Spring对属性配置分出这么多曾经,在SpringBoot中有着极其重要的意义~~~~
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
// 该方法,StandardEnvironment实现类是有复写的~
protected void customizePropertySources(MutablePropertySources propertySources) {
}
// 若你想改变默认default这个值,可以复写此方法~~~~
protected Set<String> getReservedDefaultProfiles() {
return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
}
// 下面开始实现接口的方法们~~~~~~~
@Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
}
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
// 若目前是empty的,那就去获取:spring.profiles.active
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
//支持,分隔表示多个~~~且空格啥的都无所谓
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
@Override
public void setActiveProfiles(String... profiles) {
synchronized (this.activeProfiles) {
this.activeProfiles.clear(); // 因为是set方法 所以情况已存在的吧
for (String profile : profiles) {
// 简单的valid,不为空且不以!打头~~~~~~~~
validateProfile(profile);
this.activeProfiles.add(profile);
}
}
}
// default profiles逻辑类似,也是不能以!打头~
@Override
@Deprecated
public boolean acceptsProfiles(String... profiles) {
for (String profile : profiles) {
// 此处:如果该profile以!开头,那就截断出来 把后半段拿出来看看 它是否在active行列里~~~
// 此处稍微注意:若!表示一个相反的逻辑~~~~~请注意比如!dev表示若dev是active的,我反倒是不生效的
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
if (!isProfileActive(profile.substring(1))) {
return true;
}
} else if (isProfileActive(profile)) {
return true;
}
}
return false;
}
// 采用函数式接口处理 就非常的优雅了~
@Override
public boolean acceptsProfiles(Profiles profiles) {
Assert.notNull(profiles, "Profiles must not be null");
return profiles.matches(this::isProfileActive);
}
// 简答的说要么active包含,要门是default 这个profile就被认为是激活的
protected boolean isProfileActive(String profile) {
validateProfile(profile);
Set<String> currentActiveProfiles = doGetActiveProfiles();
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}
@Override
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
public Map<String, Object> getSystemProperties() {
return (Map) System.getProperties();
}
public Map<String, Object> getSystemEnvironment() {
// 这个判断为:return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
// 所以我们是可以通过在`spring.properties`这个配置文件里spring.getenv.ignore=false关掉不暴露环境变量的~~~
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
return (Map) System.getenv();
}
// 把父环境的属性合并进来~~~~
// 在调用ApplicationContext.setParent方法时,会把父容器的环境合并进来 以保证父容器的属性对子容器都是可见的
@Override
public void merge(ConfigurableEnvironment parent) {
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps); // 父容器的属性都放在最末尾~~~~
}
}
// 合并active
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
synchronized (this.activeProfiles) {
for (String profile : parentActiveProfiles) {
this.activeProfiles.add(profile);
}
}
}
// 合并default
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
for (String profile : parentDefaultProfiles) {
this.defaultProfiles.add(profile);
}
}
}
}
// 其余方法全部委托给内置的propertyResolver属性,因为它就是个`PropertyResolver`
...
}
StandardEnvironment : extends AbstractEnvironment
- StandardEnvironment,这个是Spring应用在非web容器运行的环境。
public class StandardEnvironment extends AbstractEnvironment {
// 这两个值定义着 就是在@Value注解要使用它们时的key~~~~~
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
// 注册MapPropertySource和SystemEnvironmentPropertySource
// SystemEnvironmentPropertySource是MapPropertySource的子类~~~~
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
3 Spring Environment 源码解析 II
先找到Environment接口

3.1 从run()方法开始
找打springboot工程的run方法,如下:

不断ctrl+左键,进入到真正有意义的run方法中,如下:

在这个真正有意义的run方法中,使用prepareEnvironment方法得到一个env实例,然后将这个实例设置到context中去,如下:

源码中,spring或springboot初始化加载时,env 在 context 之前,合乎常理。这里看的springboot的源码,其实spring的源码也一样。
选择prepareEnvironment方法

进入prepareEnvironment方法,先使用getOrCreateEnvironment方法新建或获得一个环境变量,然后将这个env实例放到configureEnvironment方法中去完成相关配置,最后通过listeners.environmentPrepared方法,将env实例交给监听事件,如下:

接下来看一下SpringApplication类prepareEnvironment()方法的三个子方法。
3.2 第一子方法:新建或获取环境getOrCreateEnvironment()方法
可以看到,如果类变量env不为null,就直接返回,如果为null,就根据类变量webApplicationType的实例类型,new一个Env实现类实例返回。

所以说,getOrCreateEnvironment()方法中涉及三个类:StandardEnvironment StandardServletEnvironment StandardReactiveWebEnvironment,这三个类都继承AbstractEnvironment,AbstractEnvironment又继承于Environment接口,关系如下:

先看AbstractEnvironment类
3.2.1 AbstractEnvironment类
在AbstractEnvironment的构造方法中,调用一个自定义属性源的方法,如下:

这个自定义属性源的方法,在AbstractEnvironment中是空实现,只能看它的子类了。

值得注意的是,对于AbstractEnvironment构造函数,
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
这是一个很优美的设计,可以将customizePropertySources的实现交给子类的处理,是模板模式。
AbstractEnvironment类就到这里的,我们看其子类StandardEnvironment类。
3.2.2 StandardEnvironment类
既然AbstractEnvironment类的customizePropertySources()是空实现,看看其子类StandardEnvironment类。

StandardEnvironment类只有一个customizePropertySources方法实现,也看完了,且看StandardServletEnvironment类。
3.2.3 StandardServletEnvironment类
直接转到customizePropertySources方法实现,如下:

关于customizePropertySources()方法,customizePropertySources()接收可变参数源作为输入。
该方法在AbstractEnvironment类中为空实现,在StandardEnvironment类中为将系统配置和系统环境变量放到env中,在StandardServletEnvironment类中为servlet配置属性、servlet上下文属性、JNDI属性,并调用StandardEnvironment的customizePropertySources()方法,所以同时有了servlet配置属性、servlet上下文属性、JNDI属性、系统配置和系统环境变量五种属性。
源码设计的优美之处1:StandardServletEnvironment类中的customizePropertySources()方法调用了StandardEnvironment的customizePropertySources()方法,因为是它里面将系统配置和系统环境变量放到env中;StandardEnvironment的customizePropertySources()方法没有调用AbstractEnvironment的customizePropertySources()方法,因为是它里面将里面方法体为空,不需要调用;这体现了源码的优美。
源码设计优美之处2:
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
AbstractEnvironment类的构造方法中调用customizePropertySources(),以后customizePropertySources()一层层被重写,反正调用最后子类的,具体环境生产具体对象,这体现了源码设计优美。
3.2.4 MutablePropertySources类
customizePropertySources()接收可变参数源作为输入,实际上就是一个MutablePropertySources类对象,让我们来看一下这个MutablePropertySources类,其定义如下:


让我们看看StandardServletEnvironment添加的三个属性是什么

在我们使用spring+springmvc的时候,需要配置一个web.xml,web.xml里面需要配置两个标签< context-params>< /context-params>和< init-params>< /init-params>,当时我们只是这样这样使用,实际上,web.xml中这两个标签作为属性配置到env里面去了。
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = “servletContextInitParams”; 就是 < context-params>< /context-params>
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = “servletConfigInitParams”; 就是 < init-params>< /init-params>

我们知道,StandardEnvironment:系统变量+系统环境变量
/** System environment property name: @value. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME
/** JVM system properties property name: @value. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME
上面,前者表示系统环境变量,所以我们刚才使用@Value(“${java.version}”)访问到了系统环境变量,后者表示系统属性。
我们现在完善图,一图小结源码结构:
最后一个问题,配置顺序,优先级如何确定?
回答:不管是StandardEnvironment类和还是StandardServletEnvironment类,都是使用addLast添加属性的,所有属性的顺序就是源代码从上到下。

好了,
ConfigurableEnvironment environment = getOrCreateEnvironment(); 这行代码完了,回到SpringApplication类,看configureEnvironment(environment, applicationArguments.getSourceArgs());这行代码。
3.3 第二子方法:准备环境configureEnvironment()
3.3.1 SpringApplication类中的configureEnvironment()
configureEnvironment()是在新建/获取env实例后执行的,如下:

在configureEnvironment()方法中,第一个实参就是ConfigurableEnvironment类对象,就是env对象,刚刚看过了,第二个参数是一个DefaultApplicationArguments类对象.getSourceArgs(),它是在源码中new出来的,如下(简答了解就好,只要知道命令行参数也是可以被识别的就好):

进入到重点,看到configureEnvironment方法,如下:

类变量addConversionService 默认为true,private boolean addConversionService
它表示统一类型转换,当其为true,执行if代码块,得到一个conversionService,并设置到Spring框架的env中去。
接着往下看,可以看到,configureEnvironment方法先后执行了两个方法,就是 configurePropertySources(environment, args); 和 configureProfiles(environment, args); ,就是整个spring应用运行的环境信息:profiles + properties,就是在这两个方法里面解析出来的。

3.3.2 解析properties属性:SpringApplication类的configurePropertySources()
看到configurePropertySources()方法,这个方法解析开发者配置的所有properties属性。

先获得env实例中的属性源,如果默认属性不为空,将其添加到sources中,addLast就是在list末尾添加,如果启动的时候,命令行中也传入了参数,也添加进来,没传入也没关系。
小结,继续更新env图

env中添加命令行参数很好懂,但是默认参数defaultproperties是什么?从哪里来的?先看这个defaultproperties的定义,它在SpringApplication类中,默认是一个map结构,定义如下:

值得注意的是,这个defaultproperties仅仅在springboot才有的,spring是没有的。
关于这个defaultproperties,可以自己设置 setDefautProperties,尝试一下,如下面springboot启动类中。

运行启动起来,看,在源码中断点,真的走到了这个地方,取出defaultProperties变量,看到自己手动添加的属性。

好了,configurePropertySources()方法完成了,下面看configureProfiles()方法。
3.3.3 SpringApplication类的configureProfiles()
先打开configureProfiles()的源码,看到就是根据类变量additionalProfiles新建一个LinkedHashSet,然后将env变量中的profiles属性都放到这个新建的set中,最后将这个set变为字符串又放到env中去。

好了,我们进入到setActiveProfiles()方法,如下:

setActiveProfiles()设置env中的profile


关于configureProfiles方法,看起来好像没什么意义?将env中的profiles放到set中,然后又将set放到env中。
但是,请注意,这里这个AbstractEnvironment类中的setActiveProfiles()方法接收String类型可变数组,同时将字符串类型的profile放到一个Set类型的集合activeProfiles中,因为set集合,可以配置多个,但是不能字符串重名。
实际上,这个方法我们一开始就用到,在env中设置profile,接受一个数组,包含两个字符串 “prd” 和 “env” ,保证既是数组又不重名。

其实,在application.properties文件中设置和在Run/Debug Configurations中设置都是一个道理,底层都是调用这个方法setActiveProfiles()。

3.4 第三子方法:开始加载springboot的配置listeners.environmentPrepared(environment)
3.4.1 listeners.environmentPrepared(environment)
这里就是一个监听逻辑了,复习一下,在Spring中,通用的监听逻辑是:自定义一个事件类,事件发布者发布一个自定义事件,时间接受者listener接收自定义事件及其子类事件。

进入listeners.environmentPrepared(environment);

再进入environmentPrepared方法,如下:

看一下广播事件的具体逻辑,如下:

再进去invokeListener方法,如下:

这个listener.onApplicationContext(event)就是表示监听者监听到事件触发后要完成的相应逻辑。
好了,我们重温一下这个调用关系,如下:

好了,我们来看看这个listener监听到事件发生后的具体操作,进入onApplicationEvent方法,但是这种方法有很多个,如下:

任意找一个,这里找ConfigFileApplicationListener类,可以看到对于通过event类型,执行了不同操作,如下:

对于onApplicationEvent的第二个方法onApplicationPreparedEvent,如下:

EnvironmentPostProcessor有很多实现类,如下:

找到几个EnvironmentPostProcessor的实现类,这里选择SpringApplicationJsonEnvironmentPostProcessor类,这个类中,实现了两个接口,EnvironmentPostProcessor接口是扩展,Ordered接口是排序,如下:

3.4.2 onApplicationEnvironmentPreparedEvent()
关于onApplicationEvent的第一个方法onApplicationEnvironmentPreparedEvent()方法,如下:

再次进入到postProcessEnvironment方法,如下:

好了,整理一下调用关系,如下:

继续进入到addPropertySources方法,如下:

我们注意到,在ConfigFileApplicationListener类中,里面有三个重要的类变量,DEFAULT_PROPERTIES默认属性,DEFAULT_SEARCH_LOCATIONS默认扫描位置(开发者的文件应该存放的路径位置),DEFAULT_NAMES默认名称(开发者的文件的默认名称),如下:

在addPropertySources方法中,三步走,先添加,然后新建一个Loader,最后执行load方法,如下:

第一步,先添加,进入RandomValuePropertySource.addToEnvironment方法,如下:

3.4.3 RandomValuePropertySource类
我们先来认识一下这个RandomValuePropertySource类,类上的注释就告诉我们怎么配置,如下:

类中有两个常量,默认随机属性名称为random,前缀为random.,如下:

getProperty方法:当实参name前缀不为random.,直接返回为null;当实参name的前缀为random.,调用getRandomValue方法返回一个随机值。
getRandomValue方法:根据实参name取前缀长度的字符串匹配,返回具体的随机值,

好了,我们按照RandomValuePropertySource类的配置,自动动手试一试,配置一个name为randomLong,值为random.long,底层是由RandomValuePropertySource类的getRandomValue方法生成的,配置如下:

主代码中通过@Value或者Environment的bean实例取出属性值,这里使用Environment的bean实例,如下:

运行,真的取出来了

刷新一次又变了,每一次都会生成一个随机long,哈哈

好了,玩够了,回到ConfigFileApplicationListener类的addPropertySources方法:添加,新建Loader,执行load方法。

进入到addToEnvironment()方法,这里的addAfter就是在后面添加随机属性。

env如图:

回到ConfigFileApplicationListener类,查看new Loader(environment, resourceLoader).load();
Loader是资源文件加载器

这个Loader类的构造方法就是设置四个类属性,如下:

看到第四句,如下:
this.propertySourceLoaders SpringFactoriesLoader.
loadFactoriesPropertySourceLoader.class,getClass.getClassLoader
先看到这个SpringFactoriesLoader.loadFactories方法,这个方法就是加载spring.factories配置文件中的数据,


再看PropertySourceLoader接口本身,这个接口仅包含两个方法,获取文件后缀名方法和加载方法。

看到load方法

进入FilteredPropertySource.apply()方法,如下:

FilteredPropertySource.java
static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
Consumer<PropertySource<?>> operation) {
// 取出env实例bean中的属性源,放到可变属性源propertySources中
MutablePropertySources propertySources = environment.getPropertySources();
// 在可变属性源propertySources中,取得指定propertySourceName
PropertySource<?> original = propertySources.get(propertySourceName);
if (original == null) {
operation.accept(null); // 如果取出为null,执行operation.accept
return;
}
// 如果取出不为null,即当前存在propertySourceName,新建一个过滤属性源替换当前的propertySourceName
propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
try {
operation.accept(original);
}
finally {
propertySources.replace(propertySourceName, original);
}
}




回到load方法,看到initializeProfiles方法

进入到initializeProfiles方法,该方法初始化profiles属性,逻辑如下:

进入到getOtherActiveProfiles方法,如下:

经历了Loader构造函数,有了Loader对象,看一下load方法,如下:


在进入load方法,如下:

debug执行,如下:


进入到loadForFileExtension方法,如下:






3.X 尾声
Spring Environment全解析,完成了。
Y 推荐文献
- [Java SE] Java-文件系统-常用文件路径的获取方法 - 博客园/千千寰宇 【推荐】
- [Java SE] 彻底搞懂Java程序的三大参数配置途径:系统变量与JVM参数(VM Option)/环境变量/启动程序参数args - 博客园/千千寰宇 【推荐】
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号