文章目录
Spring源码系列:
前言
正文
方法1:super(parent);
方法2: AbstractApplicationContext:
方法3: setParent
方法4:setConfigLocations
方法5: resolvePath
方法6:getEnvironment
方法7:createEnvironment
方法8:StandardEnvironment
方法9 resolveRequiredPlaceholders
方法10 resolveRequiredPlaceholders
方法11 doResolvePlaceholders
总结
Spring源码系列:
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详解
正文
方法1:super(parent);
可以看到ClassPathXmlApplicationContext最上面的父类为AbstractApplicationContext,这里一直往上调用直到AbstractApplicationContext父类中进行初始化工作,
public AbstractApplicationContext(@Nullable ApplicationContext parent) { this(); setParent(parent); }
this(); 调用无参构造方法 见方法2
setParent(parent); 设置此上下文的父类 见方法3
方法2: AbstractApplicationContext:
public AbstractApplicationContext() { this.resourcePatternResolver = getResourcePatternResolver(); }
protected ResourcePatternResolver getResourcePatternResolver() { //返回一个返回用于解析占位符的解析器 return new PathMatchingResourcePatternResolver(this); }
方法3: setParent
public void setParent(@Nullable ApplicationContext parent) { //赋值传进来的父上下文到当前上下文属性中 this.parent = parent; if (parent != null) { //获取父容器系统环境属性 Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { //合并到当前上下文中 getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } }
方法4: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; } }
this.configLocations[i] = resolvePath(locations[i]).trim(); 见方法5
方法5: resolvePath
protected String resolvePath(String path) { //获取上下文中的系统环境属性,并调用其路径解析方法,如含有占位符:${path} return getEnvironment().resolveRequiredPlaceholders(path); }
getEnvironment() 见方法6
resolveRequiredPlaceholders(path) 见方法9
方法6:getEnvironment
public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { //创建系统环境属性 this.environment = createEnvironment(); } return this.environment; }
createEnvironment(); 见方法7
方法7:createEnvironment
protected ConfigurableEnvironment createEnvironment() { //创建一个标准的环境属性 return new StandardEnvironment(); }
new StandardEnvironment(); 见方法8
方法8:StandardEnvironment
进入这个类之后,我们看到它并没有构造方法,而且也没有resolveRequiredPlaceholders()方法,不过我们可以看到它继承了AbstractEnvironment方法,所以实例化时,肯定是调用父类的构造器。
方法9 resolveRequiredPlaceholdersresolveRequiredPlaceholders方法其实调用的是父类AbstractEnvironment中的方法,其类图关系如下:
@Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { return this.propertyResolver.resolveRequiredPlaceholders(text); }
resolveRequiredPlaceholders(text) 见方法10
方法10 resolveRequiredPlaceholders
@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
方法11 doResolvePlaceholders
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中的核心方法,文章会慢慢更新的,文章可能会有些理解不到位的地方,大家有更好的见解可以交流学习!