Spring PropertyResolver 占位符解析(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring 3.1 提供了新的占位符解析器 PropertyResolver,默认实现为 PropertySourcesPropertyResolver。相关文章如下:

  1. Spring PropertyResolver 占位符解析(一)API 介绍
  2. Spring PropertyResolver 占位符解析(二)源码分析

一、PropertyResolver 接口

PropertyResolver 的默认实现是 PropertySourcesPropertyResolver,Environment 实际上也是委托 PropertySourcesPropertyResolver 完成 占位符的解析和类型转换。 类型转换又是委托 ConversionService 完成的。

public interface PropertyResolver {
// 1. contains
boolean containsProperty(String key); // 2.1 获取指定 key,不存在可以指定默认值,也可以抛出异常
String getProperty(String key);
String getProperty(String key, String defaultValue); // 2.2 类型转换,委托 ConversionService 完成
<T> T getProperty(String key, Class<T> targetType);
<T> T getProperty(String key, Class<T> targetType, T defaultValue); String getRequiredProperty(String key) throws IllegalStateException;
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException; // 3. 解析占位符 ${key}
String resolvePlaceholders(String text);
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

ConfigurablePropertyResolver 是配置了一些解析占位符的必要属性,如占位符前缀和后缀等

public interface ConfigurablePropertyResolver extends PropertyResolver {
// 1. 类型转换
ConfigurableConversionService getConversionService();
void setConversionService(ConfigurableConversionService conversionService); // 2.1 ${} 分隔符
void setPlaceholderPrefix(String placeholderPrefix);
void setPlaceholderSuffix(String placeholderSuffix);
// 2.2 默认属性分隔符 :
void setValueSeparator(@Nullable String valueSeparator); // 3.1 ${key} getProperty(key)==null 时是否忽略,不抛出异常
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
void setRequiredProperties(String... requiredProperties);
void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

二、PropertySourcesPropertyResolver 源码分析

PropertySourcesPropertyResolver 持有一个数据源 PropertySources,可以通过 getProperty 获取对应的属性值,这方法有几种重载的方法,决定是否解析嵌套占位符和类型转换。

// 不进行类型转换,但会进行嵌套占位符的解析
@Override
public String getProperty(String key) {
return getProperty(key, String.class, true);
} // 进行类型转换,也进行嵌套占位符的解析
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
return getProperty(key, targetValueType, true);
} // 不进行类型转换,也不进行嵌套占位符的解析,返回原始的字符串
@Override
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}

我们再看一下 getProperty(key, String.class, false) 这个方法

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
// 1. 从数据源中获取属性值
Object value = propertySource.getProperty(key);
if (value != null) {
// 2. 如果属性值本身又含有占位符就属于嵌套占位符解析,如 ${a${x}b}
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
// 日志输出
logKeyFound(key, propertySource, value);
// 3. 类型转换
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}

现在最关键的方法是 resolveNestedPlaceholders,用于解析嵌套的占位符,这个方法是在其父类 AbstractPropertyResolver 实现的。

protected String resolveNestedPlaceholders(String value) {
return (this.ignoreUnresolvableNestedPlaceholders ?
resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
} private PropertyPlaceholderHelper nonStrictHelper;
private PropertyPlaceholderHelper strictHelper;
@Override
public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
}
return doResolvePlaceholders(text, this.nonStrictHelper);
}
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}

实际上嵌套占位符的解析 PropertySourcesPropertyResolver 都委托给了 PropertyPlaceholderHelper 方法来完成,而自身主要完成从 PropertySources 获取属性值。

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

三、PropertyPlaceholderHelper 嵌套占位符的解析

在看 PropertyPlaceholderHelper 之前先看一下 PlaceholderResolver 这个内部类,这个类用于获取占位符 key 对应的 value。在 AbstractPropertyResolver#doResolvePlaceholders 方法中将 this::getPropertyAsRawString 传过来了,也就是说 PlaceholderResolver 是从 propertySources 获取对应的 value 值。

@FunctionalInterface
public interface PlaceholderResolver {
// 从 propertySource 中获取 placeholderName 的 value
String resolvePlaceholder(String placeholderName);
}

下面再看 replacePlaceholders 是如何解析嵌套的占位符的。

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
// placeholderResolver 是从 propertySources 获取的属性值
return parseStringValue(value, placeholderResolver, new HashSet<>());
} // 循环解析 key ${a${x}b}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
// 找到结束的 } 位置,注意嵌套时要找对应的结束标记符 ${a${x}b}
int endIndex = findPlaceholderEndIndex(result, startIndex);
// endIndex=-1 或 startIndex=-1 结束循环
if (endIndex != -1) {
// 1. 获取 ${key} 的 key
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
// 2. key 出现了循环嵌套,直接 Game Over
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 2. 循环解析这个 key,如果这个 key 又是形如 ${...}
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 3. 至此,这个 key 不可能出现 ${} 了,因此可以放心大胆的从 propertySources 获取对应的 value
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 4. ${key:default} 如果为 null,获取真正的 key,如果为 null 则为默认值
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
// 5. 对不起,value 也可能为 ${...},递归解析
if (propVal != null) {
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
} else if (this.ignoreUnresolvablePlaceholders) {
// 6. 忽略无法解析的 key,继续...
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
} else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
} else {
startIndex = -1;
}
} return result.toString();
}

每天用心记录一点点。内容也许不重要,但习惯很重要!

Spring PropertyResolver 占位符解析(二)源码分析的更多相关文章

  1. Spring PropertyResolver 占位符解析(一)API 介绍

    Spring PropertyResolver 占位符解析(一)API 介绍 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html ...

  2. Spring注解之@Lazy注解,源码分析和总结

    一 关于延迟加载的问题,有次和大神讨论他会不会直接或间接影响其他类.spring的好处就是文档都在代码里,网上百度大多是无用功. 不如,直接看源码.所以把当时源码分析的思路丢上来一波. 二 源码分析 ...

  3. Spring第四天,BeanPostProcessor源码分析,彻底搞懂IOC注入及注解优先级问题!

  4. 涨姿势:Spring Boot 2.x 启动全过程源码分析

    目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...

  5. Spring Boot 2.x 启动全过程源码分析

    Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...

  6. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo | grep tid | grep -v "daemon" tid ...

  7. Spring系列28:@Transactional事务源码分析

    本文内容 @Transactional事务使用 @EnableTransactionManagement 详解 @Transactional事务属性的解析 TransactionInterceptor ...

  8. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boot 为什么这么简便的奥秘. 本篇基于 Spring Boot 2.0.3 版本进 ...

  9. django 之(二) --- 源码分析

    CBV类视图继承 CBV:继承自View:注册的时候使用的as_view() 入口 不能使用请求方法的名字作为参数的名字 只能接受已经存在的属性对应的参数 定义了一个view 创建了一个类视图对象 保 ...

随机推荐

  1. webpack-manifest-plugin

    [webpack-manifest-plugin] Webpack plugin for generating an asset manifest. This will generate a mani ...

  2. Brainwashing

    [Brainwashing] 1.教育和媒体都是“国家意识机器”,他们维持并复制国家领导者的思想. 2.洗脑.情感.意识. 3.洗脑所产生的各种影响具有如下特征:全用暴力,或者进行欺骗,或二者兼有. ...

  3. 新版本PHP使用更方便了

    $hostname_conn = "";$database_conn = "";$username_conn = "root";$passw ...

  4. Docker容器进入-命令行只显示-bash-4.1#

    bash-4.1# cp /etc/skel/.bash* /root/ bash-4.1# su [root@4a841f025562 ~]# [root@4a841f025562 ~]# [roo ...

  5. CentOS 7安装Zabbix 3.4

    01.最小化安装操作系统 02.升级系统组件到最新版本 yum -y update 03.关闭 SELinux sed -i “s/SELINUX=enforcing/SELINUX=disabled ...

  6. C# 页面向controller中跳转匹配方法的时候,当controller中有两个重载方法时候,不发生跳转

    在ajax中的URL跳向controller一个方法时候,controller中有两个重载的方法,ajax不发生跳转,当删除另外一个方法之后,正常跳转. 不知道,是我自己写的有问题,还是control ...

  7. HDU 3691 Nubulsa Expo(全局最小割)

    Problem DescriptionYou may not hear about Nubulsa, an island country on the Pacific Ocean. Nubulsa i ...

  8. 理解HTTP之keep-alive(转)

    理解HTTP之keep-alive 在前面一篇文章中讲了TCP的keepalive,这篇文章再讲讲HTTP层面keep-alive.两种keepalive在拼写上面就是不一样的,只是发音一样,于是乎大 ...

  9. 阿里读写分离数据源SELECT LAST_INSERT_ID()获取不到id

    异常现象 insert 通过 mybatis 以下语法给领域类 赋予的 id 值为0. 后续根据主键的update操作失效.且无异常抛出 <selectKey keyProperty=" ...

  10. CentOS 6、7 安装 Golang

    方法一:使用二进制文件安装 (推荐) 1.下载二进制文件: wget https://storage.googleapis.com/golang/go1.7.3.linux-amd64.tar.gz ...