占位符解析

Posted zhuxudong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了占位符解析相关的知识,希望对你有一定的参考价值。

占位符解析过程

占位符解析器
/**
 * 从指定的属性源中,将占位符解析为具体的值
 */
public class PropertyPlaceholderHelper {

    private static final Log logger = LogFactory.getLog(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;
    /**
     * 默认值分隔符
     */
    @Nullable
    private final String valueSeparator;
    /**
     * 是否忽略无法解析的占位符
     */
    private final boolean ignoreUnresolvablePlaceholders;

    public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
        this(placeholderPrefix, placeholderSuffix, null, true);
    }

    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;
        final String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
        if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
            simplePrefix = simplePrefixForSuffix;
        }
        else {
            simplePrefix = this.placeholderPrefix;
        }
        this.valueSeparator = valueSeparator;
        this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
    }

    /**
     * 将占位符替换为具体的值
     */
    public String replacePlaceholders(String value, final Properties properties) {
        Assert.notNull(properties, "‘properties‘ must not be null");
        return replacePlaceholders(value, properties::getProperty);
    }

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

    protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
        final StringBuilder result = new StringBuilder(value);
        // 读取占位符前缀在目标字符串中的索引
        int startIndex = value.indexOf(placeholderPrefix);
        while (startIndex != -1) {
            // 读取占位符后缀在目标字符串中的索引
            final int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                // 提取占位符值
                String placeholder = result.substring(startIndex + placeholderPrefix.length(), endIndex);
                final String originalPlaceholder = placeholder;
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference ‘" + originalPlaceholder + "‘ in property definitions");
                }
                // 递归解析占位符中包含的占位符
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // 将占位符解析为具体的值
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                if (propVal == null && valueSeparator != null) {
                    /**
                     *  如果未找到,则尝试进行 ${name:zxd} 格式的解析
                     *  读取值分隔符索引
                     */
                    final int separatorIndex = placeholder.indexOf(valueSeparator);
                    if (separatorIndex != -1) {
                        // 截取实际的占位符
                        final String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        // 截取默认值
                        final String defaultValue = placeholder.substring(separatorIndex + 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 + placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder ‘" + placeholder + "‘");
                    }
                    startIndex = result.indexOf(placeholderPrefix, startIndex + propVal.length());
                }
                else if (ignoreUnresolvablePlaceholders) {
                    // Proceed with unprocessed value.
                    startIndex = result.indexOf(placeholderPrefix, endIndex + placeholderSuffix.length());
                }
                else {
                    throw new IllegalArgumentException("Could not resolve placeholder ‘" +
                            placeholder + "‘" + " in value "" + value + """);
                }
                // 移除解析过的占位符
                visitedPlaceholders.remove(originalPlaceholder);
            }
            else {
                startIndex = -1;
            }
        }

        // 返回结果值
        return result.toString();
    }

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

    /**
     * 策略接口:用于将占位符替换为具体的值
     */
    @FunctionalInterface
    public interface PlaceholderResolver {
        /**
         * 将占位符替换为具体的值
         */
        @Nullable
        String resolvePlaceholder(String placeholderName);
    }
}

属性源

  • org.springframework.core.env.PropertySource:命名属性源
public abstract class PropertySource<T> {
    protected final Log logger = LogFactory.getLog(getClass());
    /**
     * 属性源名称
     */
    protected final String name;
    /**
     * 具体的属性源:
     * java.util.Properties
     * java.util.Map
     * javax.servlet.ServletContext
     * javax.servlet.ServletConfig
     */
    protected final T source;

    /**
     * Return the name of this {@code PropertySource}.
     */
    public String getName() {
        return this.name;
    }

    /**
     * Return the underlying source object for this {@code PropertySource}.
     */
    public T getSource() {
        return this.source;
    }

    /**
     * Return whether this {@code PropertySource} contains the given name.
     */
    public boolean containsProperty(String name) {
        return getProperty(name) != null;
    }

    /**
     * Return the value associated with the given name, or {@code null} if not found.
     */
    @Nullable
    public abstract Object getProperty(String name);
}
  • org.springframework.context.annotation.PropertySource:方便加载资源的注解
/**
 *  以声明的方式将指定的属性文件解析为 PropertySource 并加入到 Environment 中,
 *  多个属性文件中存在相同的键时,后加入的键值对将覆盖先加入的。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {

    /**
     * 属性源的名称,如果未指定,则使用 org.springframework.core.io.Resource#getDescription() 生成。
     */
    String name() default "";

    /**
     * 指定资源的路径,例如 classpath:/config/app.properties 或 file:/path/to/file.xml
     * 资源路径不支持通配符,一个路径只能精确加载一个资源文件。
     * ${...} 占位符将基于已经注册的属性进行解析,解析成功后再加载资源。
     */
    String[] value();

    /**
     * 是否忽略不出在的资源文件
     */
    boolean ignoreResourceNotFound() default false;

    /**
     * 资源文件的编码 "UTF-8"
     */
    String encoding() default "";

    /**
     * 生成 PropertySource 的工厂类和具体的实例类型
     * @see org.springframework.core.io.support.DefaultPropertySourceFactory
     * @see org.springframework.core.io.support.ResourcePropertySource
     */
    Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
  • org.springframework.core.env.PropertySources:聚合一个或多个属性源
/**
 *  封装了一个或多个 PropertySource 实例
 */
public interface PropertySources extends Iterable<PropertySource<?>> {
    /**
     * Return a sequential {@link Stream} containing the property sources.
     * @since 5.1
     */
    default Stream<PropertySource<?>> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

    /**
     * 是否包含指定名称的属性源
     */
    boolean contains(String name);

    /**
     * 根据名称读取属性源
     */
    @Nullable
    PropertySource<?> get(String name);
}
  • org.springframework.context.annotation.PropertySources:声明式添加一个或多个属性源
/**
 * Container annotation that aggregates several {@link PropertySource} annotations.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
    PropertySource[] value();
}

属性解析器

  • org.springframework.core.env.PropertyResolver:属性解析器
/**
 * 基于底层的数据源解析目标属性
 */
public interface PropertyResolver {
    /**
     * 底层数据源是否包含目标属性
     */
    boolean containsProperty(String key);
    /**
     * 根据指定的 key 读取属性值,如果不存在,则返回 null
     */
    @Nullable
    String getProperty(String key);
    /**
     * 根据指定的 key 读取属性值,如果不存在,则返回 defaultValue
     */
    String getProperty(String key, String defaultValue);

    /**
     * 根据指定的 key 读取属性值,并将其转换为 T 类型
     */
    @Nullable
    <T> T getProperty(String key, Class<T> targetType);

    /**
     * 根据指定的 key 读取属性值,并将其转换为 T 类型,如果不存在,则返回 defaultValue
     */
    <T> T getProperty(String key, Class<T> targetType, T defaultValue);

    /**
     * 根据指定的 key 读取属性值,如果值不存在,则抛出 IllegalStateException 异常
     */
    String getRequiredProperty(String key) throws IllegalStateException;

    /**
     * 根据指定的 key 读取属性值,并将其转换为 T 类型,如果值不存在,则抛出 IllegalStateException 异常
     */
    <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;

    /**
     * 将占位符解析为具体的属性值,
     * 无法解析的占位符 && 无默认值,则原样返回
     */
    String resolvePlaceholders(String text);

    /**
     * 将占位符解析为具体的属性值,
     * 无法解析的占位符 && 无默认值将抛出 IllegalArgumentException 异常
     */
    String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
  • org.springframework.core.env.PropertySourcesPropertyResolver:属性源属性解析器
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
    /**
     * 1)environmentProperties:StandardServletEnvironment
     *  属性来源及优先级
     *  configurationProperties
     *  commandLineArgs
     *  servletConfigInitParams
     *  servletContextInitParams
     *  systemProperties
     *  systemEnvironment
     *  random
     * 2)localProperties:自定义属性文件
     */
    @Nullable
    private final PropertySources propertySources;

    public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
        this.propertySources = propertySources;
    }

    /**
     * 是否包含指定的属性
     */
    @Override
    public boolean containsProperty(String key) {
        if (propertySources != null) {
            for (final PropertySource<?> propertySource : propertySources) {
                if (propertySource.containsProperty(key)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 读取属性值
     */
    @Override
    @Nullable
    public String getProperty(String key) {
        return getProperty(key, String.class, true);
    }

    /**
     * 读取属性值
     */
    @Override
    @Nullable
    public <T> T getProperty(String key, Class<T> targetValueType) {
        return getProperty(key, targetValueType, true);
    }

    /**
     * 读取属性值
     */
    @Override
    @Nullable
    protected String getPropertyAsRawString(String key) {
        return getProperty(key, String.class, false);
    }

    @Nullable
    protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        if (propertySources != null) {
            for (final PropertySource<?> propertySource : propertySources) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Searching for key ‘" + key + "‘ in PropertySource ‘" +
                            propertySource.getName() + "‘");
                }
                // 从当前属性源中读取属性
                Object value = propertySource.getProperty(key);
                if (value != null) {
                    // 如果存在 && 需要解析内嵌的占位符
                    if (resolveNestedPlaceholders && value instanceof String) {
                        value = resolveNestedPlaceholders((String) value);
                    }
                    logKeyFound(key, propertySource, value);
                    // 将读取的属性转换为合适的类型
                    return convertValueIfNecessary(value, targetValueType);
                }
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Could not find key ‘" + key + "‘ in any property source");
        }
        return null;
    }

    /**
     * Log the given key as found in the given {@link PropertySource}, resulting in
     * the given value.
     */
    protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
        if (logger.isDebugEnabled()) {
            logger.debug("Found key ‘" + key + "‘ in PropertySource ‘" + propertySource.getName() +
                    "‘ with value of type " + value.getClass().getSimpleName());
        }
    }
}

实例

@RestController
@RequestMapping("/resolver")
public class PlaceholdersController {
    @Autowired
    private StandardEnvironment environment;

    @GetMapping("/{placeHolder}")
    public String resolve(@PathVariable("placeHolder") String placeHolder) {
        return environment.resolvePlaceholders(placeHolder);
    }
}

http://localhost:8080/resolver/${local.server.port} 返回 8080

以上是关于占位符解析的主要内容,如果未能解决你的问题,请参考以下文章

无法解析弹性 beantalk 中字符串值中的占位符

SQl语句中使用占位符的优点

解析文件以使用值填充占位符

spring源码解析---占位符解析替换

Spring PropertyResolver 占位符解析源码分析

spring占位符解析器---PropertyPlaceholderHelper