spring4.1.8初始化源码学习三部曲之二:setConfigLocations方法
【摘要】 本章是学习spring4.1.8初始化源码的第二篇,我们聚焦ClassPathXmlApplicationContext.setConfigLocations方法
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
- 本章是学习spring4.1.8初始化源码的第二篇,前一章《spring4.1.8初始化源码学习三部曲之一:AbstractApplicationContext构造方法》对AbstractApplicationContext的初始化做了分析,本章我们聚焦ClassPathXmlApplicationContext.setConfigLocations方法;
整体概括
- 本章会涉及到多个类的细节,所以先从整体上概括AbstractRefreshableConfigApplicationContext.setConfigLocations方法的主要功能,后面的细节都是基于这些总结展开的:
- setConfigLocations主要工作有两个:创建环境对象ConfigurableEnvironment 、处理ClassPathXmlApplicationContext传入的字符串中的占位符;
- 环境对象ConfigurableEnvironment中包含了当前JVM的profile配置信息、环境变量、 Java进程变量;
- 处理占位符的关键是ConfigurableEnvironment、PropertyResolver、PropertyPlaceholderHelper之间的配合:
名称 | 作用 |
---|---|
ConfigurableEnvironment | 1.创建PropertyResolver; 2.向PropertyResolver提供环境变量、 Java进程变量 |
PropertyResolver | 1.创建PropertyPlaceholderHelper; 2.定义占位符的前缀和后缀(placeholderPrefix、placeholderSuffix); 3.提供getPropertyAsRawString方法给PropertyPlaceholderHelper调用,用来获取指定key对应的环境变量; |
PropertyPlaceholderHelper | 1.找到字符串中的占位符; 2.调用PropertyResolver.getPropertyAsRawString方法,从环境变量中取出占位符对应的值 3.用环境变量的值替换占位符; |
-
用思维导图来辅助:
-
对占位符的处理实战,请参考文章《windows下修改、编译、构建spring-framework4.1.8.RELEASE源码》,这里面用demo展示了占位符的替换;
展开详情
-
接下来去阅读setConfigLocations方法内部的细节代码:
-
跟踪该方法,找到是在类AbstractRefreshableConfigApplicationContext中实现的:
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;
}
}
- 从上述代码可以发现,本章我们要重点学习的是resolvePath(locations[i]),结合上一章demo中的入参,此处应该是方法resolvePath(“classpath:applicationContext.xml”);
- 跟踪到AbstractRefreshableConfigApplicationContext类,这个方法的目的是替换掉path字符串中的占位符${XXX}这样的内容:
protected String resolvePath(String path) {
return getEnironment().resolveRequiredPlaceholders(path);
}
- 先看getEnvironment()方法:
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
- 关于ConfigurableEnvironment接口,我们先来看看他的声明方法以及继承关系:
从上图可见,ConfigurableEnvironment接口有两个重要部分组成:Profile和Property;
Profile是对测试、生产等不同环境下的bean配置,这里我们没有特别设置,所以用到的profile是AbstractEnvironment的defaultProfiles;
接下来关于Property资源是如何产生的;
- 顺着调用一路看过去,发现最终会调用AbstractEnvironment类的构造方法:
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
if (this.logger.isDebugEnabled()) {
this.logger.debug(format(
"Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
}
}
- 上面的customizePropertySources是在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实例);
- 首先向propertySources添加一组属性,来自Java进程变量(getSystemProperties()内是System.getProperties()方法);
- 接着向propertySources再添加一组属性,来自系统环境变量(getSystemEnvironment()内是System.getenv()方法);
- getSystemProperties和getSystemEnvironment方法中有个相同的细节需要注意,在获取进程变量或者系统环境变量的时候,都有可能因为安全限制抛出异常,这时候就返回一个ReadOnlySystemAttributesMap的实现类,外部调用get方法的时候,再去尝试获取进程变量或者系统环境变量对应的值,取不到则返回null,代码如下:
public Map<String, Object> getSystemProperties() {
try {
return (Map) System.getProperties();
}
catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
protected String getSystemAttribute(String attributeName) {
try {
return System.getProperty(attributeName);
}
catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info(format("Caught AccessControlException when accessing system " +
"property [%s]; its value will be returned [null]. Reason: %s",
attributeName, ex.getMessage()));
}
return null;
}
}
};
}
}
- StandardEnvironment对象创建成功后,接着看它的resolveRequiredPlaceholders方法:
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
- 上面的propertyResolver从何而来?以下代码可见,这个final型的成员变量在声明时就创建了,前面准备好的propertySources集合通过构造方法传给了它,所有它已经获得了所有系统环境变量和进程环境变量:
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
- 跟踪PropertySourcesPropertyResolver.resolveRequiredPlaceholders,发现真正处理占位符的逻辑是在PropertyPlaceholderHelper.doResolvePlaceholders方法:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
});
}
- getPropertyAsRawString的具体实现在PropertySourcesPropertyResolver类中:
@Override
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
- 继续跟踪helper.replacePlaceholders(),到了PropertyPlaceholderHelper.parseStringValue方法,这里面逐一找出每个占位符去做替换:
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
- parseStringValue方法中,找到了占位符后,会调用入参placeholderResolver的resolvePlaceholder(placeholder)方法,也就是步骤9中匿名类的getPropertyAsRawString方法(实际上就是PropertySourcesPropertyResolver.getPropertyAsRawString方法),最终会在PropertySourcesPropertyResolver.getProperty方法中找出所有的属性来匹配占位符:
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
printTrack("start parseStringValue");
logger.info("before parse : [" + strVal + "]");
StringBuilder result = new StringBuilder(strVal);
int startIndex = strVal.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);
// 这里实际上会调用PropertySourcesPropertyResolver.getPropertyAsRawString方法,propVal的值就是从环境变量中取得的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
...
- 至此,Spring环境的初始化准备工作已经完成,下一章一起去看refresh()方法,那里聚集了Spring初始化的核心操作:《spring4.1.8初始化源码学习三部曲之三:AbstractApplicationContext.refresh方法》
欢迎关注华为云博客:程序员欣宸
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)