Spring 源码解析 之 PropertyPlaceholderHelper (占位符解析)

Spring 源码解析 之 PropertyPlaceholderHelper (占位符解析)

 

 

1、简介

      PropertyPlaceholderHelper 位于 org.springframework.util 包下, 用于处理包含 占位符 值的字符串的实用程序类。 例如: 占位符采用的形式为 ${name}。使用这些占位符可以替换用户提供的值。 这个类不依赖于任何其他的类, 也可以作为我们的工具类单独使用。

 

2、源码如下:

public class PropertyPlaceholderHelper {

    private static final Log logger = LogFactory.getLog(org.springframework.util.PropertyPlaceholderHelper.class);

    /**
     * 定义了常用占位符 开始、结束的符号
     */
    private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);

    static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }

    /**
     * 占位符前缀
     */
    private final String placeholderPrefix;
    /**
     * 占位符后缀
     */
    private final String placeholderSuffix;
    /**
     * 占位符前缀
     */
    private final String simplePrefix;

    /**
     * 占位符变量和关联的默认值(如果有)之间的分隔字符, 例如 @Value("${spring.profile.active : dev}")
     */
    @Nullable
    private final String valueSeparator;

    /**
     * 是否忽略不能解析的占位符
     */
    private final boolean ignoreUnresolvablePlaceholders;


    /**
     * 创建一个 PropertyPlaceholderHelper 使用提供的前缀和后缀的新前缀。无法解析的占位符将被忽略。
     * @param placeholderPrefix 占位符前缀
     * @param placeholderSuffix 占位符后缀
     */
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
        this(placeholderPrefix, placeholderSuffix, null, true);
    }

    /**
     * 创建一个 PropertyPlaceholderHelper 使用提供的前缀和后缀的新前缀。
     *
     * @param placeholderPrefix 表示占位符开头的前缀
     * @param placeholderSuffix 表示占位符结尾的后缀
     * @param valueSeparator 占位符变量和关联的默认值(如果有)之间的分隔字符
     * @param ignoreUnresolvablePlaceholders 指示是应忽略不可解析的占位符 () 还是导致异常 (true | false)
     */
    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                     @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {

        Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
        Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
        this.placeholderPrefix = placeholderPrefix;
        this.placeholderSuffix = placeholderSuffix;
        String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
        if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
            this.simplePrefix = simplePrefixForSuffix;
        }
        else {
            this.simplePrefix = this.placeholderPrefix;
        }
        this.valueSeparator = valueSeparator;
        this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }


    /**
     * 将格式例如: ${name} 的所有占位符替换为提供的 Properties中的相应属性。
     *
     * @param value 包含要替换的占位符的值
     * @param properties 用于替换的属性Properties
     * @return
     */
    public String replacePlaceholders(String value, final Properties properties) {
        Assert.notNull(properties, "'properties' must not be null");
        return replacePlaceholders(value, properties::getProperty);
    }

    /**
     * 将格式 ${name} 的所有占位符替换为从提供的 org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver返回的值。
     *
     * @param value 包含要替换的占位符的值
     * @param placeholderResolver 占位符解析器 – PlaceholderResolver 用于替换
     * @return
     */
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return parseStringValue(value, placeholderResolver, new HashSet<>());
    }

    /**
     * 真正解析字符串的占位符
     *
     * @param value 含有占位符的字符串
     * @param placeholderResolver 占位符属性解析器
     * @param visitedPlaceholders 访问过的占位符
     * @return
     */
    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) {
                // 截取前缀占位符和后缀占位符之间的字符串placeholder
                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");
                }
                // 递归调用,继续解析placeholder
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // 获取placeholder的值
                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) {
                    // 对替换完成的value进行解析, 防止properties的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();
    }

    /**
     * 查找占位符的结束符号的位置
     *
     * @param buf 待解析的字符串
     * @param startIndex 占位符开始的位置
     * @return
     */
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        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处是否与前缀匹配,若匹配,说明前缀后面还是前缀,则把前缀长度累加到index上,继续循环寻找后缀
            //withinNestedPlaceholder确保前缀和后缀成对出现后
            else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();
            }
            else {
                index++;
            }
        }
        return -1;
    }


    /**
     * 用于解析字符串中包含的占位符的替换值的策略
     */
    @FunctionalInterface
    public interface PlaceholderResolver {

        /**
         * 将提供的占位符名称解析为替换值。
         *
         * @param placeholderName 要解析的占位符的名称
         * @return 替换值,或者 null 如果不进行替换
         */
        @Nullable
        String resolvePlaceholder(String placeholderName);
    }

}

 

3、案例

  public static void main(String[] args) {
        Map<String,String> params = new HashMap<>(2);
        params.put("name", "黄晓明");
        params.put("age", "32");
        PropertyPlaceholderHelper helper1 = new PropertyPlaceholderHelper("${", "}", ":", false);
        System.out.println(helper1.replacePlaceholders("text: 姓名:${name2:张三}, 年龄:${age}", params::get));
    }

 

posted @ 2023-07-26 08:39  邓维-java  阅读(1018)  评论(0)    收藏  举报