Spring中的PropertyResolver

PropertyResolverSpring中的作用是负责属性解析,例如在XML中替换占位符(${})属性,@Value注解等

PropertyResolver

PropertySourcesPropertyResolver

PropertySource & PropertySources

在介绍PropertyResolver之前,先介绍下PropertySourcePropertySourcesPropertySource可以看做是包含了name/value的对象,而PropertySources则是由多个PropertySource组成的对象

PropertySource

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
86
87
88
89
90
91
public abstract class PropertySource<T> {

protected final String name;

// 存储name/value的对象,可以是任意对象,只要覆盖`getProperty`即可
protected final T source;

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

@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}

public String getName() {
return this.name;
}

public T getSource() {
return this.source;
}

// 判断`source`中是否包含指定属性(`name`)
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}

// 从`source`中根据指定属性名称(`name`)获取属性值(`value`),由子类实现
public abstract Object getProperty(String name);


// 省略`toString`,`hashCode`,`equals`方法


// 返回一个`PropertySource`
// 主要供内部使用,也可以用于集合比较,可以看方法注释中的示例代码
public static PropertySource<?> named(String name) {
return new ComparisonPropertySource(name);
}


// 默认实现,用于当`ApplicationContext`创建时无法及时初始化`PropertySource`时使用。
// 例如,`ServletContext`相关的属性必须等到`ServletContext`托付给`ApplicationContext`之后才能使用,可以在上下文刷新时替换
public static class StubPropertySource extends PropertySource<Object> {

public StubPropertySource(String name) {
super(name, new Object());
}

@Override
public String getProperty(String name) {
return null;
}
}

static class ComparisonPropertySource extends StubPropertySource {

private static final String USAGE_ERROR =
"ComparisonPropertySource instances are for use with collection comparison only";

public ComparisonPropertySource(String name) {
super(name);
}

@Override
public Object getSource() {
throw new UnsupportedOperationException(USAGE_ERROR);
}

@Override
public boolean containsProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}

@Override
public String getProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}

@Override
public String toString() {
return String.format("%s [name='%s']", getClass().getSimpleName(), this.name);
}
}

}

PropertySources

1
2
3
4
5
6
7
8
public interface PropertySources extends Iterable<PropertySource<?>> {

// 是否包含指定名称的`PropertySources`
boolean contains(String name);

// 根据名称获取`PropertySources`
PropertySource<?> get(String name);
}

MutablePropertySourcesPropertySources的默认实现,保存了List<PropertySource<?>>(虽然是list但是实际上通过PropertySources.name保证了每个PropertySources唯一性),并且还添加了添加/删除等方法,例如addFirst(PropertySource<?> propertySource)remove(String name)

PropertySourcesPropertyResolver

PropertySourcesPropertyResolver做的事,就是循环PropertySources,根据属性名称从PropertySource中获取属性值,并且对属性值做占位符解析替换

写个例子:

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
public static void main(String[] args) {

Map<String, Object> mapProperties = new HashMap<>();
mapProperties.put("name", "Allen");
mapProperties.put("age", 12);
mapProperties.put("nickname", "${name}|${age}");

MutablePropertySources propertySources = new MutablePropertySources();
// 此处使用MapPropertySource,也可以使用其他的PropertySource实现类
propertySources.addLast(new MapPropertySource("user", mapProperties));

PropertySourcesPropertyResolver propertySourcesPropertyResolver = new PropertySourcesPropertyResolver(propertySources);

// 是否忽略不能替换的占位符,默认为`false`。如果设置为`false`,当占位符无法替换时,抛出异常
propertySourcesPropertyResolver.setIgnoreUnresolvableNestedPlaceholders(false);
// 设置分隔符,默认为`:`。例如${a:1234},如果属性`a`存在,返回属性`a`的值,否则返回`1234`;
propertySourcesPropertyResolver.setValueSeparator(":");
// 设置占位符格式,默认为`${}`
propertySourcesPropertyResolver.setPlaceholderSuffix("}");
propertySourcesPropertyResolver.setPlaceholderPrefix("${");

System.out.println("name -> " + propertySourcesPropertyResolver.getProperty("name")); // name -> Allen
System.out.println("age -> " + propertySourcesPropertyResolver.getProperty("age")); // age -> 12
System.out.println("nickname -> " + propertySourcesPropertyResolver.getProperty("nickname")); // nickname -> Allen|12

System.out.println("introduction -> " + propertySourcesPropertyResolver.resolvePlaceholders("My name is ${name}, I'm ${age}.")); // introduction -> My name is Allen, I'm 12.
}

getProperty()方法实现

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
// PropertySourcesPropertyResolver.java
// 参数`key`为属性名称
// 参数`targetValueType`为属性值类型
// 参数`resolveNestedPlaceholders`是否需要占位符替换
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
boolean debugEnabled = logger.isDebugEnabled();
if (logger.isTraceEnabled()) {
logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
}
if (this.propertySources != null) {
// 循环所有的`propertySource`,查找属性名称(`key`)对应的属性值
for (PropertySource<?> propertySource : this.propertySources) {
if (debugEnabled) {
logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
Class<?> valueType = value.getClass();
// 如果需要占位符解析 && 属性值类型为`String`时做占位符替换
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
if (debugEnabled) {
logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
key, propertySource.getName(), valueType.getSimpleName(), value));
}
if (!this.conversionService.canConvert(valueType, targetValueType)) {
throw new IllegalArgumentException(String.format(
"Cannot convert value [%s] from source type [%s] to target type [%s]",
value, valueType.getSimpleName(), targetValueType.getSimpleName()));
}
// 将属性值转为指定类型返回
return this.conversionService.convert(value, targetValueType);
}
}
}
if (debugEnabled) {
logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
}
return null;
}

resolveNestedPlaceholders()调用的是AbstractPropertyResolver.resolveNestedPlaceholders()

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
// AbstractPropertyResolver.java部分代码

protected String resolveNestedPlaceholders(String value) {
// 根据`是否忽略不能识别的占位符名称`调用不同的解析方法
// 区别在于如果使用`${a}`时,属性`a`没有对应的属性值,`resolvePlaceholders`会输出`${a}`,而`resolveRequiredPlaceholders`则抛出异常
return (this.ignoreUnresolvableNestedPlaceholders ?
resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}

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

// 创建`PropertyPlaceholderHelper`占位符解析器
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, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
// 此处实际调用的是`PropertySourcesPropertyResolver`中的`getPropertyAsRawString()`方法,最终回到调用`getProperty()`
return getPropertyAsRawString(placeholderName);
}
});
}

// 子类实现
protected abstract String getPropertyAsRawString(String key);

可以看出,占位符处理工作是在PropertyPlaceholderHelper.replacePlaceholders()中实现:

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
// PropertyPlaceholderHelper.java
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}

protected String parseStringValue(String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

StringBuilder result = new StringBuilder(strVal);

// 占位符可以自定义,默认为`${}`
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);

// 以${name}来说,此处结果为name
String originalPlaceholder = placeholder;

// 1.此处防止循环依赖
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}

// 2.此处对结果递归解析,处理形如${name${age}}的形式,由内而外做处理,
// 如果age值为1,会先处理${age},解析为${name1},再处理
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);

// 处理占位符,调用PropertySourcesPropertyResolver.getPropertyAsRawString() -> getProperty()上述方法
String propVal = placeholderResolver.resolvePlaceholder(placeholder);

// 3.此处解释了分隔符的作用,分隔符可以自定义,默认为`:`
// 可以处理形如${name:123},解析为${name},如果name值不存在,则会使用默认值123
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;
}
}
}
if (propVal != null) {
// 2.此处对结果递归解析,如age->12,name->${age},p->${name},即对解析结果再做解析
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 string value \"" + strVal + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}

return result.toString();
}

1.循环依赖

1
2
3
4
mapProperties.put("def", "${abc}");
mapProperties.put("abc", "${def}");
// 或者如下形式
mapProperties.put("a", "${a}"); // IllegalArgumentException

2.占位符递归

1
2
3
4
mapProperties.put("a", "a");
mapProperties.put("b", "b");
mapProperties.put("ab", "${a}");
mapProperties.put("abc", "${a${b}}"); // getProperty("abc") -> 最终输出`a`

3.分隔符使用

1
mapProperties.put("a", "${abc:123}"); // getProperty("a") -> 如果`abc`存在,输出`abc`的值;否则输出`123`