• 【Spring】Spring源码中占位符解析器PropertyPlaceholderHelper的使用


    开发中经常需要使用到占位符,情况较为复杂时总是手工替换处理显得比较的繁琐,加之往往手工所写效率比不上框架自带的现有方法来的更好更快。Spring在处理yml配置文件时,对于yml文件名的占位符替换处理便是使用了占位符解析器PropertyPlaceholderHelper。本篇一起来看看Spring中这个占位符解析器PropertyPlaceholderHelper:

    PropertyPlaceholderHelper 使用起来比较简单,不需要依赖其他的一些组件类,只需要简单的 new 出来即可,最常用的方法是 replacePlaceholders 。需要注意的是它需要传入一个 Properties 实例,Properties 中的 key , value 必须都是 String 类型的,否则无法解析。

    来看这么一段代码:

    import org.springframework.util.PropertyPlaceholderHelper;
    
    publicclass PropertyPlaceholderHelperExample {
    
        public static void main(String[] args) {
            // 创建 PropertyPlaceholderHelper 实例
            PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper("${", "}", ":", true);
    
            // 定义属性文件内容
            String content = "username=${username},password=${password}";
    
            // 使用 PropertyPlaceholderHelper 解析属性文件内容
            String result = helper.replacePlaceholders(content, new Properties() {{
                setProperty("username", "admin");
                setProperty("password", "123");
            }});
    
            // 打印解析结果
            System.out.println(result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从源码可见共有两个replacePlaceholders方法,如下:

    public String replacePlaceholders(String value, final Properties properties) {
        Assert.notNull(properties, "'properties' must not be null");
        properties.getClass();
        return this.replacePlaceholders(value, properties::getProperty);
    }
    
    public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
        Assert.notNull(value, "'value' must not be null");
        return this.parseStringValue(value, placeholderResolver, (Set)null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    附PropertyPlaceholderHelper类的Spring源码:

    package org.springframework.util;
    
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Properties;
    import java.util.Set;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    /**
     * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
     * {@code ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for
     * user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or
     * using a {@link PlaceholderResolver}.
     *
     * @author Juergen Hoeller
     * @author Rob Harrop
     * @since 3.0
     */
    public class PropertyPlaceholderHelper {
    
        private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);
    
        private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
    
        static {
            wellKnownSimplePrefixes.put("}", "{");
            wellKnownSimplePrefixes.put("]", "[");
            wellKnownSimplePrefixes.put(")", "(");
        }
    
    
        private final String placeholderPrefix;
    
        private final String placeholderSuffix;
    
        private final String simplePrefix;
    
        private final String valueSeparator;
    
        private final boolean ignoreUnresolvablePlaceholders;
    
    
        /**
         * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
         * Unresolvable placeholders are ignored.
         * @param placeholderPrefix the prefix that denotes the start of a placeholder
         * @param placeholderSuffix the suffix that denotes the end of a placeholder
         */
        public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
            this(placeholderPrefix, placeholderSuffix, null, true);
        }
    
        /**
         * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
         * @param placeholderPrefix the prefix that denotes the start of a placeholder
         * @param placeholderSuffix the suffix that denotes the end of a placeholder
         * @param valueSeparator the separating character between the placeholder variable
         * and the associated default value, if any
         * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should
         * be ignored ({@code true}) or cause an exception ({@code false})
         */
        public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                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;
        }
    
        /**
         * Replaces all placeholders of format {@code ${name}} with the corresponding
         * property from the supplied {@link Properties}.
         * @param value the value containing the placeholders to be replaced
         * @param properties the {@code Properties} to use for replacement
         * @return the supplied value with placeholders replaced inline
         */
        public String replacePlaceholders(String value, final Properties properties) {
            Assert.notNull(properties, "'properties' must not be null");
            return replacePlaceholders(value, new PlaceholderResolver() {
                public String resolvePlaceholder(String placeholderName) {
                    return properties.getProperty(placeholderName);
                }
            });
        }
    
        /**
         * Replaces all placeholders of format {@code ${name}} with the value returned
         * from the supplied {@link PlaceholderResolver}.
         * @param value the value containing the placeholders to be replaced
         * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
         * @return the supplied value with placeholders replaced inline
         */
        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);
                            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();
        }
    
        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;
        }
    
    
        /**
         * Strategy interface used to resolve replacement values for placeholders contained in Strings.
         */
        public static interface PlaceholderResolver {
    
            /**
             * Resolve the supplied placeholder name to the replacement value.
             * @param placeholderName the name of the placeholder to resolve
             * @return the replacement value, or {@code null} if no replacement is to be made
             */
            String resolvePlaceholder(String placeholderName);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207

    下图这个parseStringValue方法是PropertyPlaceholderHelper占位解析的主要入口。其大致的过程是将占位的内容一块一块地取下来,通过递归将最内层的占位符先替换掉,然后跳出来替换外面的占位符。
    在这里插入图片描述

    直接附parseStringValue方法源码,注意不同Spring版本可能源码略有差异

    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) {
                //截取前缀占位符和后缀占位符之间的字符串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 string value \"" + strVal + "\"");
                }
                visitedPlaceholders.remove(originalPlaceholder);
            } else {
                startIndex = -1;
            }
        }
        System.out.println("enter..." + result);
        return result.toString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    findPlaceholderEndIndex方法也是比较的重要,它是用来寻找占位符后缀索引的。

    private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
        //获取前缀后面一个字符的索引
        int index = startIndex + this.placeholderPrefix.length();
        int withinNestedPlaceholder = 0;
        //如果前缀后面还有字符的话
        while (index < buf.length()) {
            //判断源字符串在index处是否与后缀匹配
            if (substringMatch(buf, index, this.placeholderSuffix)) {
                //如果匹配到后缀,但此时前缀数量>后缀,则继续匹配后缀
                if (withinNestedPlaceholder > 0) {
                    withinNestedPlaceholder--;
                    index = index + this.placeholderSuffix.length();
                } else {
                    return index;
                }
            } else if (substringMatch(buf, index, this.simplePrefix)) {
                //判断源字符串在index处是否与前缀匹配,若匹配,说明前缀后面还是前缀,则把前缀长度累加到index上,继续循环寻找后缀
                //withinNestedPlaceholder确保前缀和后缀成对出现后
                withinNestedPlaceholder++;
                index = index + this.simplePrefix.length();
            } else {
                //如果index出既不能和suffix又不能和simplePrefix匹配,则自增,继续循环
                index++;
            }
        }
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    Spring源码中占位符解析器PropertyPlaceholderHelper的使用就介绍到这里了,在项目中也给用起来吧!

  • 相关阅读:
    软件项目管理【UML-组件图】
    全志A40i开发板(4核ARM Cortex-A7)测评合集——Qt性能测试
    研发过程中的文档管理与工具
    电脑入门:电脑不认新硬盘时该怎么办?
    读写Raw文件
    06公共方法
    OpenCV14-图像平滑:线性滤波和非线性滤波
    昨日阅读量700
    SpringBoot整合Easy-ES实现对ES操作
    若依框架以及Mybatis-plus分页插件失效,数据库有多条却只查前十条
  • 原文地址:https://blog.csdn.net/weixin_42369773/article/details/138015055