本章是学习spring5.0.5初始化源码的第二章,前一章《spring5.0.5初始化源码三部曲之一:AbstractApplicationContext构造方法》对AbstractApplicationContext的初始化做了分析,本章我们聚焦ClassPathXmlApplicationContext.setConfigLocation()方法;
整体概括
本章会涉及到多个类的细节,所有先从整体概括AbstractRefreshableConfigApplicationContext方法的主要功能,后面的细节都是基于这些总结展开的:
- setConfigLocations主要工作有两个:创建环境对象ConfiguableEnvironment,处理ClassPathXmlApplicationContext传入的字符串的占位符;
- 环境对象ConfiguableEnvironment中包含了当前JVM的profile配置信息,环境变量,java进程变量;
- 处理占位符的关键是ConfiguableEnvironment,PropertyResolver,PropertyPlaceHolderHelper之间的配合:
名称 | 作用 |
---|---|
ConfiguableEnvironment | 1:创建PropertyResolver对象; 2:向PropertyResolver提供环境变量,java进程变量; |
PropertyResolver | 1:创建PropertyPlaceHolderHelper对象; 2:定位占位符的前缀与后缀(PlaceHolderPrefix,PlaceHodlerSuffix) 3:提供getPropertyAsRawString方法给PropertyPlaceHolderHelper方法调用,用来获取指定key对应的环境; |
PropertyPlaceHolderHelper | 1:找到字符串中的占位符; 2:用环境变量的值替换占位符; |
用思维导图来辅助:
展开详情
接下来去阅读setConfiguableLocations方法内部的细节代码:
- 跟踪该方法,找到是在类AbstractRefreshableConfigApplicationContext中实现的:
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++) {
// resolvePath为同一个类中将字符串解析为路劲的方法
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
从上述代码可以发现,本章要重点学习的是resolvePath(locations[i]),结合上一章demo的入参,此处应该是方法resolvePath(“classpath:applicationContext.xml”);
- 跟踪到AbstractRefreshableConfigApplicationContext类,这个方法的目的是替换到path字符串中的占位符${xxxx}这样的内容:
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
- 先看getEnvironment()方法:
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
-
关于ConfigurableEnvironment 接口,我们先来看看它的声明方法和继承关系:
从上图可见:ConfiguableEnvironment接口有两个重要组成部分:Profile和Property;
Profile是对测试,生产等不同环境下bean的配置,这里我们没有特殊设置,所有用到Profile是AbstractApplicationContext类的defualtProfile;
接下来Property资源是如何产生的; -
顺着调用一路看下去,发现最终会调用AbstractEnvironment类的构造方法:
public AbstractEnvironment() {
this.propertySources = new MutablePropertySources(this.logger);
this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
this.customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Initialized " + this.getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}
- 上面的customizePropertySources(this.propertySources)是在StandardEnvironment类实现的:
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
上述代码中,可以把propertySources理解成一个容器(其对象内部核心成员propertySourceList是CopyOnWriteArrayList实例);
首先向propertySourceList添加一组属性,来自java进程变量(getSystemProperties()内是(Map) System.getProperties()方法);
接着向propertySourceList再添加一组属性,来自系统环境变量(getSystemEnvironment()内是Map) System.getenv()方法);
getSystemPropertys和getSystemEnvironment有个相同的细节需要注意,在获取进程变量或系统环境变量的时候,都有可能因为安全限制而抛出异常,这时候返回一个ReadOnlySystemAttributesMap实现类,外部调用get方法的时候,在去尝试获取进程变量或系统环境变量对应的值,取不到则返回null,代码如下:
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemEnvironment() {
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
return (Map) System.getenv();
}
catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getenv(attributeName);
}
catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system environment variable '" +
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
}
- StandardEnvironment对象创建成功后,接着看它的resolveRequiredPlaceholders(path)方法:
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
上面的propertyResolver从哪里来,以下代码可见,这个final型的成员变量在声明的时候就创建了,前面准备好的propertyResource集合通过构造方法传给了它,所有它已经获得了所有进程变量和系统环境变量:
public AbstractEnvironment() {
this.propertySources = new MutablePropertySources(this.logger);
this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
- 跟踪this.propertyResolver.resolveRequiredPlaceholders(text),发现真正处理占位符的逻辑是在PropertyPlaceholderHelper.doResolvePlaceholders(text, this.strictHelper)方法:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
- 继续跟踪helper.replacePlaceholders(text, this::getPropertyAsRawString),到了PropertyPlaceholderHelper.parseStringValue(value, placeholderResolver, new HashSet<>()),这里逐一找出每个占位符去做替换:
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<>());
}
- parseStringValue方法中,找到占位符后,会调placeholderResolver.resolvePlaceholder(actualPlaceholder)方法,也就是步骤中8类的this::getPropertyAsRawString(实际上就是PropertySourcesPropertyResolver.getPropertyAsRawString方法),最终会在PropertySourcesPropertyResolver.getProperty方法中找出所有的属性来匹配占位符:
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");
}
// 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();
}
至此spring环境的初始化准备工作已经完成,下一章一起去看refresh()方法,那里聚集了spring初始化的核心操作,