zoukankan      html  css  js  c++  java
  • Spring PropertyResolver 占位符解析(二)源码分析

    Spring PropertyResolver 占位符解析(二)源码分析

    Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

    Spring 3.1 提供了新的占位符解析器 PropertyResolver,默认实现为 PropertySourcesPropertyResolver。相关文章如下:

    1. Spring PropertyResolver 占位符解析(一)API 介绍
    2. Spring PropertyResolver 占位符解析(二)源码分析

    PropertyResolver 类图

    一、PropertyResolver 接口

    PropertyResolver 的默认实现是 PropertySourcesPropertyResolver,Environment 实际上也是委托 PropertySourcesPropertyResolver 完成 占位符的解析和类型转换。 类型转换又是委托 ConversionService 完成的。

    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 是配置了一些解析占位符的必要属性,如占位符前缀和后缀等

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

    二、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) 这个方法

    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 实现的。

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

    三、PropertyPlaceholderHelper 嵌套占位符的解析

    在看 PropertyPlaceholderHelper 之前先看一下 PlaceholderResolver 这个内部类,这个类用于获取占位符 key 对应的 value。在 AbstractPropertyResolver#doResolvePlaceholders 方法中将 this::getPropertyAsRawString 传过来了,也就是说 PlaceholderResolver 是从 propertySources 获取对应的 value 值。

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

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

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

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    【CSS 第五天】背景,边框
    CSS Sprite雪碧图
    【ASP】session实现购物车
    【ASP】response和sever对象实现用户登录
    【操作系统】银行家算法
    【操作系统】先来先服务
    【操作系统】多级反馈队列算法
    【页面置换算法】LRC算法和FIFS算法
    Alpha版(内部测试版)发布
    项目结束--事后诸葛亮会议总结
  • 原文地址:https://www.cnblogs.com/binarylei/p/10284838.html
Copyright © 2011-2022 走看看