zoukankan      html  css  js  c++  java
  • spring源码解析(一)---占位符解析替换

     一、结构类图

     

    ①、PropertyResolver : Environment的顶层接口,主要提供属性检索和解析带占位符的文本。bean.xml配置中的所有占位符例如${}都由它解析

    ②、ConfigurablePropertyResolver : 该接口定义了如何对组件本身进行配置。如:刚刚提到获取value时可以指定任意类型,这依赖于ConversionService进行类型转换,当前接口就提供了对ConversionService的设置和获取。另外,可以配置属性占位符的格式,包括:占位符前缀(默认为"${")、占位符后缀(默认为"}")、占位符值分隔符(默认为":",用于分隔propertyName和defaultValue)。组件还可以设置哪些属性是必须存在的,还可以校验必须存在的属性是否真的存在(不存在的话会抛出异常)

    ③、AbstractPropertyResolver : 实现了ConfigurablePropertyResolver接口的所有方法

    ④、PropertySourcesPropertyResolver : 以PropertySources属性源集合(内部持有属性源列表List<PropertySource>)为属性值的来源,按序遍历每个PropertySource,获取到一个非null的属性值则返回

    二、demo示例

    public static void main(String[] args) {
            Properties properties = System.getProperties();
            
            properties.setProperty("prefixName", "read-code");
            
            ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:${prefixName}-spring.xml");
            
            ReadCodeService readCodeService = (ReadCodeService) ac.getBean("readCodeService");
            
            readCodeService.say();
        }
    View Code

    三、源码剖析

    1、入口 : 

    ClassPathXmlApplicationContext 构造函数setConfigLocations
    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
                throws BeansException {
    
            super(parent);
            setConfigLocations(configLocations);
            if (refresh) {
                refresh();
            }
        }

    2、AbstractRefreshableConfigApplicationContext

    ①、ClassPathXmlApplicationContext构造函数调用它的基类AbstractRefreshableConfigApplicationContext.setConfigLocations

        /**
         * Set the config locations for this application context.
         * <p>If not set, the implementation may use a default as appropriate.
         */
        public void setConfigLocations(String... locations) {
            if (locations != null) {
                Assert.noNullElements(locations, "Config locations must not be null");
                this.configLocations = new String[locations.length];
                for (int i = 0; i < locations.length; i++) {
                    this.configLocations[i] = resolvePath(locations[i]).trim(); // 解析路劲
                }
            }
            else {
                this.configLocations = null;
            }
        }

    ②、解析路劲

        /**
         * Resolve the given path, replacing placeholders with corresponding
         * environment property values if necessary. Applied to config locations.
         * @param path the original file path
         * @return the resolved file path
         * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
         */
        protected String resolvePath(String path) {
            return getEnvironment().resolveRequiredPlaceholders(path);
        }

    3、AbstractPropertyResolver

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

     上述方法主要做了两件事 : 

    ①、初始化占位符解析器

    createPlaceholderHelper : 主要是初始化占位符的常量,eg : 前缀 ${  后缀} and so on

    ②、调用私有方法---替换占位符具体值

    private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
            return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
                @Override
                public String resolvePlaceholder(String placeholderName) {
                    return getPropertyAsRawString(placeholderName);
                }
            });
        }

     4、占位符 key - > value , 

    实现PropertyPlaceholderHelper内部接口PlaceholderResolver方法resolvePlaceholder。找到占位符key对应的value,为下文替换key埋下伏笔
    protected String getPropertyAsRawString(String key) {
            return getProperty(key, String.class, false);
        }

    代码太多了,这里只给出重点

        protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
            boolean debugEnabled = logger.isDebugEnabled();
            if (logger.isTraceEnabled()) {
                logger.trace(String.format("getProperty("%s", %s)", key, targetValueType.getSimpleName()));
            }
            if (this.propertySources != null) {
                for (PropertySource<?> propertySource : this.propertySources) {
                    if (debugEnabled) {
                        logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
                    }
                    Object value;
                    if ((value = propertySource.getProperty(key)) != null) {
                        Class<?> valueType = value.getClass();
                        if (resolveNestedPlaceholders && value instanceof String) {
                            value = resolveNestedPlaceholders((String) value);
                        }
                        if (debugEnabled) {
                            logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
                                    key, propertySource.getName(), valueType.getSimpleName(), value));
                        }
                        if (!this.conversionService.canConvert(valueType, targetValueType)) {
                            throw new IllegalArgumentException(String.format(
                                    "Cannot convert value [%s] from source type [%s] to target type [%s]",
                                    value, valueType.getSimpleName(), targetValueType.getSimpleName()));
                        }
                        return this.conversionService.convert(value, targetValueType);
                    }
                }
            }
            if (debugEnabled) {
                logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
            }
            return null;
        }
    View Code

     5、占位符解析器, 解析并替换具体值得逻辑在这里

    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
            Assert.notNull(value, "'value' must not be null");
            return parseStringValue(value, placeholderResolver, new HashSet<String>());
        }

     递归查找占位符

    protected String parseStringValue(
                String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
    
            StringBuilder result = new StringBuilder(strVal);
    
            int startIndex = strVal.indexOf(this.placeholderPrefix);
            while (startIndex != -1) {
                int endIndex = findPlaceholderEndIndex(result, startIndex);
                if (endIndex != -1) {
                    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...
                    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); // 这里是调用第四步骤的实现PropertyPlaceholderHelper内部接口PlaceholderResolver方法resolvePlaceholder :占位符 key -> value
                            if (propVal == null) {
                                propVal = defaultValue;
                            }
                        }
                    }
                    if (propVal != null) {
                        // Recursive invocation, parsing placeholders contained in the
                        // previously resolved placeholder 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 string value "" + strVal + """);
                    }
                    visitedPlaceholders.remove(originalPlaceholder);
                }
                else {
                    startIndex = -1;
                }
            }
    
            return result.toString();
        }

     findPlaceholderEndIndex 查找占位符在所在字符串后缀的位置

    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
            int index = startIndex + this.placeholderPrefix.length();
            int withinNestedPlaceholder = 0;
            while (index < buf.length()) {
                if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
                    if (withinNestedPlaceholder > 0) {
                        withinNestedPlaceholder--;
                        index = index + this.placeholderSuffix.length();
                    }
                    else {
                        return index;
                    }
                }
                else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
                    withinNestedPlaceholder++;
                    index = index + this.simplePrefix.length();
                }
                else {
                    index++;
                }
            }
            return -1;
        }
    View Code

    StringUtis.substringMatch 匹配当前位置的字符是否为占位符后缀

    public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
            for (int j = 0; j < substring.length(); j++) {
                int i = index + j;
                if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
                    return false;
                }
            }
            return true;
        }
    View Code
  • 相关阅读:
    【逆向】《0day安全-软件漏洞分析技术》实验笔记2
    【逆向】《0day安全-软件漏洞分析技术》实验笔记1
    WorkWithPlus 13.15 升级!列表对象的优化效果显著!
    「版本更新」Genexus 16 Upgrade 9已发布!
    下一波数字化转型来临,我们需要选择更智能的开发工具
    太棒!企业和程序员都高兴!2-3个月打造全栈工程师
    GeneXus中如何使用聊天机器人
    「版本更新」GeneXus16 Upgrade 8的特性
    看完视频我终于明白:资深架构师角度的技术架构是这样!
    数据报告和分析:Dashboard
  • 原文地址:https://www.cnblogs.com/chenmo-xpw/p/5575571.html
Copyright © 2011-2022 走看看