Spring源码分析ClassPathXmlApplicationContext获取配置信息(refresh之前)
ClassPathXmlApplicationContext
// xml的一种bean配置方式
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
基于上述代码,分析获取配置信息部分,即refresh方法之前的部分。
继承关系
部分说明如下:
- ClassPathXmlApplicationContext:Resource配置信息,基于本文配置方式不涉及针对Resource配置的构造方法。后续补充该方式配置。
- AbstractRefreshableConfigApplicationContext:本文设计的配置信息存储在该类中(configLocations),同时此类实现BeanNameAware和InitializingBean接口,BeanNameAware作用是感知bean的id,InitializingBean的作用是初始化bean时自动调用afterPropertiesSet方法。
- AbstractRefreshableApplicationContext:提供是否允许BeanDefinition覆盖和循环引用的开关,同时包含DefaultListableBeanFactory,该工厂实现批量、分层、自动装配等功能,是功能强大的BeanFactory。
构造方法
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
在本文配置方法中,refresh=true,parent=null。
本文主要解析setConfigLocations(configLocations); 这行代码,也就是refresh核心方法之前的处理。
构造方法向上追溯到AbstractApplicationContext类
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
继续追踪this()默认构造
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
PathMatchingResourcePatternResolver为Ant风格资源解析器,三种Ant通配符如下:
通配符 | 作用 |
---|---|
? | 匹配任何单字符 |
* | 匹配0或者任意数量的字符 |
** | 匹配0或者更多目录 |
获取配置信息
之前继承关系图中AbstractRefreshableConfigApplicationContext中可存储配置信息,因此该类实现setConfigLocations方法:
public void setConfigLocations(@Nullable 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;
}
}
跟进解析路径方法resolvePath
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
根据方法名可知,首先获取环境信息,然后替代配置路径中的占位符,也就是解析路径。
获取环境信息
AbstractApplicationContext中实现getEnvironment方法
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
关注createEnvironment方法
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
实际上是创建了一个StandardEnvironment对象。
StandardEnvironment
这里只分析StandardEnvironment和AbstractEnvironment两个类,目的是完成第一步getEnvironment获取环境信息。
创建StandardEnvironment首先看AbstractEnvironment抽象类构造方法:
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
propertySources为AbstractEnvironment中的定义的成员变量,用于存储属性资源,定义如下:
private final MutablePropertySources propertySources = new MutablePropertySources();
MutablePropertySources理解为可变属性资源,实现了PropertySources接口,可以理解为属性资源的key-value对。其中资源存储采用CopyOnWriteArrayList。
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
customizePropertySources
明白了在哪存储资源,再看如何向资源容器中添加资源:
protected void customizePropertySources(MutablePropertySources propertySources) {
}
AbstractEnvironment中声明该方法,在StandardEnvironment中实现:
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
- getSystemProperties方法最终调用System.getProperties(),获取java相关属性,包括jdk、jvm等信息。
- getSystemEnvironment方法最终调用System.getenv(),获取系统环境变量信息。
- 环境变量是在OS级别指定的。
- Java属性通过将-D选项传递给JVM来指定(并且可以通过编程方式设置)。
至此,环境信息获取完成,然后进行解析,调用resolveRequiredPlaceholders方法。
解析路径
重新定位到AbstractRefreshableConfigApplicationContext类的resolvePath方法:
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
getEnvironment()已经分析完成,创建了一个StandardEnvironment对象,获取到环境信息。resolveRequiredPlaceholders方法在PropertyResolver接口中声明,接下来分析该方法在何处调用。
PropertyResolver类图
此图在之前StandardEnvironment继承关系的基础上延伸了属性解析器的具体实现类。
AbstractEnvironment
AbstractEnvironment中实现解析方法如下:
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
此类中包含一个propertyResolver成员,定义如下:
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
对应上面的类图,propertyResolver为属性解析器对象,属性资源propertySources即为前文中存储属性资源的MutablePropertySources对象。
AbstractPropertyResolver
有了属性解析器propertySource(PropertySourcesPropertyResolver类),也有了属性资源propertySource(MutablePropertySources类),可以开始执行资源解析,解析操作在AbstractPropertyResolver中来完成。
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
该类中包含一个工具类PropertyPlaceholderHelper,作用是处理属性的占位符,占位符是什么东西,继续跟创建该工具类的方法createPlaceholderHelper:
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
此处调用了PropertyPlaceholderHelper的构造器,传入的参数信息如下:
参数名 | 参数值 | 作用 |
---|---|---|
placeholderPrefix | ${ | 占位符前缀 |
placeholderSuffix | } | 占位符后缀 |
valueSeparator | : | 冒号,后接占位符指代内容 |
ignoreUnresolvablePlaceholders | false | 是否忽略不可解析的占位符 |
接下来看一下PropertyPlaceholderHelper的构造器:
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;
String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
this.simplePrefix = simplePrefixForSuffix;
}
else {
this.simplePrefix = this.placeholderPrefix;
}
this.valueSeparator = valueSeparator;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
其中simplePrefix参数表示基本的占位符类型前缀,在wellKnownSimplePrefixes中定义了三种常见占位符类型:
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<>(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
有了属性占位符解析工具PropertyPlaceholderHelper,下面开始解析,首先进入doResolvePlacehoders方法:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
这里发现具体的解析操作时交给解析工具PropertyPlaceholderHelper完成,参数包括需要解析的内容text和一个lambda表达式的方法getPropertyAsRawString(此方法后面分析,这里先不用管)。
PropertyPlaceholderHelper
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
这里参数为PlaceholderResolver,这是一个策略接口,用于解析占位符中需要替换的值。
看一下PlaceholderResolver接口的定义:
/**
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
*/
@FunctionalInterface
public interface PlaceholderResolver {
/**
* Resolve the supplied placeholder name to the replacement value.
* @param placeholderName the name of the placeholder to resolve
* @return the replacement value, or {@code null} if no replacement is to be made
*/
@Nullable
String resolvePlaceholder(String placeholderName);
}
@FunctinalInterface表示这是一个函数式接口,只能有一个抽象方法,这里结合lambda表达式调用,后面可以看到。
继续分析parseStringValue方法:
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
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 == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
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) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
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();
}
这里是具体的解析算法,采用递归解析的方式,对算法感兴趣可以详细看一下。我们重点关注调用的resolvePlaceholder方法,实际上就是调用的AbstractPropertyResolver类中的getPropertyAsRawString方法,该方法在PropertySourcesPropertyResolver中具体实现:
@Override
@Nullable
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
继续看getProperty方法,通过占位符名字,从属性资源中获取对应的值:
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.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;
}
从这里可以看出,因为之前获取的环境资源存储在PropertySourcesPropertyResolver类中的PropertySources类型的成员变量中,所以在这里进行具体的占位符名称的替换操作。
最终,将解析好的字符串存储在AbstractRefreshableConfigApplicationContext中:
@Nullable
private String[] configLocations;
后续的refresh部分,会从这里拿配置信息进行注册Bean等操作。