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 实现了 BeanFactoryPostProcessorpostProcessBeanFactory 方法,这样在拿到 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 方法,它将原始字符串解析为替换占位符后的值,它是通过 valueResolverresolveStringValue 方法实现的。

PropertyPlaceholderConfigurer

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

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

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

PlaceholderResolvingStringValueResolver

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

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