占位符解析
阿新 • • 發佈:2019-01-16
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
占位符解析