SpringBoot environment attribute type conversion and parsing placeholder

premise

Source code analysis and wrote the previous article on Environment extended attributes loaded, which referred to the placeholder attribute parsing and type conversion is relatively complex, this article is to analyze and interpret these complex issues. On these two issues, the choice of a more complex method of processing parameters PropertySourcesPropertyResolver#getProperty, the dependency parsing placeholder to PropertySourcesPropertyResolver#getPropertyAsRawString:

protected String getPropertyAsRawString(String key) {
    return getProperty(key, String.class, false);
}

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.isDebugEnabled()) {
        logger.debug("Could not find key '" + key + "' in any property source");
    }
    return null;
}

Analytical properties placeholder

Placeholder property analysis method is PropertySourcesPropertyResolver parent class AbstractPropertyResolver#resolveNestedPlaceholders:

protected String resolveNestedPlaceholders(String value) {
    return (this.ignoreUnresolvableNestedPlaceholders ?
        resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
}

ignoreUnresolvableNestedPlaceholders property defaults to false, can be AbstractEnvironment#setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders)provided, when (and no operator to configure the default value placeholder) failed placeholder property This property is set to true, no exception is thrown resolved, returns the attribute as the string, or throws IllegalArgumentException. We only need to analyze AbstractPropertyResolver#resolveRequiredPlaceholders:

//AbstractPropertyResolver中的属性:
//ignoreUnresolvableNestedPlaceholders=true情况下创建的PropertyPlaceholderHelper实例
@Nullable
private PropertyPlaceholderHelper nonStrictHelper;

//ignoreUnresolvableNestedPlaceholders=false情况下创建的PropertyPlaceholderHelper实例
@Nullable
private PropertyPlaceholderHelper strictHelper;

//是否忽略无法处理的属性占位符,这里是false,也就是遇到无法处理的属性占位符且没有默认值则抛出异常
private boolean ignoreUnresolvableNestedPlaceholders = false;

//属性占位符前缀,这里是"${"
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;

//属性占位符后缀,这里是"}"
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;

//属性占位符解析失败的时候配置默认值的分隔符,这里是":"
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;


public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}

//创建一个新的PropertyPlaceholderHelper实例,这里ignoreUnresolvablePlaceholders为false
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
}

//这里最终的解析工作委托到PropertyPlaceholderHelper#replacePlaceholders完成
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

Only the final analysis PropertyPlaceholderHelper#replacePlaceholders, important to note here:
- Noting source string of text where the first parameter is a property value, e.g., attributes we need to address is MyProperties: s e r v e r . p O r t {spring.application.name}, text here is s e r v e r \. p O r t {spring.application.name}.
- second parameter placeholderResolver replacePlaceholders methods, subtle compared here, the methods of this :: getPropertyAsRawString references corresponding to the following code:

//PlaceholderResolver是一个函数式接口
@FunctionalInterface
public interface PlaceholderResolver {
  @Nullable
  String resolvePlaceholder(String placeholderName);  
}
//this::getPropertyAsRawString相当于下面的代码
return new PlaceholderResolver(){

    @Override
    String resolvePlaceholder(String placeholderName){
        //这里调用到的是PropertySourcesPropertyResolver#getPropertyAsRawString,有点绕
        return getPropertyAsRawString(placeholderName);
    }
}       

Then look at PropertyPlaceholderHelper#replacePlaceholdersthe source code:

//基础属性
//占位符前缀,默认是"${"
private final String placeholderPrefix;
//占位符后缀,默认是"}"
private final String placeholderSuffix;
//简单的占位符前缀,默认是"{",主要用于处理嵌套的占位符如${xxxxx.{yyyyy}}
private final String simplePrefix;

//默认值分隔符号,默认是":"
@Nullable
private final String valueSeparator;
//替换属性占位符
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    Assert.notNull(value, "'value' must not be null");
    return parseStringValue(value, placeholderResolver, new HashSet<>());
}

//递归解析带占位符的属性为字符串
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) {
            //提取第一个占位符中的原始字符串,如${server.port}->server.port
            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...
            // 递归调用完毕后,可以确定得到的字符串一定是不带占位符,这个时候调用getPropertyAsRawString获取key对应的字符串值
            String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            // 如果字符串值为null,则进行默认值的解析,因为默认值有可能也使用了占位符,如${server.port:${server.port-2:8080}}
            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());
                    // 这里是把默认值的表达式做一次解析,解析到null,则直接赋值为defaultValue
                    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                    if (propVal == null) {
                        propVal = defaultValue;
                    }
                }
            }
            // 上一步解析出来的值不为null,但是它有可能是一个带占位符的值,所以后面对值进行递归解析
            if (propVal != null) {
                // Recursive invocation, parsing placeholders contained in the
                // previously resolved placeholder value.
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                // 这一步很重要,替换掉第一个被解析完毕的占位符属性,例如${server.port}-${spring.application.name} -> 9090--${spring.application.name}
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                if (logger.isTraceEnabled()) {
                    logger.trace("Resolved placeholder '" + placeholder + "'");
                }
                // 重置startIndex为下一个需要解析的占位符前缀的索引,可能为-1,说明解析结束
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            }
            else if (this.ignoreUnresolvablePlaceholders) {
                // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为true,直接返回当前的占位符之间的原始字符串尾的索引,也就是跳过解析
                // Proceed with unprocessed value.
                startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
            }
            else {
                // 如果propVal为null并且ignoreUnresolvablePlaceholders设置为false,抛出异常
                throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
            }
            // 递归结束移除判重集合中的元素
            visitedPlaceholders.remove(originalPlaceholder);
        }
        else {
            // endIndex = -1说明解析结束
            startIndex = -1;
        }
    }
    return result.toString();
}

//基于传入的起始索引,搜索第一个占位符后缀的索引,兼容嵌套的占位符
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
    //这里index实际上就是实际需要解析的属性的第一个字符,如${server.port},这里index指向s
    int index = startIndex + this.placeholderPrefix.length();
    int withinNestedPlaceholder = 0;
    while (index < buf.length()) {
        //index指向"}",说明有可能到达占位符尾部或者嵌套占位符尾部
        if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
            //存在嵌套占位符,则返回字符串中占位符后缀的索引值
            if (withinNestedPlaceholder > 0) {
                withinNestedPlaceholder--;
                index = index + this.placeholderSuffix.length();
            }
            else {
                //不存在嵌套占位符,直接返回占位符尾部索引
                return index;
            }
        }
        //index指向"{",记录嵌套占位符个数withinNestedPlaceholder加1,index更新为嵌套属性的第一个字符的索引
        else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
            withinNestedPlaceholder++;
            index = index + this.simplePrefix.length();
        }
        else {
            //index不是"{"或者"}",则进行自增
            index++;
        }
    }
    //这里说明解析索引已经超出了原字符串
    return -1;
}

//StringUtils#substringMatch,此方法会检查原始字符串str的index位置开始是否和子字符串substring完全匹配
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;
}

The above process is relatively complicated, because use of recursion, we give an actual example to explain the entire resolution process, for example, we used the four property items, our goal is to get server.desc values:

application.name=spring
server.port=9090
spring.application.name=${application.name}
server.desc=${server.port-${spring.application.name}}:${description:"hello"}

spec-1

Property type conversion

After the completion of step placeholder analytical properties, is obtained attribute value string, the string can be converted to the specified type, the function of AbstractPropertyResolver#convertValueIfNecessarycomplete:

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

Conversion logic is actually delegate to the parent class method DefaultConversionService GenericConversionService#convert:

public <T> T convert(@Nullable Object source, Class<T> targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}

public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
    Assert.notNull(targetType, "Target type to convert to cannot be null");
    if (sourceType == null) {
        Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
        return handleResult(null, targetType, convertNullSource(null, targetType));
    }
    if (source != null && !sourceType.getObjectType().isInstance(source)) {
        throw new IllegalArgumentException("Source to convert from must be an instance of [" +
                    sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
    }
    // 从缓存中获取GenericConverter实例,其实这一步相对复杂,匹配两个类型的时候,会解析整个类的层次进行对比
    GenericConverter converter = getConverter(sourceType, targetType);
    if (converter != null) {
        // 实际上就是调用转换方法
        Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
        // 断言最终结果和指定类型是否匹配并且返回
        return handleResult(sourceType, targetType, result);
    }
    return handleConverterNotFound(source, sourceType, targetType);
}

Examples of all of the above can GenericConverter available in DefaultConversionService's addDefaultConverterssee, the converter has been added to the default instance of more than 20, in some cases if you can not meet the demand can add custom converter, you can achieve GenericConverter interfaces added to it.

summary

SpringBoot entire abstract type converter has done better, in SpringMVC applications, using org.springframework.boot.autoconfigure.web.format.WebConversionService, compatible Converter, Formatter, ConversionService converter and other types of external and provide a sets uniform conversion method.

(This article End)

Released seven original articles · won praise 6 · views 10000 +

Guess you like

Origin blog.csdn.net/zjcsuct/article/details/81408404