工作中一直和Spring框架打交道,可是从来都没有真正去了解过该它。只知道Spring框架给项目带来极大的便利,降低了代码之间的耦合度,而不至于牵一发而动全身,还提供了非常丰富的扩展点、高级特性等等。其中最为重要是IOC、AOP。接下来抽出一段时间来记录阅读框架的点点滴滴
为了更好的梳理整条线大致的流转,用一张图贯穿始终,方便自己的记住Spring的大致脉络。先确定始终,始:包含Bean的定义信息配置文件,终:Bean定义信息封装在BeanDefinition中,并保存在DefaultListableBeanFactory容器中。
通过ClassPathXmlApplicationContext(版本4.3.10.RELEASE)作为源码的入口。如下是最简单启动方式。
// Car类中只有一个属性name,还包含set、get、构造方法、toString方法
@Test
public void ClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("conf/SpringIOC.xml");
Car car = context.getBean(Car.class);
System.out.println(car);
}
SpringIOC.xml中如下
<bean id="car" class="com.rookie.Car">
<property name="name" value="audi"/>
</bean>
ClassPathXmlApplicationContext源码中
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh,
ApplicationContext parent) throws BeansException {
// 层层往上调,最终会初始化PathMatchingResourcePatternResolver解析器
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
PathMatchingResourcePatternResolver可以用来查找类路径、jar的类路径、文件系统中的资源,资源路径可以时Ant风格或者无通配符,该类会在后面具体的解释。setConfigLocations方法在文章的末尾进行相关的解释,IOC的主核心代码为AbstractApplicationContext中的refresh方法,跟随debug的脚步来到方法中的
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
// 对当前容器的一些标识位
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// 容器的运行需要某些必备的环境参数
initPropertySources();
// 验证上一步设置的参数在当前环境中是否存在
getEnvironment().validateRequiredProperties();
// 存储事件集合,目前好像没用到,为什么要在此处提前初始化好?待用到时候再回来做笔记 TODO
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
针对initPropertySources的扩展点,如下做一个小demo进行测试
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
public MyClassPathXmlApplicationContext(String... configLocations) {
super(configLocations);
}
// 重写方法
@Override
protected void initPropertySources() {
getEnvironment().setRequiredProperties("abc");
}
}
采用的时junit进行测试,当VM中没有设置abc该变量值时,会报如下的具体错误
The following properties were declared as required but could not be resolved: [abc]
在VM options中随便设置对应的参数值:-Dabc=IOC时,代码正常运行。
分析setConfigLocations方法之前,需将conf/SpringIOC.xml改为conf/Spring${abc}.xml,并且VM参数不变。有点像SpEL处理占位符。最终的结果导向是将占位符${abc}替换为IOC,分析下相关核心源代码。
AbstractRefreshableConfigApplicationContext
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++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
/**
* Resolve the given path, replacing placeholders with corresponding
* environment property values if necessary. Applied to config locations.
* @param path the original file path
* @return the resolved file path
* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
// 通过容器的上下文来解析给定路径的占位符
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
PropertyPlaceholderHelper
protected String parseStringValue(String value, PlaceholderResolver placeholderResolver,
Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// 获取第一个占位符的开始位置
int startIndex = value.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");
}
// 递归调用,解析占位符中是否还有占位符符号
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 从上下文的环境中获取字符串所对应的值(K-V键值对)
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 当占位符字符串是abc:MVC时,如果abc在环境中没有找到对应值,则以MVC作为默认值返回
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) {
// 设置的k-v值是否也存在占位符,有一次的递归调用
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 真正开始替换占位符中的值,即${abc} => IOC
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();
}
待处理问题点:
-
prepareRefresh中为什么要提前初始化好存储事件集合实例 this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();