依赖注入之@Value原理(placeholder解析)
resolveEmbeddedValue是doResolveDependency方法处理@Value注解的第二步,resolveEmbeddedValue解析@Value注解中设置value的placeholder,将${xxx}替换为application.properties中配置的值。
回顾一下doResolveDependency方法,如下
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Class<?> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// A custom TypeConverter which does not support TypeDescriptor resolution...
return (descriptor.getField() != null ?
converter.convertIfNecessary(value, type, descriptor.getField()) :
converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
}
}
...
}
finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
前面分析过,resolveEmbeddedValue最终会调用PropertySourcesPropertyResolver#resolvePlaceholders方法或者PropertySourcesPropertyResolver#resolveRequiredPlaceholders处理placeholder,区别在于当${xxx}指定的placehodler不存在值时,resolveRequiredPlaceholders会抛异常,resolvePlaceholders会忽略,PropertySourcesPropertyResolver里的这两个方法都继承自AbstractPropertyResolver,看一下这两个方法
@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,然后调用doResolvePlaceholders来解析value
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
doResolvePlaceholders直接调用PropertyPlaceholderHelper#replacePlaceholders来解析,并将getPropertyAsRawString方法引用作为PlaceholderResolver传进去。PropertyPlaceholderHelper#replacePlaceholders方法会不断提取value中的placeholder,通过PlaceholderResolver将placeholder解析成真正的值。getPropertyAsRawString就是从配置中找到配置的值,这里不在分析。
大部分逻辑都在PropertyPlaceholderHelper,回头看createPlaceholderHelper创建PropertyPlaceholderHelper的
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
这里将四个参数传了进去
- placeholderPrefix,就是’${’
- placeholderSuffix,就是’}’
- valueSeparator,就是’:’
- ignoreUnresolvablePlaceholders,resolvePlaceholders和resolveRequiredPlaceholders分别传的true、false
placeholderPrefix、placeholderSuffix、ignoreUnresolvablePlaceholders都好理解,解释下valueSeparator。当@Value指定的placeholder不存在配置的值时,除了希望是否忽略之外,还希望能填进去一个默认值,${app.name:jack}这样写时当不存在app.name的配置时,用默认值jack代替。
PropertyPlaceholderHelper#replacePlaceholders方法直接调用了PropertyPlaceholderHelper#parseStringValue方法,这个是placeholder解析的核心逻辑。分析该逻辑之前考虑几个问题:
- placeholder能否存在多个?例如 p 1 a n d {p1}and p1and{p2}
- placeholder能否嵌套?例如KaTeX parse error: Expected '}', got 'EOF' at end of input: {app.{name}}
- 通过placeholder获取到的值能否还包含placeholder?例如 a p p . n a m e , 配 置 中 a p p . n a m e = a p p {app.name},配置中app.name=app app.name,配置中app.name=app{name}
- placeholder配置的值还包含placeholder,怎么解决循环问题?例如 a p p . n a m e , 配 置 中 a p p . n a m e = p {app.name},配置中app.name=p app.name,配置中app.name=p{app.name}
parseStringValue的逻辑主要在解决上面这些问题,通过递归解决嵌套和配置包含placehodler问题,通过记录正在解析的placeholder检测循环并抛异常。while循环里逐个处理text中并列的多个placeholder,对每个placeholder处理逻辑如下
- findPlaceholderEndIndex找到与之相闭合的’}',找不到则设置startIndex=-1,退出while循环
- 拿到placeholder,加入visitedPlaceholders,visitedPlaceholders保存递归过程中正在解析的placeholder,如果当前placeholder在visitedPlaceholders存在,抛异常退出。这个placeholder里面可能还存在placehodler,所以要先处理内部placeholder,在通过placeholderResolver解析当前placeholder
- 递归调用parseStringValue解析拿到的placeholder,其实就是处理当前placeholder内部的placeholder,即KaTeX parse error: Expected '}', got 'EOF' at end of input: {app.{name}}情况
- 处理完内部placeholder后,通过placeholderResolver解析当前placeholder,即从配置中获取值
- 如果值为null(即不存在该配置),有可能是通过valueSeparator配置了默认值,处理默认值情况
- 如果解析的值还是null,则抛异常或忽略
- 如果解析出来的值不是null,那么配置的值也是可能包含placeholder的,即app.name=app${name}情况,递归调用parseStringValue来解析
- 最后将解析出来的值替换placeholder
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");
}
// 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) {
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();
}