1:PropertyResolver
在底层数据源之上定义一系列属性的操作,这些数据源可以是yaml,properties,甚至是nosql(如redis,也是基于key-value结构的),来看下源码:
// 基于任何的底层数据源来解析属性值
public interface PropertyResolver {
// 返回是否包含key,不包含返回false,包含但是值为null,也是false
boolean containsProperty(String key);
// 获取key对应的属性值,如果无法解析,返回null
@Nullable
String getProperty(String key);
// 返回key对应的属性值,如果无法解析则返回默认值defaultValue
String getProperty(String key, String defaultValue);
// 获取key对应的属性值,类型是Class<T> targetType中的T,如果是
// 不存在则返回null
@Nullable
<T> T getProperty(String key, Class<T> targetType);
// 获取key对应的属性值,类型是Class<T> targetType中的T,如果是
// 不存在则返回T defaultValue
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 获取key对应的属性值,如果是不存在则抛出IllegalStateException
String getRequiredProperty(String key) throws IllegalStateException;
// 获取key对应的属性值,并指定类型,如果是不存在则抛出IllegalStateException
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 解析${...}占位符,如text为${spring.profiles.active},当spring.profiles.active=dev
// 则结果就是dev,如果是不包含则结果不变,依然是${spring.profiles.active}
String resolvePlaceholders(String text);
// 解析${...}占位符,如text为${spring.profiles.active},当spring.profiles.active=dev
// 则结果就是dev,如果是不包含则抛出IllegalArgumentException
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
2:ConfigurablePropertyResolver
这是PropertyResolver的子类,从类名中的Configurable
可以看出这是可配置的,比如配置属性转换使用的ConversionService
,配置占位符的前缀后缀等,下面来看下源码:
// 提供了配置ConversionService,占位符前后缀,分隔符等方法的接口
public interface ConfigurablePropertyResolver extends PropertyResolver {
// 返回执行属性转换的ConfigurableConversionService,可以add或者是remove自定义的Converter
ConfigurableConversionService getConversionService();
// 设置自己的ConfigurableConversionService,不常用,一般使用内部提供的,如果需要使用自定义的
// Converter可以通过addConverter方法添加
void setConversionService(ConfigurableConversionService conversionService);
// 设置占位符的前缀
void setPlaceholderPrefix(String placeholderPrefix);
// 设置占位符的后缀
void setPlaceholderSuffix(String placeholderSuffix);
// 设置占位符的值和默认值之间的分割符,一般默认是“:”,当然也可以设置为其他
void setValueSeparator(@Nullable String valueSeparator);
// 当遇到解析不了的占位符时,是否抛出异常,为false时代表使用严格模式,抛出异常,为true时代表
// 不使用严格模式,直接返回传入的${...},getProperty方法以及其变种方法必须检测这里的
// 的值,以便在遇到无法解析的占位符时知道是抛出异常还是直接原样返回
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
// 设置必须提供的属性,可以通过validateRequiredProperties方法来进行验证
void setRequiredProperties(String... requiredProperties);
// 验证通过setRequiredProperties方法设置必须提供的属性是否都提供了,如果是没有提供
// 或者是提供的值是null,则抛出MissingRequiredPropertiesException
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}
接下来我们来看该接口的抽象子类org.springframework.core.env.AbstractPropertyResolver
,提供了基于任何底层数据源来解析属性的抽象基类,我们继续来这个类。
3:AbstractPropertyResolver
该抽象基类仅仅是定义了解析文件所需要的配置,如占位符的前后缀,ConvensionService等。
3.1:主要属性
// ConversionService
@Nullable
private volatile ConfigurableConversionService conversionService;
// 执行属性占位符替换的帮助类
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;
@Nullable
private PropertyPlaceholderHelper strictHelper;
private boolean ignoreUnresolvableNestedPlaceholders = false;
// 占位符前缀
// public static final String PLACEHOLDER_PREFIX = "${";
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
// 占位符后缀
// public static final String PLACEHOLDER_SUFFIX = "}";
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
// 分隔符
// public static final String VALUE_SEPARATOR = ":";
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
// 必须存在的属性
private final Set<String> requiredProperties = new LinkedHashSet<>();
3.2:主要方法
// 获取ConversionService
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;
}
// 设置ConversionService
@Override
public void setConversionService(ConfigurableConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
// 设置占位符前缀默认是"${"
@Override
public void setPlaceholderPrefix(String placeholderPrefix) {
Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
this.placeholderPrefix = placeholderPrefix;
}
// 设置占位符后缀,默认是"}"
@Override
public void setPlaceholderSuffix(String placeholderSuffix) {
Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
this.placeholderSuffix = placeholderSuffix;
}
// 设置待替换占位符和默认值之间的占位符,默认是":"
@Override
public void setValueSeparator(@Nullable String valueSeparator) {
this.valueSeparator = valueSeparator;
}
// 设置是否强制要求占位符值必须存在
@Override
public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
}
// 设置必须存在的属性
@Override
public void setRequiredProperties(String... requiredProperties) {
for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
}
具体的获取属性的操作子类org.springframework.core.env.PropertySourcesPropertyResolver
中,接下来看下该类。
4:PropertySourcesPropertyResolver
该类中有一个重要的属性private final PropertySources propertySources;
该类维护了一组PropertySource
,我们先来看下该类:
// 维护一个或者是多个PropertySource的接口,PropertySource代表一个数据源
public interface PropertySources extends Iterable<PropertySource<?>> {
default Stream<PropertySource<?>> stream() {
return StreamSupport.stream(spliterator(), false);
}
// 是否包含指定名称的PropertySource
boolean contains(String name);
// 根据名称获取PropertySource
@Nullable
PropertySource<?> get(String name);
}
再来看下PropertySource
接口,源码如下:
// 泛型T代表数据源类型,可以是java.util.Properties,也可以是java.util.HashMap,也可以是ServletContext,ServletConfig等
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
// 属性源的名称
protected final String name;
// 属性源
protected final T source;
// 通过给定的属性源名称和属性源创建PropertySource实例
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
/**
* Create a new {@code PropertySource} with the given name and with a new
* {@code Object} instance as the underlying source.
* <p>Often useful in testing scenarios when creating anonymous implementations
* that never query an actual source but rather return hard-coded values.
*/
// 使用给定的名称,并使用Object作为底层属性源创建PropertySource实例,一般用于测试场景中,
// 比如直接硬编码一些值作为返回值
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
// 返回本PropertySource的名称
public String getName() {
return this.name;
}
// 返回本PropertySource的底层数据源
public T getSource() {
return this.source;
}
// 返回PropertySource是否包含指定name的数据,子类可酌情重载具体实现
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
// 返回和给定的name关联的value
@Nullable
public abstract Object getProperty(String name);
}
所有的getProperty方法都是委托给getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders)
这个三个参数的,所以我们重点来看下这个方法。
4.1:getProperty
org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
// 存在propertySources才继续,否则直接返回null
if (this.propertySources != null) {
// 循环遍历PropertySources
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
// 调用当前propertySource的getProperty方法获取值
Object value = propertySource.getProperty(key);
// 如果值不为空
if (value != null) {
// 如果是需要解析占位符,并且值的类型是String,则解析占位符
if (resolveNestedPlaceholders && value instanceof String) {
// <20210428 1754>
// 解析占位符
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// <20210428 1755>
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
// 没有propertySources或者是没有从propertySources中获取到值,则这里返回null
return null;
}
<20210428 1754>
处是解析占位符,具体参考4.2:resolveNestedPlaceholders
,<20210428 1755>
处是根据要求的目标类型对值进行类型转换,具体参考4.3:convertValueIfNecessary
。
4.2:resolveNestedPlaceholders
org.springframework.core.env.AbstractPropertyResolver#resolveNestedPlaceholders
protected String resolveNestedPlaceholders(String value) {
// 根据属性不存在时是否抛出异常的布尔变量ignoreUnresolvableNestedPlaceholders
// 决定调用resolvePlaceholders方法还是resolveRequiredPlaceholders方法,但是
// 最终都会调用doResolvePlaceholders
return (this.ignoreUnresolvableNestedPlaceholders ?
resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}
继续看doResolvePlaceholders
:
org.springframework.core.env.AbstractPropertyResolver#doResolvePlaceholders
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
// <20210428 1812>
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
<20210428 1812>
处是通过属性替换帮助类来完成占位符的替换,如配置的server.port=8888
,则${server.port}
最终结果就是8888
,关于PropertyPlaceholderHelper,可以参考这里和这里。
4.3:convertValueIfNecessary
org.springframework.core.env.AbstractPropertyResolver#convertValueIfNecessary
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
// 如果是没有要求的目标类型,则直接强转返回
if (targetType == null) {
return (T) value;
}
ConversionService conversionServiceToUse = this.conversionService;
if (conversionServiceToUse == null) {
// 如果是满足强转的条件,则直接强转返回
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
conversionServiceToUse = DefaultConversionService.getSharedInstance();
}
// 使用ConversionService的类型转换体系执行转换
return conversionServiceToUse.convert(value, targetType);
}
5:简单总结
上面主要讲解了PropertyResolver,以及其实现类,PropertyResolver的作用就是从数据源中根据指定的key获取值,并在必要的条件下执行属性类型的转换,这里数据源来自PropertySource的API,类型转换来自ConversionService的API,因此可以用下图来说明这几者之间的关系:
6:Environment
全限定名称是org.springframework.core.env.Environment
,是对应用程序运行环境的抽象的接口,表示当前程序正在运行的环境。在应用程序运行环境中首先由很多的配置文件,我们可以称之为properties
,但是这些properties并非都是同时生效的,到底哪些生效通过叫做profile
的配置项确定,而properties和profile
也正是Environment的主要内容,总结如下:
properties:代表所有的配置信息,通过ProertyResolver来承载。
profile:决定properties中哪些配置项加载生效。
另外,如JVM系统属性,系统环境变量,servlet上下文参数,也都包含在Properties,所以一切和属性配置相关的获取等操作都可以通过Environment来完成,来看下接口源码:
// 代表当前正在运行的应用程序的环境的接口。为应用程序环境中的"profiles"和"properties"两个重要的
// 方面建立了模型。属性相关的访问方法通过ProeprtyResolver接口来提供。
public interface Environment extends PropertyResolver {
// 返回environment中明确设置激活的profiles的集合。profiles是用来对bean定义进行逻辑分组,
// 只有激活的profiels的分组的bean定义才会被进一步初始化为bean。Profiles可以通过
// AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME"spring.profiles.active"作为
// 系统属性值设置,或者是通过调用ConfigurableEnvironment#setActiveProfiles()方法设置。
// 如果是没有明确激活的profiles设置,那么方法getDefaultProfiles()方法的返回值将会自动
// 激活
String[] getActiveProfiles();
// 返回当没有明确的profiles设置时,默认激活的一组profiles
String[] getDefaultProfiles();
// 返回传入的profiles是否包含在明确设置的激活profiles,如果是没有明确设置激活的,则通过
// 默认激活的来判断,这里多个profile是或的关系,一个在,则返回true,取反的话可以通过"!"
@Deprecated
boolean acceptsProfiles(String... profiles);
// 返回getActiveProfiles是否匹配给定的Profiles
boolean acceptsProfiles(Profiles profiles);
}
接着来看下Environment的类图:
7:ConfigurableEnvironment
可配置的Environment接口类,提供了针对激活的profiles,默认的profiles的设置的能力,以及操作Properties的能力。源码如下:
// 提供了设置激活的profiles和默认激活的profiles,操纵底层的proerptysource的机制。允许
// 用户设置和验证必须的属性,定制conversionservice,也可以基于ConfigurablePropertyResolver进行
// 其他的设置
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// 设置Environment的激活的profiles集合。profiles用来在容器的启动期间决定是否将bean 定义注册到
// 容器中。注意该方法是覆盖的方式来设置的,另外如果传入0个参数,则清空当前,想要添加新的profile
// 可以通过调用addActiveProfile方法来完成
void setActiveProfiles(String... profiles);
// 添加信息的profile到当前的激活profiles集合中
void addActiveProfile(String profile);
// 设置默认激活的profiles,当没有通过方法setActiveProfiles明确设置激活的profiles时
// 这里设置的默认的profiles生效
void setDefaultProfiles(String... profiles);
// 以可修改的方式返回当前Enviroment的PropertySources,允许操作当获取属性时用来搜索的PropertySource
// 操作常用的API,如addFirst,addLast,addBefore,addAfter,可以根据实际情况的属性获取
// 的优先级来选择合适的API添加PropertySource
MutablePropertySources getPropertySources();
// 返回通过System.getProperties方法返回的map。大部分的Environment的实现都会将system properties
// 作为一个默认的PropertySource来搜索,因此,该方法不建议直接使用,而是直接通过Environment
// 来直接获取属性
Map<String, Object> getSystemProperties();
// 获取System.getEnv的结果
Map<String, Object> getSystemEnvironment();
// 追加parent的active profiels,default profiles,以及property sources到当前的(子)Environment中
// 对于重名的PropertySource,保留子的,废弃父的,即保留当前类的
void merge(ConfigurableEnvironment parent);
}
接下来看抽象实现类org.springframework.core.env.AbstractEnvironment
。
8:AbstractEnvironment
8.1:properties相关
定义了钩子方法org.springframework.core.env.AbstractEnvironment#customizePropertySources
,该方法是个空实现,子类,可以通过重载该方法,设置自定义的属性源PropertySource,源码如下:
// 创建Environment实例,会通过回调customizePropertySources的方法
// ,因此子类可以复写该方法来设置自定义的属性源
public AbstractEnvironment() {
// 回调钩子方法customizePropertySources
customizePropertySources(this.propertySources);
}
// 设置通过getProperty(xxx)等方法搜索属性时,所查找的PropertySource的集合信息
protected void customizePropertySources(MutablePropertySources propertySources) {
}
获取可修改数据源方法:
org.springframework.core.env.AbstractEnvironment#getPropertySources
@Override
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
获取后就可以通过org.springframework.core.env.MutablePropertySources
的addXxx
的相关API来进行属性源的动态添加了。
8.2:profile相关
// 定义设计激活profiles的key
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
// 激活的profiles的集合
private final Set<String> activeProfiles = new LinkedHashSet<>();
// 定义默认激活的profiles的key
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
// 默认激活的profiles的集合
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
获取profiles:
org.springframework.core.env.AbstractEnvironment#getActiveProfiles
@Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
}
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
// 为空
if (this.activeProfiles.isEmpty()) {
// 从属性源中获取属性名称为ACTIVE_PROFILES_PROPERTY_NAME
// 的激活profiles(,分割)
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
// 如果是在属性源中设置了
if (StringUtils.hasText(profiles)) {
// 去除空格,并转换为数组,设置到激活的profiles中
// 这里的代码说明,我们只需要设置到环境中如"-Dspring.profiles.active=aa,bb"
// 这里就会自动set了
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
设置profile:
org.springframework.core.env.AbstractEnvironment#setActiveProfiles
public void setActiveProfiles(String... profiles) {
Assert.notNull(profiles, "Profile array must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Activating profiles " + Arrays.asList(profiles));
}
synchronized (this.activeProfiles) {
// 清空已存在的
this.activeProfiles.clear();
// 新的
for (String profile : profiles) {
// 校验,主要校验不为空,不以!开头,子类
// 可以提供更加强大和全面的校验
validateProfile(profile);
// 添加到集合中
this.activeProfiles.add(profile);
}
}
}