Spring PropertyResolver 佔位符解析(二)源碼分析

Spring PropertyResolver 佔位符解析(二)源碼分析

Spring 系列目錄(http://www.javashuo.com/article/p-kqecupyl-bm.html)html

Spring 3.1 提供了新的佔位符解析器 PropertyResolver,默認實現爲 PropertySourcesPropertyResolver。相關文章以下:java

  1. Spring PropertyResolver 佔位符解析(一)API 介紹
  2. Spring PropertyResolver 佔位符解析(二)源碼分析

PropertyResolver 類圖

1、PropertyResolver 接口

PropertyResolver 的默認實現是 PropertySourcesPropertyResolver,Environment 實際上也是委託 PropertySourcesPropertyResolver 完成 佔位符的解析和類型轉換。 類型轉換又是委託 ConversionService 完成的。spring

public interface PropertyResolver {
    // 1. contains
    boolean containsProperty(String key);

    // 2.1 獲取指定 key,不存在能夠指定默認值,也能夠拋出異常
    String getProperty(String key);
    String getProperty(String key, String defaultValue);

    // 2.2 類型轉換,委託 ConversionService 完成
    <T> T getProperty(String key, Class<T> targetType);
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);

    String getRequiredProperty(String key) throws IllegalStateException;
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    // 3. 解析佔位符 ${key}
    String resolvePlaceholders(String text);
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}

ConfigurablePropertyResolver 是配置了一些解析佔位符的必要屬性,如佔位符前綴和後綴等ide

public interface ConfigurablePropertyResolver extends PropertyResolver {
    // 1. 類型轉換
    ConfigurableConversionService getConversionService();
    void setConversionService(ConfigurableConversionService conversionService);

    // 2.1 ${} 分隔符
    void setPlaceholderPrefix(String placeholderPrefix);
    void setPlaceholderSuffix(String placeholderSuffix);
    // 2.2 默認屬性分隔符 :
    void setValueSeparator(@Nullable String valueSeparator);
    
    // 3.1 ${key} getProperty(key)==null 時是否忽略,不拋出異常
    void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
    void setRequiredProperties(String... requiredProperties);
    void validateRequiredProperties() throws MissingRequiredPropertiesException;
}

2、PropertySourcesPropertyResolver 源碼分析

PropertySourcesPropertyResolver 持有一個數據源 PropertySources,能夠經過 getProperty 獲取對應的屬性值,這方法有幾種重載的方法,決定是否解析嵌套佔位符和類型轉換。源碼分析

// 不進行類型轉換,但會進行嵌套佔位符的解析
@Override
public String getProperty(String key) {
    return getProperty(key, String.class, true);
}

// 進行類型轉換,也進行嵌套佔位符的解析
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
    return getProperty(key, targetValueType, true);
}

// 不進行類型轉換,也不進行嵌套佔位符的解析,返回原始的字符串
@Override
protected String getPropertyAsRawString(String key) {
    return getProperty(key, String.class, false);
}

咱們再看一下 getProperty(key, String.class, false) 這個方法ui

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
    if (this.propertySources != null) {
        for (PropertySource<?> propertySource : this.propertySources) {
            // 1. 從數據源中獲取屬性值
            Object value = propertySource.getProperty(key);
            if (value != null) {
                // 2. 若是屬性值自己又含有佔位符就屬於嵌套佔位符解析,如 ${a${x}b}
                if (resolveNestedPlaceholders && value instanceof String) {
                    value = resolveNestedPlaceholders((String) value);
                }
                // 日誌輸出
                logKeyFound(key, propertySource, value);
                // 3. 類型轉換
                return convertValueIfNecessary(value, targetValueType);
            }
        }
    }
    return null;
}

如今最關鍵的方法是 resolveNestedPlaceholders,用於解析嵌套的佔位符,這個方法是在其父類 AbstractPropertyResolver 實現的。this

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

private PropertyPlaceholderHelper nonStrictHelper;
private PropertyPlaceholderHelper strictHelper;
@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);
}

實際上嵌套佔位符的解析 PropertySourcesPropertyResolver 都委託給了 PropertyPlaceholderHelper 方法來完成,而自身主要完成從 PropertySources 獲取屬性值。日誌

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, this::getPropertyAsRawString);
}

3、PropertyPlaceholderHelper 嵌套佔位符的解析

在看 PropertyPlaceholderHelper 以前先看一下 PlaceholderResolver 這個內部類,這個類用於獲取佔位符 key 對應的 value。在 AbstractPropertyResolver#doResolvePlaceholders 方法中將 this::getPropertyAsRawString 傳過來了,也就是說 PlaceholderResolver 是從 propertySources 獲取對應的 value 值。code

@FunctionalInterface
public interface PlaceholderResolver {
    // 從 propertySource 中獲取 placeholderName 的 value
    String resolvePlaceholder(String placeholderName);
}

下面再看 replacePlaceholders 是如何解析嵌套的佔位符的。htm

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    Assert.notNull(value, "'value' must not be null");
    // placeholderResolver 是從 propertySources 獲取的屬性值
    return parseStringValue(value, placeholderResolver, new HashSet<>());
}

// 循環解析 key ${a${x}b}
protected String parseStringValue(
        String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

    StringBuilder result = new StringBuilder(value);

    int startIndex = value.indexOf(this.placeholderPrefix);
    while (startIndex != -1) {
        // 找到結束的 } 位置,注意嵌套時要找對應的結束標記符 ${a${x}b}
        int endIndex = findPlaceholderEndIndex(result, startIndex);
        // endIndex=-1 或 startIndex=-1 結束循環
        if (endIndex != -1) {
            // 1. 獲取 ${key} 的 key
            String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
            String originalPlaceholder = placeholder;
            // 2. key 出現了循環嵌套,直接 Game Over
            if (!visitedPlaceholders.add(originalPlaceholder)) {
                throw new IllegalArgumentException(
                        "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
            }
            // 2. 循環解析這個 key,若是這個 key 又是形如 ${...}
            placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
            // 3. 至此,這個 key 不可能出現 ${} 了,所以能夠放心大膽的從 propertySources 獲取對應的 value
            String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            // 4. ${key:default} 若是爲 null,獲取真正的 key,若是爲 null 則爲默認值
            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;
                    }
                }
            }
            // 5. 對不起,value 也可能爲 ${...},遞歸解析
            if (propVal != null) {
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            } else if (this.ignoreUnresolvablePlaceholders) {
                // 6. 忽略沒法解析的 key,繼續...
                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();
}

天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索