1. 程式人生 > >占位符解析

占位符解析

variable 需要 prefix lse wstring build 方便 req sequence

占位符解析過程

占位符解析器
/**
 * 從指定的屬性源中,將占位符解析為具體的值
 */
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

占位符解析