Spring 运行环境之 Environment
Spring 运行环境之 Environment
1、简介
Spring Framework 3.1 开始引入 Environment 抽象,它统一 Spring 配置属性的存储,包括占位符处理和类型转换,不仅完整地替换 PropertyPlaceholderConfigurer,而且还支持更丰富的配置属性源(PropertySource)。
Environment 接口使用场景:
- ⽤于属性占位符处理。
- 用于转换 Spring 配置属性类型。
- 用于存储 Spring 配置属性源(PropertySource)。
- 用于 Profiles 状态的维护。
2、Environment 的标准实现类图:
3、Environment 分析
3.1、Environment 接口
// 它继承自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);
}
3.2、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
3.3、ConfigurablePropertyResolver
顾名思义,它是一个可配置的处理器。这个方法不仅有父接口所有功能,还扩展定义类型转换、属性校验、前缀、后缀、分隔符
等一些列的功能,这个在具体实现类里有所体现~
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框架设计的一般规律之一
3.4、AbstractPropertyResolver
它是对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
身上,这个类不可小觑,是一个与业务无关非常强大的工具类,我们可以直接拿来使用~
3.5、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) { ... }
}
3.6、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;
}
...
}
3.7、ConfigurableEnvironment
扩展出了修改
和配置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);
}
3.8、AbstractEnvironment(环境的抽象):
该抽象类完成了对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`
...
}
3.9、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()));
}
}
4、运用
在 spring 启动过程已经对 Environment 进行初始化了,那么我们是如何使用该对象的? 我们可以通过实现接口EnvironmentAware、EnvironmentCapable
或者直接@Autowired
可以很方便的得到当前应用的环境:Environment
。实现了此接口的类都应该有一个Environment类型的环境,并且可以通过getEnvironment
方法取得。 我们熟知的所有的Spring应用上下文都实现了这个接口,因为ApplictionContext
就实现了这个接口,表示每个应用上下文都是有自己的运行时环境的。
4.1、 EnvironmentAware
在类中通过实现该接口,spring 容器会自动帮我们注入 Environment 对象;这样子我们就可以拿到这个对象进行我们想要的操作;
4.2、 StringValueResolver
StringValueResolver
本身,Spring对它的定义为:一个处理字符串的简单策略接口。
// @since 2.5 该接口非常简单,就是个函数式接口~
@FunctionalInterface
public interface StringValueResolver {
@Nullable
String resolveStringValue(String strVal);
}
唯一public
实现类为:EmbeddedValueResolver
4.3、EmbeddedValueResolver
帮助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) {
// 先使用Bean工厂处理占位符resolveEmbeddedValue
String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
// 再使用el表达式参与计算~~~~
if (this.exprResolver != null && value != null) {
Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
value = (evaluated != null ? evaluated.toString() : null);
}
return value;
}
}
关于Bean工厂resolveEmbeddedValue
的实现,我们这里也顺带看看:
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
...
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
// embeddedValueResolvers是个复数:因为我们可以自定义处理器添加到bean工厂来,增强它的能力
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
// 只要处理结果不为null,所以的处理器都会执行到~~~~
if (result == null) {
return null;
}
}
return result;
}
...
}
而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());
}
}
5、总结
Environment
作为一个上层应用接口,代表着Spring应用运行环境属性信息,可以说还是非常的重要的。毕竟平时开发中,我们也不乏少用~