Spring IOC源码:ApplicationContext刷新前准备工作

文章目录

  • 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);

Spring IOC源码:ApplicationContext刷新前准备工作_第1张图片我们可以看到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

Spring IOC源码:ApplicationContext刷新前准备工作_第2张图片
进入这个类之后,我们看到它并没有构造方法,而且也没有resolveRequiredPlaceholders()方法,不过我们可以看到它继承了AbstractEnvironment方法,所以实例化时,肯定是调用父类的构造器。

方法9 resolveRequiredPlaceholders

resolveRequiredPlaceholders方法其实调用的是父类AbstractEnvironment中的方法,其类图关系如下:
Spring IOC源码:ApplicationContext刷新前准备工作_第3张图片

	@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中的核心方法,文章会慢慢更新的,文章可能会有些理解不到位的地方,大家有更好的见解可以交流学习!

你可能感兴趣的:(spring,spring,java,后端)