三十二、IoC 之深入分析 PropertyPlaceholderConfigurer

IoC 之深入分析 PropertyPlaceholderConfigurer

BeanFactoryPostProcessor 作用:在容器启动阶段,可以对解析好的 BeanDefinition 进行定制化处理,而其中 PropertyPlaceholderConfigurer 是其一个非常重要的应用,也是其子类

PropertyPlaceholderConfigurer 允许我们用 Properties 文件中的属性,来定义应用上下文(配置文件或者注解),即:我们可以在 XML 配置文件(或者其他方式,如注解方式)中使用占位符的方式来定义一些资源,并将这些占位符所代表的资源配置到 Properties 中,这样只需要对 Properties 文件进行修改即可。

PropertyPlaceholderConfigurer

该类间接实现了 Aware 和 BeanFactoryPostProcessor 两大扩展接口,之前介绍BeanFactoryPostProcessor,知道了BeanFactoryPostProcessor 提供了 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 接口方法,在这个体系中该方法的是在 PropertyResourceConfigurer 中实现,该类为属性资源的配置类,它实现了 BeanFactoryPostProcessor 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// <1> 返回合并的 Properties 实例
Properties mergedProps = mergeProperties();

// Convert the merged properties, if necessary.
// <2> 转换合并属性
convertProperties(mergedProps);

// Let the subclass process the properties.
// <3> 让子类处理属性
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
  • <1> 处,调用 mergeProperties() 方法,返回合并的 Properties 实例。Properties 实例维护着一组 key-value ,其实就是 Properties 配置文件中的内容。
  • <2> 处,调用 convertProperties(Properties props) 方法,转换合并的值,其实就是将原始值替换为真正的值。
  • <3> 处,调用 processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) 方法,前面两个步骤已经将配置文件中的值进行了处理,那么该方法就是真正的替换过程,该方法由子类实现
1
2
3
// PropertyResourceConfigurer.java

protected abstract void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException;

注:PropertyPlaceholderConfigurer继承了PlaceholderConfigurerSupportPlaceholderConfigurerSupport继承了PropertyResourceConfigurer

PropertyPlaceholderConfigurer

在 PropertyPlaceholderConfigurer 中,重写 processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) 方法:

1
2
3
4
5
6
7
8
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {

// <1> 创建 StringValueResolver 对象,PlaceholderResolvingStringValueResolver是个内部类
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
// <2> 处理
doProcessProperties(beanFactoryToProcess, valueResolver);
}
PlaceholderResolvingStringValueResolver

首先,构造一个 PlaceholderResolvingStringValueResolver 类型的 StringValueResolver 实例,该类是个内部类。StringValueResolver 为一个解析 String 类型值的策略接口,该接口提供了 resolveStringValue(String strVal) 方法,用于解析 String 值。PlaceholderResolvingStringValueResolver 为其一个解析策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PropertyPlaceholderConfigurer.java

private class PlaceholderResolvingStringValueResolver implements StringValueResolver {

private final PropertyPlaceholderHelper helper;

private final PlaceholderResolver resolver;

public PlaceholderResolvingStringValueResolver(Properties props) {
this.helper = new PropertyPlaceholderHelper(
placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
this.resolver = new PropertyPlaceholderConfigurerResolver(props);
}

// ... 省略 resolveStringValue 方法
}
  • 在构造 String 值解析器 StringValueResolver 时,将已经解析的 Properties 实例对象封装在 PlaceholderResolver 实例 resolver 中。PlaceholderResolver 是一个用于解析字符串中包含占位符的替换值的策略接口,该接口有一个 resolvePlaceholder(String strVa) 方法,用于返回占位符的替换值。
  • 还有一个 PropertyPlaceholderHelper 工具 helper ,从名字上面看应该是进行替换的工具类。
doProcessProperties()方法

得到 String 解析器的实例 valueResolver 后,则会调用 doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, StringValueResolver valueResolver) 方法,来进行真值的替换操作。该方法在父类 PlaceholderConfigurerSupport 中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// PlaceholderConfigurerSupport.java
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {

// <1> 创建 BeanDefinitionVisitor 对象
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

// <2> 得到该容器的所有 BeanName
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// 校验
//检查我们是否没有解析我们自己的bean定义,以避免在属性文件位置中无法解析的占位符失败。
if (!(curName.equals(this.beanName)// 1. 当前实例 PlaceholderConfigurerSupport 不在解析范围内
&& beanFactoryToProcess.equals(this.beanFactory))) {// 2. 同一个 Spring 容器
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}

// <3> Spring 2.5的新功能:解析别名目标名称和别名中的占位符。
beanFactoryToProcess.resolveAliases(valueResolver);

// <4> Spring 3.0的新功能:解决嵌入值(例如注释属性)中的占位符。
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
  • <1> 处,根据 String 值解析策略 valueResolver 得到 BeanDefinitionVisitor 实例。BeanDefinitionVisitor 是 BeanDefinition 的访问者,我们通过它可以实现对 BeanDefinition 内容的进行访问,内容很多,例如 Scope、PropertyValues、FactoryMethodName 等等。
  • <2> 处,得到该容器的所有 BeanName,然后对其进行访问( visitBeanDefinition(BeanDefinition beanDefinition) 方法)。
  • <3> 处,解析别名的占位符。
  • <4> 处,解析嵌入值的占位符,例如注释属性。
visitBeanDefinition()方法

doProcessProperties()核心在于 visitBeanDefinition(BeanDefinition beanDefinition) 方法的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//BeanDefinitionVisitor.java
/**
*遍历给定的BeanDefinition对象以及其中包含的MutablePropertyValues和ConstructorArgumentValues。
*/
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
if (beanDefinition.hasPropertyValues()) {
//本篇重点
visitPropertyValues(beanDefinition.getPropertyValues());
}
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}

该方法基本访问了 BeanDefinition 中所有比较有用的东西了,包括 parent 、class 、factory-bean 、factory-method 、scope 、property 、constructor-arg 。

visitPropertyValues()方法

因为该篇主要内容是property,所以重点只看visitPropertyValues()方法。

过程就是对属性数组进行遍历,调用 resolveValue(Object value)方法,对属性进行解析获取最新值,如果新值和旧值不等,则用新值替换旧值。:

1
2
3
4
5
6
7
8
9
10
11
12
13
//BeanDefinitionVisitor.java
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
// 遍历 PropertyValue 数组
for (PropertyValue pv : pvArray) {
// 解析真值
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
// 设置到 PropertyValue 中
pvs.add(pv.getName(), newVal);
}
}
}

resolveValue()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//BeanDefinitionVisitor.java
@Nullable
protected Object resolveValue(@Nullable Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (newBeanName == null) {
return null;
}
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (newBeanName == null) {
return null;
}
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
// 由于 Properties 中的是 String,所以重点在此处
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}

resolveStringValue()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 解决给定的String值,例如解析占位符。
*/
@Nullable
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver object into the constructor or override the 'resolveStringValue' method");
}
// 解析真值
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}

valueResolver 是我们在构造 BeanDefinitionVisitor 实例时传入的 String 类型解析器 PlaceholderResolvingStringValueResolver,调用其 resolveStringValue(String strVal) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// PropertyPlaceholderConfigurer.java
// 内部类 PlaceholderResolvingStringValueResolver.java
public PlaceholderResolvingStringValueResolver(Properties props) {
this.helper = new PropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
this.resolver = new PropertyPlaceholderConfigurerResolver(props);
}

@Override
@Nullable
public String resolveStringValue(String strVal) throws BeansException {
// 解析真值
String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
if (trimValues) {
resolved = resolved.trim();
}
// 返回真值
return (resolved.equals(nullValue) ? null : resolved);
}

helper 为 PropertyPlaceholderHelper 实例对象,而 PropertyPlaceholderHelper 则是处理应用程序中包含占位符的字符串工具类。在构造 helper 实例对象时需要传入了几个参数:placeholderPrefixplaceholderSuffixvalueSeparator,这些值在 PlaceholderConfigurerSupport 中定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PlaceholderConfigurerSupport.java

/** Default placeholder prefix: {@value}. */
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/** Default placeholder suffix: {@value}. */
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
/** Default value separator: {@value}. */
public static final String DEFAULT_VALUE_SEPARATOR = ":";

/** Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}. */
protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
/** Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX}. */
protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
/** Defaults to {@value #DEFAULT_VALUE_SEPARATOR}. */
@Nullable
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;

replacePlaceholders()方法

调用 PropertyPlaceholderHelper 的 replacePlaceholders(String value, PlaceholderResolver placeholderResolver) 方法,进行占位符替换:

1
2
3
4
5
// PropertyPlaceholderHelper.java
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}

调用 parseStringValue(String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) 方法,该方法是本篇最核心的地方${} 占位符的替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

// 获取前缀 "${" 的索引位置
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}

StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
// 获取 后缀 "}" 的索引位置
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 截取 "${" 和 "}" 中间的内容,这也就是我们在配置文件中对应的值
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 解析占位符键中包含的占位符,真正的值
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 从 Properties 中获取 placeHolder 对应的值 propVal
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 如果不存在
if (propVal == null && this.valueSeparator != null) {
// 查询 : 的位置
int separatorIndex = placeholder.indexOf(this.valueSeparator);
// 如果存在 :
if (separatorIndex != -1) {
// 获取 : 前面部分 actualPlaceholder
String actualPlaceholder = placeholder.substring(0, separatorIndex);
// 获取 : 后面部分 defaultValue
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
// 从 Properties 中获取 actualPlaceholder 对应的值
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
// 如果不存在 则返回 defaultValue
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 递归调用,解析先前解析的占位符值中包含的占位符。
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// 忽略值
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;
}
}
// 返回propVal,就是替换之后的值
return result.toString();
}
  1. 获取占位符前缀 "${" 的索引位置 startIndex
  2. 如果前缀 "${" 存在,则从 “{” 后面开始获取占位符后缀 “}” 的索引位置 endIndex
  3. 如果前缀 “${” 和后缀 "}" 都存在,则截取中间部分 placeholder
  4. 从 Properties 中获取 placeHolder 对应的值 propVal
  5. 如果 propVal 为空,则判断占位符中是否存在 ":",如果存在则对占位符进行分割处理,前面部分为 actualPlaceholder,后面部分 defaultValue,尝试从 Properties 中获取 actualPlaceholder 对应的值 propVal,如果不存在,则将 defaultValue 的值赋值给 propVal
  6. 返回 propVal,也就是 Properties 中对应的值。

三十二、IoC 之深入分析 PropertyPlaceholderConfigurer
http://www.muzili.ren/2022/06/11/IoC 之深入分析 PropertyPlaceholderConfigurer/
作者
jievhaha
发布于
2022年6月11日
许可协议