Spring加载流程源码分析02【setConfigLocations】

  上篇文章介绍了Spring源码中的三步中的super(parent)的代码,本文介绍下setConfigLocations(configLocation)方法

类图

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
		throws BeansException {
	// 1.初始化父类
	super(parent);
	// 2.设置本地的配置信息
	setConfigLocations(configLocations);
	// 3.完成Spring容器的初始化
	if (refresh) {
		refresh();
	}
}

setConfigLocations

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++) {
			//循环取出每一个path参数,在此处就一个“applicationContext.xml“”
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}
  1. setConfigLocations主要工作有两个:创建环境对象ConfigurableEnvironment 、处理ClassPathXmlApplicationContext传入的字符串中的占位符;
  2. 环境对象ConfigurableEnvironment中包含了当前JVM的profile配置信息、环境变量、 Java进程变量;
  3. 处理占位符的关键是ConfigurableEnvironment、PropertyResolver、PropertyPlaceholderHelper之间的配合
// 这个方法的目的是替换掉path字符串中的占位符${XXX}这样的内容
protected String resolvePath(String path) {
     // 1.进入getEnvironment()
     // 2.进入resolveRequiredPlaceholders方法
	return getEnvironment().resolveRequiredPlaceholders(path);
}

1.ConfigurableEnvironment

  getEnvironment():创建了ConfigurableEnvironment 对象

public ConfigurableEnvironment getEnvironment() {
	if (this.environment == null) {
		this.environment = createEnvironment();
	}
	return this.environment;
}

提供的方法中可以看出两个功能

  1. 处理profile:Profile是对测试、生产等不同环境下的bean配置,这里我们没有特别设置,所以用到的profile是AbstractEnvironment的defaultProfiles; 之前介绍IOC的时候也介绍过profile。
  2. 处理property
  3. 获取系统环境信息
  4. 合并环境信息

2.PropertyResolver

resolveRequiredPlaceholders(path)

处理占位符的方法

3.PropertyPlaceholderHelper

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	if (this.strictHelper == null) {
		// 创建PropertyPlaceholderHelper对象
		this.strictHelper = createPlaceholderHelper(false);
	}
	return doResolvePlaceholders(text, this.strictHelper);
}

进入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)方法,也就是上面匿名类的getPropertyAsRawString方法(实际上就是PropertySourcesPropertyResolver.getPropertyAsRawString方法),最终会在PropertySourcesPropertyResolver.getProperty方法中找出所有的属性来匹配占位符

protected String parseStringValue(
			String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {

		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);
				// Now obtain the value for the fully resolved key...
				String propVal = placeholderResolver.resolvePlaceholder(placeholder);
				if (propVal == null && this.valueSeparator != null) {
					int separatorIndex = placeholder.indexOf(this.valueSeparator);
					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.
					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 string value \"" + strVal + "\"");
				}
				visitedPlaceholders.remove(originalPlaceholder);
			}
			else {
				startIndex = -1;
			}
		}

		return result.toString();
	}

名称

作用

ConfigurableEnvironment

1.创建PropertyResolver;2.向PropertyResolver提供环境变量、 Java进程变量

PropertyResolver

1.创建PropertyPlaceholderHelper;2.定义占位符的前缀和后缀(placeholderPrefix、placeholderSuffix);3.提供getPropertyAsRawString方法给PropertyPlaceholderHelper调用,用来获取指定key对应的环境变量;

PropertyPlaceholderHelper

1.找到字符串中的占位符;2.调用PropertyResolver.getPropertyAsRawString方法,从环境变量中取出占位符对应的值3.用环境变量的值替换占位符;

至此setConfigLocations的代码就查看到此,后面介绍refresh的代码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券