占位符解析
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
以上是关于占位符解析的主要内容,如果未能解决你的问题,请参考以下文章