在DefaultListableBeanFactory的doResolveDependency方法中,首先通过AutowireCandidateResolver的getSuggestedValue方法获取@Value注解的属性值后(例如在@Value注解中配置的value属性为 “ u s e r . n a m e ” , 那 这 里 获 取 到 的 就 是 " {user.name}”,那这里获取到的就是" user.name”,那这里获取到的就是"{user.name}”)。
@Value("${user.name}") ------> "${user.name}" // 经过#getSuggestedValue方法处理后
private String userName;
接下来判断获取到的值是否不等于null,如果不等于null,则判断值是否是String类型的,如果是Stri-ng类型的,首先调用resolveEmbeddedValue方法。在@Value注解中配置的EL表达式就是在这里完成解析的。
// DefaultListableBeanFactory#doResolveDependency
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
Class<?> type = descriptor.getDependencyType();
// 这里使用的ContextAnnotationAautowireCandidateResolver
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
if (value instanceof String) {
// 解析EL表达式
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()));
}
}
// 只贴出和本次分析相关的代码,其余代码省略...
}
resolveEmbeddedValue方法定义在AbstractBeanFactory类中,在该方法中通过遍历所有注册的StringValueResolver接口实现类的resolveStringValue方法来解析传入的value,该循环正常情况下的结束条件是当前正遍历的StringValueResolver的resolveStringValue方法返回值为null或者遍历完毕,即使上一个StringValueResolver实现类已经解析完传入的value,下一个(如果有)实现类依然可以解析。
// AbstractBeanFactory#resolveEmbeddedValue
public String resolveEmbeddedValue(@Nullable String value) {
if (value == null) {
return null;
}
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
if (result == null) {
return null;
}
}
return result;
}
应用上下文默认情况下往IoC容器中注册了一个StringValueResolver实现类-在AbstractApplicationContext的finishBeanFactoryInitialization方法中,通过Lambda表达式注册。
在该实现中通过调用Environment实现类的resolvePlaceholders方法来完成解析。
// AbstractApplicationContext#finishBeanFactoryInitialization
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 只贴出和本次分析相关的代码,其余代码省略...
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
// 只贴出和本次分析相关的代码,其余代码省略...
}
resolvePlaceholers方法由PropertyResolver接口定义,由AbstractEnvironment来实现。在该方法中,通过调用提前实例化好的ConfigurablePropertyResolver接口实现类PropertySourcePropertyResolver的resolvePlaceholders方法来完成。
在创建PropertySourcesPropertyResolver时传入在AbstractEnvironment对象中定义的MutableProper-tySources对象实例作为构造参数。
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
// AbstractEnvironment#resolvePlaceholders
public String resolvePlaceholders(String text) {
return this.propertyResolver.resolvePlaceholders(text);
}
PropertySourcePropertyResolver并未实现resolvePlaceholders方法,而是由其父类AbstractProperty-Resolver来实现。在该实现中,首先判断nonStrictHelper属性是否为空,如果为空则调用createPlace-HolderHelper方法创建PropertyPlaceholderHelper实例并赋值给nonStrictHelper。
PropertyPlaceholderHelper位于spring core中的工具包中,该类并未实现任何接口。需注意的是在创建PropertyPlaceholderHelper时,传入的几个构造参数,它们分别为:placeholderPrefix(占位符前缀"${")、placeholderSuffix(占位符后缀"}"),valueSeparator(值分隔符“:”),ignoreUnresolvablePlace-holders(,忽略不能解析的占位符,默认为true)。
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
/** Prefix for system property placeholders: "${". */
public static final String PLACEHOLDER_PREFIX = "${";
/** Suffix for system property placeholders: "}". */
public static final String PLACEHOLDER_SUFFIX = "}";
/** Value separator for system property placeholders: ":". */
public static final String VALUE_SEPARATOR = ":";
// AbstractPropertyResolver#resolvePlaceholders
public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
}
return doResolvePlaceholders(text, this.nonStrictHelper);
}
// AbstractPropertyResolver#createPlaceholderHelper
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
doResolvePlaceholders方法定义在AbstractPropertyResolver类中,这是一个私有方法。在该方法中调用传入的helper的replacePlaceholders方法,并传入了一个方法引用。
// AbstractPropertyResolver#doResolvePlaceholders
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
在PropertyPlaceholderHelper的replacePlaceholders方法中,对传入的value进行断言后,直接调用p-arseStringValue方法并返回。
// PropertyPlaceholderHelper#replacePlaceholders(String, PropertyPlaceholderHelper.PlaceholderResolver)
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
先查看该方法全部源码,有个总体印象,接下来会分段分析。
// PropertyPlaceholderHelper#parseStringValue
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);
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) {
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;
}
}
return result.toString();
}
在parseStringValue方法中 ,首先通过String提供的indexOf方法来获取占位符前缀("${")的下标,如果该下标等于-1,即未找到,则不解析,直接原值返回。
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
接下来创建一个StringBuilder对象,初始值为传递的value。执行while循环,结束条件是占位符前缀的下标不等于-1。调用findPlaceholderEndIndex方法,传入前面创建的StringBuilder对象以及占位符前缀的下标。如果该方法返回的占位符后缀("}")的下标等于-1,则将startIndex赋值为-1,循环结束。
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 省略其它代码...
} else {
startIndex = -1;
}
接下来判断获取到的占位符后缀("}")下标是否不等于-1,如果判断成立,从StringBuilder对象(以下按变量名result)中从占位符前缀长度开始截取直到前面获取到的占位符后缀下标处并赋值给String类型的变量-placeholder。判断方法入参vistedPlacceholder是否等于null,如果等于null,则创建一个初始长度为4的HashSet,将前面处理好的字符串添加到该集合中,如果添加失败则抛出异常。
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");
}
递归调用当前方法(parseStringValue)来继续处理本次处理完毕的字符串。重点是接下来调用placeho-lderResolver的resolvePlaceholder方法,在该方法中完成了从Environment实现类对象中获取对应属性值,并将获取到的属性值赋值给propVal变量。
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
这里调用的是在前面doResolvePlaceholders方法中传递的PropertySourcesPropertyResolver的getPr-opertyAsRawString方法引用。如果该方法返回值等于null并且值分隔符(":")不等于null,那么则开始处理存在默认值的情况(例如@Value("${user.name:zhangsan}"))。
首先调用placeholder的indexOf方法来判断是否存在值分隔符(“:”),如果获取到的下标不等于-1,则调用placeholder的subString方法来从第零位截取到获取的值分隔符起始下标处,并赋值给actualPla-ceholder。然后继续调用placeholder的subString方法,从值分隔符起始下标处加上值分隔符长度位截取获取默认值。
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;
}
}
}
继续调用PropertySourcesPropertyResolver的resolvePlaceholder方法来解析截取掉默认值的占位符,如果这次获取到的值还是等于null,那么则将默认值赋值给propVal变量。
接下来判断前面通过PlaceholderResolver的resolvePlaceholder方法或者解析用户在@Value注解中配置的默认值得到的propVal变量是否不等于null,如果不等于null,继续递归调用当前方法(parseStrin-gValue)传入propVal。调用result的replace方法,从占位符前缀("KaTeX parse error: Expected 'EOF', got '}' at position 79: …exOf方法,从占位符后缀("}̲")起始下标+propVal长…{")下标并赋值给startIndex变量。通常获取到的都是-1。
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());
}
如果propVal等于空,则判断ignoreUnresolvablePlaceholdes属性是否为true,如果为true,则调用result的indexOf方法获取占位符前缀的下标,从占位符后缀("}")起始索引位+占位符后缀长度位开始查找,并将查找到的结果赋值给startIndex变量。
else if (this.ignoreUnresolvablePlaceholders) {
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
如果propVal等于空并且ignoreUnresolvablePlaceholders属性为false,那么则抛出异常。
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
最终调用result的toString方法并返回。
return result.toString();
接下来就分析下PropertyPlaceholderHelper的findPlaceholderEndIndex方法是如何从给定字符串中找到占位符后缀("}")的下标的。
首先根据传入的占位符前缀("KaTeX parse error: Expected '}', got 'EOF' at end of input: …起始下标索引加上占位符前缀("{")长度得到从给定字符串要开始查找的起始下标(index),接下来就是while循环,该循环的终止条件是index小于给定的字符串长度。
在这个while循环中,每一次遍历都会调用StringUtils的subStringMatch方法,传入给定的字符串,以及当前下标和要匹配的子字符串。这里可以看到无论是if还是else都是调用StringUtils的subStringMa-tch方法,它们之间的调用区别就在于传入的子字符串不一样。
在if中,调用StringUtils的subStringMatch方法时传入的子字符串为占位符后缀("}"),而在else if中调用StringUtils的subStringMatch方法时传入的子字符串为简单前缀("{",注意不是占位符前缀“${”)。简单占位符前缀通常用在嵌套占位符中。
// PropertyPlaceholderHelper#findPlaceholderEndIndex
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderSuffix.length();
} else {
return index;
}
} else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
withinNestedPlaceholder++;
index = index + this.simplePrefix.length();
} else {
index++;
}
}
return -1;
}
这里就分析下StringUtils的subStringMatch方法是如何进行子串匹配的。
首先是判断传入的下标+子串长度是否大于字符串的长度,如果判断成立直接返回false。接下来开始循环,循环结束的条件是自增变量-i(i从0开始自增)小于子串的长度。
在该循环中,通过调用字符串的charAt方法来获取调用substringMatch方法时传入的下标+1位置的字符,使用该字符和子串中第i位的字符进行等等比较,如果有任何一个字符不相等直接返回false。最终返回true。
// StringUtils#substringMatch
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
if (index + substring.length() > str.length()) {
return false;
}
for (int i = 0; i < substring.length(); i++) {
if (str.charAt(index + i) != substring.charAt(i)) {
return false;
}
}
return true;
}
PlaceholderResolver的resolvePlaceholder方法是如何解析占位符并返回对应的值呢?
前面已经分析过在调用PropertyPlaceholderHelper的replacePlaceholders方法是传入的PlaceholderR-esolver为AbstractPropertyResolver的getPropertyAsRawString方法引用,因此在PropertyPlaceholde-rHelper的parseStringValue方法中调用PlaceholderResolver的resolvePlaceholder方法时,实际上调用的是PropertySourcesPropertyResolver的getPropertyAsRawString方法。
在getPropertyAsRawString方法中通过调用getProperty方法来完成,需注意的是这里在调用getProp-erty方法是传入的resolveNestedPlaceholders为false。
// PropertySourcesPropertyResolver#getPropertyAsRawString
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
前面我们已经分析过,在创建PropertySourcesPropertyResolver对象实例时,需要传入一个Propert-ySources对象,AbstractEnvironment传入的是自己持有的PropertySources,因此在getProperty方法中遍历的PropertySources实际是AbstractEnvironment的PropertySources。
遍历PropertySources集合,获取到每一个PropertySource对象,然后调用其getProperty方法,如果该方法的返回值不等于null,则判断调用该方法时传入的resolveNestedPlaceholders是否为true并且值是String类型的,则调用resolveNestedPlaceholders方法来解析嵌套的占位符。通过前面的分析我们可以得知传入的resolveNestedPlaceholders为false。
最后调用convertValueIfNecessary方法来进行类型转换并返回。
// PropertySourcesPropertyResolver#getProperty
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
这里的遍历顺序是从前往后,因此我们也可以得出一个结论,在PropertySources集合中靠前的Prop-ertyValue中的配置会覆盖之后的PropertyValue中的相同属性名配置。注意在PropertySources集合中的顺序和添加时的顺序没有太大关系,即先添加不一定就在前面。
此外还值得一提的是调用的convertValueIfNecessary方法,在该方法中首先判断传入的targetType是否等等于null,如果等等于null,则直接返回。
其次获取实例变量conversionService并赋值给conversionToUse变量,判断conversionServiceToUse变量是否等等于null,如果不等于null,则直接调用其convert方法。否则首先调用ClassUtils的isAssign-ableValue方法(该方法主要用来判断目标类型和值类型是否是基本类型对应的包装类型),如果判断成立则直接返回原值。
通过注释我们也可以看出Spring是为了尽可能地避免去初始化共享的DefaultConversionService。还需要注意的一点是在外部化配置中,Spring没有使用类型转换的另一部分-基于JavaBeans的Propert-yEditor,而只使用了统一类型转换服务-ConversionService。
// AbstractPropertyResolver#convertValueIfNecessary
protected <T> T convertValueIfNecessary(Object value, @Nullable Class<T> targetType) {
if (targetType == null) {
return (T) value;
}
ConversionService conversionServiceToUse = this.conversionService;
if (conversionServiceToUse == null) {
// Avoid initialization of shared DefaultConversionService if
// no standard type conversion is needed in the first place...
if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
conversionServiceToUse = DefaultConversionService.getSharedInstance();
}
return conversionServiceToUse.convert(value, targetType);
}