依赖注入之@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.nameapp.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.nameapp.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();
	}