占位符解析

占位符解析过程

占位符解析器
/**
 * 从指定的属性源中,将占位符解析为具体的值
 */
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

原文地址:https://www.cnblogs.com/zhuxudong/p/10274401.html

时间: 2024-10-09 09:48:02

占位符解析的相关文章

spring源码解析(一)---占位符解析替换

一.结构类图 ①.PropertyResolver : Environment的顶层接口,主要提供属性检索和解析带占位符的文本.bean.xml配置中的所有占位符例如${}都由它解析 ②.ConfigurablePropertyResolver : 该接口定义了如何对组件本身进行配置.如:刚刚提到获取value时可以指定任意类型,这依赖于ConversionService进行类型转换,当前接口就提供了对ConversionService的设置和获取.另外,可以配置属性占位符的格式,包括:占位符前

spring占位符解析器---PropertyPlaceholderHelper

一.PropertyPlaceholderHelper 职责 扮演者占位符解析器的角色,专门用来负责解析路劲中or名字中的占位符的字符,并替换上具体的值 二.例子 public class PropertyPlaceholderHelperDemo { @SuppressWarnings("resource") public static void main(String[] args) { Properties properties = System.getProperties();

Spring PropertyResolver 占位符解析(一)API 介绍

Spring PropertyResolver 占位符解析(一)API 介绍 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) Spring 3.1 提供了新的占位符解析器 PropertyResolver,默认实现为 PropertySourcesPropertyResolver.相关文章如下: Spring PropertyResolver 占位符解析(一)API 介绍 Spring PropertyResolver

解析占位符的替换算法

#解析占位符替换的算法, <property name="url" value="${url}"/> //text 为输入占位符 如:${url} public String parse(String text) {     StringBuilder builder = new StringBuilder();     if (text != null && text.length() > 0) {       char[] sr

SPRING多个占位符配置文件解析源码研究--转

原文地址:http://www.cnphp6.com/archives/85639 Spring配置文件: <context:property-placeholder location="classpath:/settings.properties" /> <context:property-placeholder location="classpath:/conf.properties"/> settings.properties redi

配置数据源无法解析占位符

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p&

【Spring源码分析】.properties文件读取及占位符${...}替换源码解析

前言 我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.properties文件中配置的参数使用占位符"${}"替换的方式读入并设置到Bean的相应参数中. 这种做法最典型的就是JDBC的配置,本文就来研究一下.properties文件读取及占位符"${}"替换的源码,首先从代码入手,定义一个DataSource,模拟一下JDB

spring加载配置文件无法解析占位符问题:Could not resolve placeholder &#39;from&#39; in string value &quot;${from}&quot;

Could not resolve placeholder 'from' in string value "${from}" 解决: 在spring的xml配置文件中当有多个*.properties文件需要加载时, 应当集中在一个xml文件中加载,建议在主xml文件中加载,即(applicationContext.xml)中加载, 这样就不需要关注 子xml文件 与 *.properties 加载顺序问题 加载多个*.properties文件,以','隔开 <context:pr

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

1.增加SQL代码可读性2.占位符可以预先编译,提高执行效率3.防止SQL注入4用占位符的目的是绑定变量,这样可以减少数据SQL的硬解析,所以执行效率会提高不少 绑定变量是Oracle解决硬解析的首要利器,能解决OLTP系统中library cache的过度耗用以提高性能 绑定变量是Oracle解决硬解析的首要利器,能解决OLTP系统中library cache的过度耗用以提高性能.然刀子磨的太快,使起来锋利,却容易折断.凡事皆有利弊二性,因地制宜,因时制宜,全在如何权衡而已.本文讲述了绑定变量