Spring IOC源码:简单易懂的Spring IOC 思路介绍
Spring IOC源码:核心流程介绍
Spring IOC源码:ApplicationContext刷新前准备工作
Spring IOC源码:obtainFreshBeanFactory 详解(上)
Spring IOC源码:obtainFreshBeanFactory 详解(中)
Spring IOC源码:obtainFreshBeanFactory 详解(下)
Spring IOC源码:<context:component-scan>源码详解
Spring IOC源码:invokeBeanFactoryPostProcessors 后置处理器详解
Spring IOC源码:registerBeanPostProcessors 详解
Spring IOC源码:实例化前的准备工作
Spring IOC源码:finishBeanFactoryInitialization详解
Spring IoC源码:getBean 详解
Spring IoC源码:createBean( 上)
Spring IoC源码:createBean( 中)
Spring IoC源码:createBean( 下)
Spring IoC源码:finishRefresh 完成刷新详解
在进入spring正式核心逻辑处理前,Spring IOC需要提前进行些初始化工作,为后续的操作准备好一些环境。下面主要讲解这部分的内容,也是refresh()方法前的代码逻辑,即下面的super(parent),setConfigLocations(configLocations);本人以xml配置的方式,入口为ClassPathXmlApplicationContext;
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//调用父类方法进行初始化
super(parent);
//对location文件路径进行解析、替换占位符等工作
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
super(parent); 见方法1详解
setConfigLocations(configLocations); 见方法4详解
我们可以看到ClassPathXmlApplicationContext最上面的父类为AbstractApplicationContext,这里一直往上调用直到AbstractApplicationContext父类中进行初始化工作,
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
this(); 调用无参构造方法 见方法2
setParent(parent); 设置此上下文的父类 见方法3
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
//返回一个返回用于解析占位符的解析器
return new PathMatchingResourcePatternResolver(this);
}
public void setParent(@Nullable ApplicationContext parent) {
//赋值传进来的父上下文到当前上下文属性中
this.parent = parent;
if (parent != null) {
//获取父容器系统环境属性
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
//合并到当前上下文中
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
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;
}
}
this.configLocations[i] = resolvePath(locations[i]).trim(); 见方法5
protected String resolvePath(String path) {
//获取上下文中的系统环境属性,并调用其路径解析方法,如含有占位符:${path}
return getEnvironment().resolveRequiredPlaceholders(path);
}
getEnvironment() 见方法6
resolveRequiredPlaceholders(path) 见方法9
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
//创建系统环境属性
this.environment = createEnvironment();
}
return this.environment;
}
createEnvironment(); 见方法7
protected ConfigurableEnvironment createEnvironment() {
//创建一个标准的环境属性
return new StandardEnvironment();
}
new StandardEnvironment(); 见方法8
进入这个类之后,我们看到它并没有构造方法,而且也没有resolveRequiredPlaceholders()方法,不过我们可以看到它继承了AbstractEnvironment方法,所以实例化时,肯定是调用父类的构造器。
resolveRequiredPlaceholders方法其实调用的是父类AbstractEnvironment中的方法,其类图关系如下:
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
resolveRequiredPlaceholders(text) 见方法10
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
//创建占位符解析器
this.strictHelper = createPlaceholderHelper(false);
}
//解析并返回处理后的配置文件路径
return doResolvePlaceholders(text, this.strictHelper);
}
doResolvePlaceholders(text, this.strictHelper) 见方法11
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 Set<String> visitedPlaceholders) {
//这里的this.placeholderPrefix 默认值为${
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");
}
// 递归调用,看占位符内容是否还有包含占位符 ,比如${${user}}
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 通过占位符内容名称去查询出,获取具体值,如user从environment中查出当前电脑名称
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
//如果查不到可能是以键值对的形式 如 name:zhangsan
//这时候我们要解析出KEY跟VALUE
//获取:符号的下标
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
//获取出key的值,如name
String actualPlaceholder = placeholder.substring(0, separatorIndex);
//获取出value的值,如zhangsan
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
//通过key值名称去查询,如果查不到则以actualPlaceholder为默认值进行赋值
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();
}
至此占位符解析结束,返回解析后的值赋值给上下文的configLocations数组;
refresh方法调用前的解析工作大概就是这样,这里只是Spring IOC的流程,如果是Springboot的话,会有一些方法重载,代码会更复杂。刷新前的准备工作,主要就是创建StandEnvironment属性类,解析占位符等操作;后面的文章再来写下refresh中的核心方法,文章会慢慢更新的,文章可能会有些理解不到位的地方,大家有更好的见解可以交流学习!