Spring源码分析 | 细说ApplicationContext

前言

之前零零散散学习过Spring的源码,但大部分都是浑沦吞枣,也不够认真。这次下定决心开启Spring源码学习之路,路很长,贵在坚持!

正文

虽然现在使用XML配置已经很少了,大部分人也是上来就是Springboot,拿来就用很是方便。但本文本着好理解Spring的思想,在下文中使用了最古老的ClassPathXmlApplicationContext的启动方式:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${filename}.xml");

干的漂亮,我们就从new开始吧!

构造函数

//org.springframework.context.support.ClassPathXmlApplicationContext.ClassPathXmlApplicationContext(String[], boolean, ApplicationContext)
public ClassPathXmlApplicationContext(
		String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
		throws BeansException {
	//1调用super,目的是为了初始化父类必要的属性
	super(parent);
	//2
	setConfigLocations(configLocations);
	if (refresh) {
	//3
		refresh();
	}
}

上面核心步骤有三个,我们分开来说,先说第一个super(parent);

super

看看ClassPathXmlApplicationContext的类结构:
在这里插入图片描述
所以super这行代码会初始化父类,可以看到父类是很多的。其中主要初始化父类中的AbstractApplicationContext,这个类被初始化时做了以下的事情:

  • 静态代码块
//org.springframework.context.support.AbstractApplicationContext
static {
	// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
	// on application shutdown in WebLogic 8.1. (Reported by Dustin Woods.)
	ContextClosedEvent.class.getName();
}

目的是尽早的加载ContextClosedEvent类,防止在WebLogic 8.1此容器关闭程序时报错,很明显这个是个防护代码。

  • 调用无参构造,初始化各个成员变量
//org.springframework.context.support.AbstractApplicationContext
public AbstractApplicationContext() {
	this.resourcePatternResolver = getResourcePatternResolver();
}

protected ResourcePatternResolver getResourcePatternResolver() {
	return new PathMatchingResourcePatternResolver(this);
}

以上代码初始化了PathMatchingResourcePatternResolver这个类对象,并保存下来。根据这个类的名字可以猜到其大概的作用:给定一个path(classpath:xxx或classpath*:xxx),可以将其解析为Spring中的Resource,供后续使用。

  • 调用setParent方法,保存父容器(先不要纠结为什么要保存,后面会一一讲到,简单的说是因为Spring允许容器的继承关系)
//org.springframework.context.support.AbstractApplicationContext
public void setParent(ApplicationContext parent) {
	//保存父容器(一般来讲,我们平时都是单个容器,所以不会存在这个对象)
	this.parent = parent;
	if (parent != null) {
		//获取父容器的环境对象
		Environment parentEnvironment = parent.getEnvironment();
		//判断父容器的环境对象是否属于ConfigurableEnvironment
		if (parentEnvironment instanceof ConfigurableEnvironment) {
			//将当前自己的环境对象和父容器的环境对象合并,也就是将父容器的环境对象中保存的值拿过来,自己保存一遍
			getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
		}
	}
}

经过上面的步骤,super(parent);这行代码基本上要干的事情已经干完了。

接着我们就来看看构造函数第二行代码setConfigLocations(configLocations);

setConfigLocations

//org.springframework.context.support.AbstractRefreshableConfigApplicationContext
public void setConfigLocations(String... locations) {
	if (locations != null) {
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			//使用configLocations保存被正确解析过的path
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}
//org.springframework.context.support.AbstractRefreshableConfigApplicationContext
protected String resolvePath(String path) {
	return getEnvironment().resolveRequiredPlaceholders(path);
}

//org.springframework.context.support.AbstractApplicationContext
public ConfigurableEnvironment getEnvironment() {
	//获取环境对象时如果为空,则创建一个,如果正常到这已经不是空了
	if (this.environment == null) {
		this.environment = createEnvironment();
	}
	return this.environment;
}

//org.springframework.context.support.AbstractApplicationContext
protected ConfigurableEnvironment createEnvironment() {
	return new StandardEnvironment();
}

请注意,上面的代码中有一行代码为getEnvironment().resolveRequiredPlaceholders(path),主要解决的是占位符问题,比如:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("${filename}.xml");

这里的${filename}就会在上面的步骤中解决掉,也间接说明了在使用配置文件时,文件名是可以动态给定的。
getEnvironment().resolveRequiredPlaceholders(path)这行代码究竟干了什么呢?

//org.springframework.core.env.AbstractEnvironment#resolveRequiredPlaceholders
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	//这里使用AbstractEnvironment中的一个propertyResolver对象来解析占位符,那么这个对象在什么时候被赋值呢 它又是什么呢?
	return this.propertyResolver.resolveRequiredPlaceholders(text);
}

propertyResolver对象是在AbstractEnvironment初始化的时候已经被new出来了,请看:

//org.springframework.core.env.AbstractEnvironment
//类如其名,可变属性源
private final MutablePropertySources propertySources = new MutablePropertySources();

//类如其名,可配置的属性解析器
private final ConfigurablePropertyResolver propertyResolver =
		new PropertySourcesPropertyResolver(this.propertySources);

但是resolveRequiredPlaceholders的实现是在PropertySourcesPropertyResolver的父类完成的,即AbstractPropertyResolver

//org.springframework.core.env.AbstractPropertyResolver#resolveRequiredPlaceholders
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
	if (this.strictHelper == null) {
		//这里直接调用createPlaceholderHelper然后new了一个对象
		this.strictHelper = createPlaceholderHelper(false);
	}
	return doResolvePlaceholders(text, this.strictHelper);
}

//org.springframework.core.env.AbstractPropertyResolver#doResolvePlaceholders
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

可以看到,最终实现解析占位符的是一个叫做PropertyPlaceholderHelper类的对象,具体到replacePlaceholders方法:


public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
	return parseStringValue(value, placeholderResolver, null);
}

//org.springframework.util.PropertyPlaceholderHelper#parseStringValue
protected String parseStringValue(
		String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
	//1、寻找占位符开始位置,即${
	int startIndex = value.indexOf(this.placeholderPrefix);
	if (startIndex == -1) {
		return value;
	}

	StringBuilder result = new StringBuilder(value);
	while (startIndex != -1) {
		//2、寻找结束符开始位置,即}
		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");
			}
			// Recursive invocation, parsing placeholders contained in the placeholder key.
			//3、如果去掉占位符后里面还有占位符,继续递归
			placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
			// Now obtain the value for the fully resolved key...
			//4、调用传入的placeholderResolver对象的`resolvePlaceholder`
			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
				// 解析完之后,如果还存在占位符,还是会继续递归的
				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();
}

这个方法稍微解释一下:

  • 首先,这个方法的第二个参数不知道大家注意到了吗?是PlaceholderResolver这个对象,大家还记得上面是怎么传递的吗?这里再贴一下上面的代码:
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
	return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

这里的this::语法用过java8应该都不陌生了,什么鬼这里传进来的是个方法?其实这也是Java的语法糖。我们只需要看一下PlaceholderResolver这个接口的定义就知道了:

@FunctionalInterface
public interface PlaceholderResolver {

	/**
	 * Resolve the supplied placeholder name to the replacement value.
	 * @param placeholderName the name of the placeholder to resolve
	 * @return the replacement value, or {@code null} if no replacement is to be made
	 */
	@Nullable
	String resolvePlaceholder(String placeholderName);
}

豁然开朗,这不就是函数式编程嘛,害。
下面介绍一下replacePlaceholders方法的主要逻辑:

  • 在代码1处,寻找占位符开始位置,默认的占位符以${开始,当然也可以自定义的。
  • 在代码2处,寻找结束符开始位置,默认的占位符以}结束,再向这行代码的上面瞅一眼,都while循环了各位还不懂吗?递归~
  • 在代码3处,如果去掉占位符后里面还有占位符,继续递归。(eg:KaTeX parse error: Expected '}', got 'EOF' at end of input: {{$profile}})
  • 在代码4处,调用传入的placeholderResolver对象的resolvePlaceholder方法进行解析真正的变量吗,实际上调用的就是我们一开始传入进来的辣个方法getPropertyAsRawString,入参为这里真正的占位符placeholder,我们现在就来看一下getPropertyAsRawString方法的实现,在PropertySourcesPropertyResolver中实现了这个方法:
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}

//org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	if (this.propertySources != null) {
		//1
		for (PropertySource<?> propertySource : this.propertySources) {
			if (logger.isTraceEnabled()) {
				logger.trace("Searching for key '" + key + "' in PropertySource '" +
						propertySource.getName() + "'");
			}
			//2
			Object value = propertySource.getProperty(key);
			if (value != null) {
				if (resolveNestedPlaceholders && value instanceof String) {
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Could not find key '" + key + "' in any property source");
	}
	return null;
}

上面代码1处,熟悉吗?这不就是初始化AbstractEnvironment的时候初始化PropertySourcesPropertyResolver时传入的对象吗?再来熟悉一下:

//org.springframework.core.env.AbstractEnvironment
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
	private final MutablePropertySources propertySources = new MutablePropertySources();

	private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);
}

再看代码2处,Object value = propertySource.getProperty(key);我们通过代码知道,MutablePropertySources在new的时候,内部是没有属性的,那么属性从哪里来呢?其实就是在AbstractEnvironment的构造函数中调用customizePropertySources,下面以StandardEnvironment的实现举例:

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()));
}

分别将系统属性和系统变量都添加进去了。

小结:

其实上面介绍这么多,稍微复杂点的也就是解析这个占位符了,那么可能有的小伙伴要问了:为什么需要这么多层的嵌套呢?我们分别说一下在解析占位符时这些类的作用,首先看一下PropertySourcesPropertyResolver的类层次结构:
在这里插入图片描述

  • PropertyResolver主要定义一些获取属性的操作
  • ConfigurablePropertyResolver主要是可配置的类,比如配置占位符前缀、后缀等
  • AbstractPropertyResolver 实现了解析占位符的逻辑过程
  • PropertySourcesPropertyResolver实现了PropertyResolver的获取属性的方法,其实就是提供数据源!

但是为什么还要有PropertyPlaceholderHelper这个类的存在呢?我的理解是将具体解析规则放入这个类中,倘若只有解析规则要变,是不需要改变AbstractPropertyResolver的逻辑了,也更好的体现了类的单一原则。

总结

精力有限,今天我们分析了构造函数的前两句代码分别都干了什么,即super(parent)setConfigLocations(configLocations)。这两行代码的内部实现实际上算是很简单的。Spring的核心都在我们即将要学习的第三行代码中,即refresh()。这个方法我只能说,直呼好家伙!下文我们将一步步分解这个方法,今天就到这吧。
微信搜索Java成神之路或扫描下方二维码,发现更多Java有趣知识,让我们一起成长!

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值