前言
之前零零散散学习过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有趣知识,让我们一起成长!