PropertyPlaceholderConfigurer源码阅读

PropertyPlaceholderConfigurer 用于加载指定的配置文件,替换 bean 配置中的占位符为指定的属性值。

用法

首先看下它的使用方法,以便有个直观的感受,顺便思考下它是怎么做到的,这样有目的性去看源码会有更好的效果。

在配置 PropertyPlaceholderConfigurer 时,一般通过 locations 属性指定多个 property 源文件,比如下面的示例

1
2
3
4
5
6
7
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>

它指定了一个配置文件:jdbc.properties,位于 classpath 下。该属性文件的内容如下:

1
2
3
4
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1/demos?useUnicode=true&amp;charterEncoding=utf8
jdbc.username=userName
jdbc.password=passWord

配置好这些之后,就可以通过占位符引用配置文件中的属性。例如在配置 bean 时,${jdbc.driverClassName} 会被替换成 com.mysql.jdbc.Driver

1
2
3
4
5
6
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

这样做的好处是将配置从代码中分离了出来,单独管理,提高了灵活性,换种方式说,就是将配置和代码解耦了。

源码阅读

接下来看下源码,看看 PropertyPlaceholderConfigurer 是如何实现上面提到的功能的:比如是如何读的配置文件、结果放在何处、在何时替换的 bean 配置等等。

首先还是看下类图,这样做的目的是对类的结构有一个整体的认识,以便在看到相关类时,不至于迷失在茫茫代码中。

image-20200429010532658

需要说明的是,为了避免干扰,我从图中删去了 Ordered 相关接口和 Aware 相关接口,但这并不影响源码的阅读和理解。

从上往下看,首先看到 BeanFactoryPostProcessor 接口,想必你已经很熟悉了,它提供了一个 postProcessBeanFactory 回调方法,可以对 ConfigurableListableBeanFactory 进行一些操作。

然后就是 PropertiesLoaderSupport 类

PropertiesLoaderSupport

PropertiesLoaderSupport 是一个抽象的基类,它用于从一个或多个 Resource 中加载 properties,另外还支持通过 api 设置本地 properties(local properties)。

loadProperties 是它的一个核心方法,用于加载 properties 到指定的实例中,它的内部是通过 PropertiesLoaderUtils 类的 fillProperties 方法实现单个 Resource 的加载的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
try {
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
}
catch (FileNotFoundException | UnknownHostException ex) {
if (this.ignoreResourceNotFound) {
if (logger.isDebugEnabled()) {
logger.debug("Properties resource not found: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
}

PropertiesLoaderUtils.fillProperties 则是通过指定的 PropertiesPersister 策略加载资源

1
2
3
4
5
6
7
8
9
10
11
12
13
String filename = resource.getResource().getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
stream = resource.getInputStream();
persister.loadFromXml(props, stream);
}
else if (resource.requiresReader()) {
reader = resource.getReader();
persister.load(props, reader);
}
else {
stream = resource.getInputStream();
persister.load(props, stream);
}

在 loadProperties 的基础上,mergeProperties 方法封装了生成最终 Properties 的策略,它主要是根据配置的覆盖策略对 localProperties 以及从 Resource 中加载出来的 Properties 进行合并处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();

if (this.localOverride) {
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}

if (this.localProperties != null) {
for (Properties localProp : this.localProperties) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}

if (!this.localOverride) {
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}

return result;
}

PropertyResourceConfigurer

再往下看到的是PropertyResourceConfigurer 类,它也是一个抽象类,主要封装了配置 bean 属性值的流程。为了能够对 beanFactory 中的所有 BeanDefinition 进行处理,PropertyResourceConfigurer 实现了 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法,这样在拿到 properties 之后就可以对 beanFactory 进行操作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();

// Convert the merged properties, if necessary.
convertProperties(mergedProps);

// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}

它的流程如下:

  1. 获取合并后的 Properties
  2. 必要情况下对 Properties 进行转换,比如对 value 进行一些特殊的处理
  3. 将属性值应用到 beanFactory 中,这个交给子类实现

PlaceholderConfigurerSupport

这也是一个抽象类,从名字上就可以看出,它用于对占位符进行处理。这里定义了几个常量,分别是占位符前缀、占位符后缀、属性值分隔符

1
2
3
4
5
6
7
8
/** 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 = ":";

PlaceholderConfigurerSupport 提供了一个模板方法:doProcessProperties,注意它不是 PropertyResourceConfigurer 中留给子类实现的 processProperties 方法。

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
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {

// 构建访问器
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);

String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
// 遍历beanFactory中的bean
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
// 访问BeanDefinition,解析占位符,并设置为解析后的结果
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}

// New in Spring 2.5: 解析alias中的占位符并注册
beanFactoryToProcess.resolveAliases(valueResolver);

// New in Spring 3.0: 解析嵌入的值,例如annotation属性
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}

它的主要思路是:遍历 beanFactory 中的 BeanDefinition,使用 BeanDefinitionVisitor 去访问每一个 BeanDefinition 进行处理。很明显 visitor.visitBeanDefinition(bd) 需要对 BeanDefinition 的每一个属性做处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void visitBeanDefinition(BeanDefinition beanDefinition) {
// parentName
visitParentName(beanDefinition);
// className
visitBeanClassName(beanDefinition);
// FactoryBeanName
visitFactoryBeanName(beanDefinition);
// FactoryMethodName
visitFactoryMethodName(beanDefinition);
// scope
visitScope(beanDefinition);
// PropertyValues
if (beanDefinition.hasPropertyValues()) {
visitPropertyValues(beanDefinition.getPropertyValues());
}
// constructorArgumentValues
if (beanDefinition.hasConstructorArgumentValues()) {
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
}

这里可以看到都有哪些配置支持占位符替换。以 parentName 的处理为例,看下是如何实现占位符替换的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected void visitParentName(BeanDefinition beanDefinition) {
String parentName = beanDefinition.getParentName();
if (parentName != null) {
// 解析占位符
String resolvedName = resolveStringValue(parentName);

// 不相等,则设置为解析后的结果
if (!parentName.equals(resolvedName)) {
beanDefinition.setParentName(resolvedName);
}
}
}

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);
}

这里的关键在于 resolveStringValue 方法,它将原始字符串解析为替换占位符后的值,它是通过 valueResolver 的 resolveStringValue 方法实现的。

PropertyPlaceholderConfigurer

PropertyPlaceholderConfigurer 是最终的实现类,它实现了 PropertyResourceConfigurer 的 processProperties 方法

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

// 构建解析器
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);

// 解析属性值
doProcessProperties(beanFactoryToProcess, valueResolver);
}

它主要职责是构建解析器,然后将解析工作交给父类 PlaceholderConfigurerSupport 的 doProcessProperties 方法来处理,因此我们只需关心解析器的构建。

PlaceholderResolvingStringValueResolver

它就是解析器,用于解析占位符为配置的属性值。该类有 PropertyPlaceholderHelper 和 PlaceholderResolver 两个属性

image-20200429015220357
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public PlaceholderResolvingStringValueResolver(Properties props) {
// 构建helper
this.helper = new PropertyPlaceholderHelper(
placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
// 构建 resolver
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();
}

// null值处理
return (resolved.equals(nullValue) ? null : resolved);
}
}

PropertyPlaceholderConfigurerResolver

它用于将解析出来的占位符转换为配置的属性值

1
2
3
4
public String resolvePlaceholder(String placeholderName) {
return PropertyPlaceholderConfigurer.this.resolvePlaceholder(placeholderName,
this.props, systemPropertiesMode);
}

转换操作就是从配置的 key-value 中查询属性值,根据 systemPropertiesMode 配置的不同,取数据的 properties 来源也有所不同

1
2
3
4
5
6
7
8
9
10
11
12
13
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}

PropertyPlaceholderHelper

它用来解析原始字符串,找到占位符并替换成对应属性值

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

StringBuilder result = new StringBuilder(value);

// 找前缀
int startIndex = value.indexOf(this.placeholderPrefix);

// 找到了前缀则继续
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.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}

// 里面可能还有前后缀组合,递归解析
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);

// Now obtain the value for the fully resolved key...
// 已经没有前后缀组合,开锁解析占位符,得到解析后的结果
String propVal = placeholderResolver.resolvePlaceholder(placeholder);

// 解析结果为空,并且分隔符不为空,尝试分割后解析
if (propVal == null && this.valueSeparator != null) {
// 分隔符的位置,例如 value:3 中的分隔符为 :
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;
}
}
}

// 结果不为空
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
// 结果里可能还有前后缀组合,继续递归解析
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) {
// Proceed with unprocessed value.
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();
}