配置文件路径解析
当执行Spring应用程序的时候,首先遇见的就是路径解析问题。关键代码如下:
// 设置xml配置文件路径
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
// 占位符号解析和替换
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
在“占位符解析和替换”代码部分中,需要掌握的的知识就是Enviroment,可以看到getEnvironment()之后又调用了resolveRequiredPlaceholders函数,这个地方就是占位符解析和替换的工作。接下来要了解的知识就是“Spring环境、属性和占位符”。
环境和属性
Spring 环境和属性由四个部分组成:
- PropertySource:属性源。key-value 属性对抽象,用于配置数据。
- PropertyResolver:属性解析器。用于解析属性配置。
- Profile:剖面。只有被激活的Profile才会将其中所对应的Bean注册到Spring容器中
- Environment:环境。Profile 和 PropertyResolver 的组合。
类名 | 描述 |
---|---|
PropertySource | 提供了可配置属性源上的搜索操作。 |
PropertyResolver | 属性解析器,用于解析任何基础源的属性的接口 |
ConfigurablePropertyResolver | 提供属性类型转换的功能 |
AbstractPropertyResolver | 解析属性文件的抽象基类。设置了解析属性文件所需要ConversionService、prefix、suffix、valueSeparator等信息。 |
PropertySourcesPropertyResolver | PropertyResolver 的实现,对一组 PropertySources 提供属性解析服务 |
ConversionService | 用于在运行时执行类型转换 |
Environment | 集成在容器中的抽象,它主要包含两个方面:Profiles和Properties |
ConfigurableEnvironment | 设置激活的 profile 和默认的 profile 的功能以及操作 Properties 的工具 |
PropertySource
PropertySource是在Spring Environment之上提供了可配置属性源上的搜索操作。
public abstract class PropertySource<T> {
//属性源的名字
public String getName()
//属性源(比如来自Map,那就是一个Map对象)
public T getSource()
//是否包含某个属性
public boolean containsProperty(String name)
//得到属性名对应的属性
public abstract Object getProperty(String name)
}
搜索过程是按照层次结构执行的。默认情况下,系统属性优先于环境变量。因此,在调用env.getProperty(“foo”)时,如果在系统属性和环境变量中都设置了foo属性,则系统变量将优先于环境变量。完整层次结构如下所示,优先级最高的条目位于顶部:
- ServletConfig参数
- ServletContext参数
- JNDI环境变量(如:“java:comp/env/”)
- JVM system properties("-D"命令行参数,如-Dfoo=“abcd”)
- JVM system environment(操作系统环境变量)
请注意,属性值不会被合并,而是会被前面的条目覆盖。
PropertyResolver
属性解析器,用于解析任何基础源的属性的接口
public interface PropertyResolver {
// 检测是否包含指定的key
boolean containsProperty(String key);
// 返回指定key对应的value,如果没有则返回null
String getProperty(String key);
// 返回指定key对应的value,如果没有则返回defaultValue
String getProperty(String key, String defaultValue);
// 返回指定key对应的value,并解析成指定类型。如果没有对应值则返回null
<T> T getProperty(String key, Class<T> targetType);
// 返回指定key对应的value,并解析成指定类型。如果没有对应值则返回defaultValue
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
// 转换指定key的value为指定类型。如果没有则返回null。
// 如果value不能转换成指定类型,则抛出ConversionException
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
// 返回指定key的value值,如果没有则抛出异常
String getRequiredProperty(String key) throws IllegalStateException;
// 转换指定key的value为指定类型,如果没有则抛出异常
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
// 解析${}占位符并替换为getProperty方法返回的结果,无法解析的占位符会被忽略
String resolvePlaceholders(String text);
// 解析${}占位符并替换为getProperty方法返回的结果,无法解析的占位符会抛异常
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
解析过程详解
第一步
AbstractRefreshableConfigApplicationContext.java
public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
第二步
AbstractRefreshableConfigApplicationContext.java
// 路径解析
protected String resolvePath(String path) {
// 从环境中解析path中的占位符
return getEnvironment().resolveRequiredPlaceholders(path);
}
第三步
AbstractEnvironment.java
// 对占位符进行解析
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
第四步
AbstractPropertyResolver.java
// 创建占位符解析工具类
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
第五步
PropertyPlaceholderHelper.java
// 调用占位符解析函数
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
// 占位符解析和替换文本的函数
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// 查找占位符前缀
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
// 查找占位符后缀
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
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 && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 递归调用,解析先前解析的占位符值中包含的占位符
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}