4.Spring占位符解析

        前文介绍了获取 Environment 实例对象,这篇文章来分析解析路径的源码,直接上源码。

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean {
    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }
}

        接着来看 resolveRequiredPlaceholders 的源码。

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {

    // 可配置的类型转换服务
    @Nullable
    private volatile ConfigurableConversionService conversionService;
    // 非严格的占位符解析助手
    @Nullable
    private PropertyPlaceholderHelper nonStrictHelper;
    // 严格的占位符解析助手
    @Nullable
    private PropertyPlaceholderHelper strictHelper;
    // 忽略无法解析的嵌套占位符
    private boolean ignoreUnresolvableNestedPlaceholders = false;
    // 占位符前缀 ${
    private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
    // 占位符后缀 }
    private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
    // 属性分隔符 :
    @Nullable
    private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
    // 需要的属性集合
    private final Set<String> requiredProperties = new LinkedHashSet<>();

    // 解析必要的占位符
    @Override
    public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
        // 如果占位符解析助手为空
        if (this.strictHelper == null) {
            // 创建占位符解析助手
            this.strictHelper = createPlaceholderHelper(false);
        }
        // 执行解析
        return doResolvePlaceholders(text, this.strictHelper);
    }

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

        首先创建一个  PropertyPlaceholderHelper  的实例对象,然后再执行解析工作。先来看一下

/**
 * 用于处理包含占位符值的字符串的实用程序类。
 * 可以替换这些占位符来替换用户提供的值。
 */
public class PropertyPlaceholderHelper {

    // 众所周知的简单前缀
    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;
    // 分隔符
    @Nullable
    private final String valueSeparator;
    // 忽略无法解析的嵌套占位符
    private final boolean ignoreUnresolvablePlaceholders;
}

        来看  doResolvePlaceholders 的源码。

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
    
    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { 
        return helper.replacePlaceholders(text, this::getPropertyAsRawString);
    }
}

        这里有调用了解析助手的 replacePlaceholders 方法。

public class PropertyPlaceholderHelper {

    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        // 解析字符串中的值
        return parseStringValue(value, placeholderResolver, null);
    }
}

        来看具体的解析过程。

public class PropertyPlaceholderHelper {

    protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
        // 获取前缀占位符起始索引,如果前缀占位符不存在返回 -1
        int startIndex = value.indexOf(this.placeholderPrefix);
        // 检测是否包含前缀占位符,即 ${ 如果不包含(不包含则不需要解析),直接返回
        if (startIndex == -1) {
            return value;
        }
        // 创建 StringBuilder 实例对象
        StringBuilder result = new StringBuilder(value);
        // 这里循环的原因在于 可能存在 ${xx${xx}} 形式的路径,即通过判断是否包含 ${ 来控制循环结束
        while (startIndex != -1) {
            // 查找与占位符前缀对应的占位符后缀对应的索引
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            // 如果匹配到对应后缀占位符,则需要对占位符进行解析
            if (endIndex != -1) {
                // 获取占位符前缀到对应占位符后缀之间的字符串,有可能中间也包含占位符
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                // 记录最外层占位符中的字符串
                String originalPlaceholder = placeholder;
                // 如果通过参数传递的 visitedPlaceholders 为 null,由于存在递归调用,
                // 下一次执行 parseStringValue 方法时 visitedPlaceholders 不为 null
                if (visitedPlaceholders == null) {
                    // 创建已访问占位符集合,初始长度为 4,用户保存解析的占位符,如: ${xxx}
                    visitedPlaceholders = new HashSet<>(4);
                }
                // 将 originalPlaceholder 加入集合 visitedPlaceholders 表示已访问,如果添加失败,抛出异常
                // HashSet 不能存在重复元素,添加重复元素返回 false
                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);
                // 通过占位符解析器获取占位符中变量名对应系统属性中的变量值,这里也可以理解为递归调用
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                // 如果未获取到属性值,并且分隔符不为 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);
                        // 如果解析完的值为 null
                        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) {
                    // 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 {
                // 设置其实索引值为 -1,以便跳出循环
                startIndex = -1;
            }
        }
        return result.toString();
    }
}
public class PropertyPlaceholderHelper {

    // 查找与占位符前缀相对应的占位符后缀索引位置
    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        // 指定一个索引 index 值为前缀占位符索引位 + 前缀占位符长度
        int index = startIndex + this.placeholderPrefix.length();
        // 表示嵌套占位符数量
        int withinNestedPlaceholder = 0;
        // 循环条件: 索引值小于序列长度
        while (index < buf.length()) {
            // 判断占位符后缀是否与字符串指定索引 +1 位置处的字符是否匹配,如果匹配到占位符后缀
            if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
                // 如果嵌套占位符数量大于 0
                if (withinNestedPlaceholder > 0) {
                    // 嵌套占位符数量减 1
                    withinNestedPlaceholder--;
                    // 调整起始索引位置,用于解析嵌套占位符
                    index = index + this.placeholderSuffix.length();
                }
                // 不存在嵌套占位符
                else {
                    // 返回占位符后缀索引
                    return index; 
                }
            }
            // 判断简单前缀是否与字符串指定索引 +1 位置处的字符相匹配
            else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                // 嵌套占位符数量 +1
                withinNestedPlaceholder++;
                // 调整索引起始位置
                index = index + this.simplePrefix.length();
            } else {
                // 调整索引位置,继续向后匹配
                index++;
            }
        }
        return -1;
    }
}
/**
 * 抽象基类,用于解析各种基础属性资源,可以理解为一个工具类
 */
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {

    // 可配置的类型转换服务
    @Nullable
    private volatile ConfigurableConversionService conversionService;
    // 非严格的占位符解析助手
    @Nullable
    private PropertyPlaceholderHelper nonStrictHelper;
    // 严格的占位符解析助手
    @Nullable
    private PropertyPlaceholderHelper strictHelper;
    // 忽略无法解析的嵌套占位符
    private boolean ignoreUnresolvableNestedPlaceholders = false;
    // 占位符前缀 ${
    private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
    // 占位符后缀 }
    private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
    // 属性分隔符 :
    @Nullable
    private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
    // 需要的属性集合
    private final Set<String> requiredProperties = new LinkedHashSet<>();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值