#博学谷IT学习技术支持#
上一节我们大体了解了一下Spring-IOC执行的大致流程,没看过的小伙伴可以通过链接查看
Spring ioc执行流程(粗讲)
小伙伴们看完后,应该还很茫然。不过不要着急,接下来我们开始逐步分析。
首先,根据上节使用ClassPathXmlApplicationContext类来加载xml配置文件,我们跟进源码。
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//设置父级,这里parent为null
super(parent);
//这里进行介质并设置配置文件的路径,就是上面的 classpath:applicationContext.xml
setConfigLocations(configLocations);
//是否执行refresh操作,这里默认为true
if (refresh) {
refresh();
}
}
本节主要讲解setConfigLocation()方法
跟进setConfigLocations()方法,会到resolvePath()方法
this.configLocations[i] = resolvePath(locations[i]).trim();
继续跟进resolvePath()方法
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
getEnvironment()方法
getEnvironment()方法创建了StandardEnvironment类,调用StandardEnvironment父类AbstractEnvironment的构造方法
public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
this.propertyResolver = createPropertyResolver(propertySources);
customizePropertySources(propertySources);
}
customizePropertySources()方法
继续看customizePropertySources()方法
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()));
}
这个方法主要用来解析systemProperties和systemEnvironment,我们debug看下解析后的数据
resolveRequiredPlaceholders()方法
到这里,getEnvironment() 差不多结束了,接下来继续看resolveRequiredPlaceholders()方法
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; //"${"
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; // "}"
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; // ":"
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
// 跟进createPlaceholderHelper(false)方法
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
这一步如果strictHelper属性为空的话,会创建一个PropertyPlaceholderHelper对象并赋值给strictHelper。然后传入占位符的前缀后缀和分隔符。后续的配置文件路径configLocation的解析操作就是交给strictHelper进行的
doResolvePlaceholders()方法
// 到了dodoResolvePlaceholders()方法
return doResolvePlaceholders(text, this.strictHelper);
// 进入方法内部,helper就是传入的strictHelper
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
// 继续跟进helper.replacePlaceholders()方法,主要的解析操作就在parseStringValue()方法内部进行
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, @Nullable SetvisitedPlaceholders) { // 获取需要解析的路径是否包含前缀 ‘${‘,如果没有,直接返回 int startIndex = value.indexOf(this.placeholderPrefix); if (startIndex == -1) { return value; } // 封装为StringBuilder,方便进行后续操作 StringBuilder result = new StringBuilder(value); while (startIndex != -1) { // 获取后缀 ‘}’ 索引位置 int endIndex = findPlaceholderEndIndex(result, startIndex); // 如果endIndex 为-1 的话,说明不存在后缀‘}’,设置startIndex为-1,跳出while循环 if (endIndex != -1) { // 截取获得需要替换的内容 String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; // 这里会设置visitedPlaceholders属性,为了避免循环引用 // 如${aaa},但是解析aaa后,aaa的也是${aaa},这样就会导致无限循环,所以需要 // 存入visitedPlaceholders防止循环 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); // 开始尝试获取palceholder变量作为key所对应的value // 就是遍历前面customizePropertySources()方法获取的属性内容 String propVal = placeholderResolver.resolvePlaceholder(placeholder); // 如果值为空 if (propVal == null && this.valueSeparator != null) { // 获取分隔符 ‘:’的索引 int separatorIndex = placeholder.indexOf(this.valueSeparator); // 如果存在分隔符,则将placeholder分割,然后继续获取内容 // 如果不存在,直接跳过 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. // 继续对解析后的值进行递归解析 // 如: ${aaa} -> aaa -> ${bbb},然后该步骤不要就行对${bbb}继续进行解析 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()); } // 判断是否忽略不能解析的placeholder,true则跳过,继续后面的解析,false直接抛出异常 else if (this.ignoreUnresolvablePlaceholders) { startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); } else { throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value "" + value + """); } // 将当前placeholder从visitedPlaceholders中移除 visitedPlaceholders.remove(originalPlaceholder); } else { startIndex = -1; } } //返回解析后的数据 return result.toString(); }
解析完成后,将解析后的值复制到this.configLocations属性内
this.configLocations[i] = resolvePath(locations[i]).trim();
到这里,setConfigLocations()方法就分析完了,如果有问题的或者不对的地方,请小伙伴们积极指出,谢谢!!!



