源代码计划: Spring 5.6 源码解析
该版本是以 Spring 5.6 为基础,从 bean 的创建流程、AOP 的核心对象创建、事务的创建加载整个 spring 框架的深度解析。
与 源代码计划: Spring 源码深度解析 倾向于以书本为线索的不同之处在于,该版本更多的是案例的演示与 debug 的整个流程。
前言
在之前的关于 spring 源码的另一篇 随笔 当中,以 <spring 源码深度解析> 一书为开篇,跟随章节内容研究过关于 spring 3.2 版本最原始的容器启动加载而原型。
java
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
@Test
public void testSimpleLoad(){
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean myTestBean = (MyTestBean) beanFactory.getBean("myTestBean");
System.out.println(myTestBean.getTestStr());
}
}
在 5.6 版本当中笔者换另一种资源加载的方式进行启动,研究继承实现关系。
java
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("application-peppa.xml");
Student bean = application.getBean(Student.class);
System.out.println(bean.getName());
}
}
在前的 3.2 版本开篇曾提到过 spring 的核心功能有两个大类。 XmlBeanDefinitionReader
与 DefaultListableBeanFactory
,而中前者用于加载解析 XML 文件而后者用于创建注册 Bean 。那就看看,ClassPathXmlApplicationContext 具体是怎么实现 解析
与 注册
的呢?
从图中的 ClassPathXmlApplicationContext 基础关系类图中可以看到,AbstractApplicationContext 是一个绑定解析配置文件与容器实例化的实现类。
XML 配置文件的加载
经过对 ClassPathXmlApplicationCintext 构造方法的重载调用,直到 super(parent)
处对一系列父类的构造方法进行调用。直到 AbstartctApplicationContext
的构造方法,在一系列的父类构造的调用当中有大量的初始化变量。这些变量都是可以关注的。
java
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 调用父类的构造方法,进行相关的对象创建的初始化等操作(对父类的一些构造方法不要忽略,会有一些额外的属性填充工作)
super(parent);
setConfigLocations(configLocations); // 设置应用程序上下文的资源配置路径 ${jdbc.url},${jdbc.username} 解析路径
if (refresh) {
refresh();
}
}
java
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() { // 用于解析当前系统的资源【xml,配置文件都属于资源】
this.resourcePatternResolver = getResourcePatternResolver(); // 创建资源模式处理器(refresh()方法就在此类当中)
}
java
protected ResourcePatternResolver getResourcePatternResolver() {
// 创建一个资源模式解析器 (其实就是用于解析 xml 配置文件)
return new PathMatchingResourcePatternResolver(this);
}
java
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) { // 在当前的 spring 项目当中,看不到【父子容器】的概念,如果进入到 spring-mvc 的时候就会出现父子容器
Environment parentEnvironment = parent.getEnvironment(); // 如果父容器不为空,获取父容器的环境对象
if (parentEnvironment instanceof ConfigurableEnvironment) { // 如果当前的环境对象是一个可配置的环境对象
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); // 进行相关的一个合并工作
}
}
}
这里的 AbstartApplicationContext 的构造方法当中的 setParent
方法用于设置父类的处理父类的容器,但是在当前的 Spring 项目当中是没有父类的容器的。因此这里 this.parent
也就为 null 。
在返回到 AbstarctXmlApplicationContext 类的属性初始化当中有一个 validating
变量,该变量的属性值为 true 。而该变量的主要作用是设置 xml 加载的配置文件内部的 验证模式
默认是 XSD。而关于 XML 文件的验证加载方式在之前的 源代码计划: Spring 源码深度解析 中同样也提到过,其中提到关于 手动指定
与 自动验证
。
java
private boolean validating = true; // 设置 xml 文件的验证标志, 默认是 true [验证配置文件加载的表示 xsd ?]
java
/**
* Initialize the bean definition reader used for loading the bean
* definitions of this context. Default implementation is empty.
* <p>Can be overridden in subclasses, e.g. for turning off XML validation
* or using a different XmlBeanDefinitionParser implementation.
* @param reader the bean definition reader used by this context
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass
*/
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
java
/**
* Set whether to use XML validation. Default is {@code true}.
* <p>This method switches namespace awareness on if validation is turned off,
* in order to still process schema namespaces properly in such a scenario.
* @see #setValidationMode
* @see #setNamespaceAware
*/
public void setValidating(boolean validating) {
this.validationMode = (validating ? VALIDATION_AUTO : VALIDATION_NONE);
this.namespaceAware = !validating;
}
继续执行回到 AbstartctApplicationContext 的子类 ClassPathXmlApplicationContext 类当中的 setConfigLocations
方法当中。
java
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++) { // 这里可能有多个配置文件路径
// 思考【我们有没有在配置文件文件中写过 ${jdbc.url},${jdbc.username}】 既然名字是这样解析的,那么 xml 配置文件中的 是不是也是这样的呢 ? 【功能复用】
this.configLocations[i] = resolvePath(locations[i]).trim(); // 解析给定的路径【比如解析 spring-${username}.xml这方式】
}
}
else {
this.configLocations = null;
}
}
该方法的主要作用就是将我们通过 ClassPathXmlApplicationContext( "application-peppa.xml") 传入的 xml 多配置文件处理成一个 String 类的配置文件数组。
但其中还有另外一个细节是关于 resolvePath
方法的。这里的 resolvePath 方法的任务完成通配符进行解析,例如: 将 application-${user.name}.xml 解析成 application-peppa.xml 。
java
protected String resolvePath(String path) {
// getEnvironment() 第一个获取当前系统环境变量的值,为替换做准备
// resolveRequiredPlaceholders(path) 解析必要的占位符[${xxxx}],进行替换
return getEnvironment().resolveRequiredPlaceholders(path);
}
首先, 通配符中的变量属性都是来自于当前的环境,因此必定是先要通过 getEnviroment()
获取当前的运行环境,后面的 resolveRequiredPlaceholders(path)
方法进行具体的解析 。
java
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) { // 首次进入当前的环境为空
this.environment = createEnvironment();
}
return this.environment;
}
java
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
java
public AbstractEnvironment() {
// 在创建环境对象的时候,调用父类的构造方法。定制化属性资源,在父类当中是一个空的实现, 在调用的子类的时候具体实现。
customizePropertySources(this.propertySources);
}
最终在 getSystemProperties()
与 etSystemEnvironment()
方法当中获取系统的属性。因此在进行对象创建的时候,父类初始化的时候就已经完成对于环境的加载创建。
java
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()));// 获取系统环境
}
XML 配置文件的路径解析
回到具体的解析方法 resolveRequiredPlaceholdes
当中
java
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
// 占位符的助手 [前缀 ${ 后缀 } 分隔符 :]
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper); // 开始解析
}
java
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
获取对象属性当中的解析占位符的具体符号,通过 doResolvePlaceolders
方法完成具体的解析工作。
java
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
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");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); // 递归调用截取第一个占位符包含的字符串可能会包含 os-${username}}
// 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 value \"" + value + "\"");
}
//删除集合当中的解析元素 spring-${username}.xml
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
关于该方法当中有几个关键的调用这里提一下:
-
findPlaceholderEndIndex () 该方法主要是找到结束的匹配索引下标位置
javaprivate int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + this.placeholderPrefix.length(); // 占位符开始的位置 + 前缀索引的长度 int withinNestedPlaceholder = 0; while (index < buf.length()) { if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { // 判断当前的下一个字符是否出现了后缀 [}] if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { // 如果没有出现则判断是否出现新的占位符 [{] ,该种情况主要用于判断是否出现类似于 [spring-${os-${username}}.xml] 占位符嵌套 withinNestedPlaceholder++; index = index + this.simplePrefix.length(); } else { index++; } } return -1; }
-
parseStringValue() 这里有一个递归的调用,这主要用于处理 {user.name-{user.host}} 这种通配符中嵌套通配符的方式的。
-
placeholderResolver.resolvePlaceholder(placeholder); 从系统当中获取指定的属性值
而这里有进行了一次通配符解析的调用,主要用于处理,系统环境属性当中是否存在通配符的这种情况。
最终删除原有配置文件集合当中的为解析前的文件。
refresh 方法
完成了关于 XML 配置文件的路径加载后,开始进行核心的处理 refresh
方法。
java
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh(); // 准备 Refresh 刷新 (前戏)
// Tell the subclass to refresh the internal bean factory.
// 创建容器对象: DefaultListableBeanFactory
// 加载 xml 配置文件的属性到当前工程当中, 最重要的就是 BeanDefinition 的属性填充
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory); //准备 BeanFactory Bean工厂 (前戏)
try {
// 子类覆盖方法做的处理,此处我们自己一般不做任何的工作,但是可以查到 web 中的代码,是由具体的实现的
// Allows post-processing of the bean factory in context subclasses. (子类扩展:后置增强处理器)
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context. (实例化并且执行已经注册的 BFPP beans )
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.(注册)
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource(); // 国际化
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); // 实例化剩下的非懒加载的对象
// Last step: publish corresponding event.
finishRefresh(); // 完成整体的刷新
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
refresh 当中一共有 12 个核心方法分别是:
-
prepareRefresh() // 完成刷新前的准备工作
scss
obtainFreshBeanFactory(); //创建 DefaultListableBeanFactory 容器对象,加载 XML 配置文件到当前工具,完成 BeanDefinition 的属性填充
-
prepareBeanFactory(beanFactory); //准备 BeanFactory 工厂
-
postProcessBeanFactory(beanFactory); //后置增强处理器,子类具体实现
-
invokeBeanFactoryPostProcessors(beanFactory); //实例化以及注册的 BFPP
-
registerBeanPostProcessors(beanFactory); // 注册具体的 Bean
-
initMessageSource(); //国际化处理
-
initApplicationEventMulticaster(); //初始化时间监听器
-
onRefresh(); //初始化特殊的 Bean 空实现
-
registerListeners(); //注册监听器
-
finishBeanFactoryInitialization(beanFactory); // 实例化剩下的非懒加载对象
-
finishRefresh(); //完成整体的刷新