源代码计划: Spring 5.6 源码解析 【二】
在之前笔者记录过关于 xml 配置文件的路径加载读取,以及关于 BeanFactory
工厂的创建过程。
而我们接下来要做的就是将创建的 BeanFactory
内填充定义的 BeanDefinition
描述信息,而这些 BeanDefinition
描述信息里面就包括了,每个单独的 Bean 的 Class 类路径,以及对于一些特殊的 Bean 例如工厂 Bean 的 factoryBeanName
,factoryMethodName
等这样的信息。
当有两这些 Bean 的描述形象,同时添加到了当前 bean 工厂当中的 beanDefinitionName
以及 beanDefinitionMap
当中,为后面的 bean 初始化做准备。
那么就接下来看看,当前完成了 xml 配置文件的读取之后, spring 又是如何一步步的将 xml 配置文件当中的这些标签内容,解析成一个准备进行初始化加载的 beanDefinition
描述对象呢?
java
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean class="com.peppa.dto.Student">
<property name="name" value="kakarote"/>
<property name="age" value="18"/>
</bean>
<bean class="com.peppa.dto.User">
<property name="name" value="特兰克斯"/>
<property name="email" value="320948xxxx@qq.com"/>
</bean>
</beans>
回到 refresh() 方法的,从当前 beanFactory 的创建开始。
java
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
进入 obtainFreshBeanFactory
方法之后,进过对 refreshBeanFactory
的调用进入到其主流程。到核心方法 loaderBeanDeinitions
当中。当前当前的 beanFactory
内部的 beanDefintionName
和 beanDefinitionMap
这些属性内部的资源都是 null
,而接下来的目的就是完成这些属性的填充,以及一些其他细节的处理。
因为笔者记录本次的主题是关于 XML 解析成
beanDefinition
的过程,因此可能这里会刻意的忽略掉一些些其他的细节,以为主核心流程进行表述。
java
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) { // 判断是否存在 beanFactory
destroyBeans(); // 销毁创建的 bean
closeBeanFactory(); // 关闭之前的 beanFacotry
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory(); // 创建 DefaultListableBeanFactory 工厂对象,该对象目前只是一个初始空值
beanFactory.setSerializationId(getId()); // 设置序列化 id (在调用最终的父类构造方法的时候分配一个默认的 id)
customizeBeanFactory(beanFactory); // 设置参数值 【自定义扩展可以修改 allowBeanDefinitionOverriding - allowCircularReferences】
// loadBeanDefinitions 这个方法当中涉及到非常复杂的重载方法调用,内容相当多
// 加载 bean 定义(读取 xml 配置文件中的信息)
// 这里将 工厂类 beanFactory 当作一个参数传入,最终要将 xml 配置文件当中的所有属性解析到 beanFactory 工厂类当中
// 因此就会涉及到很多的解析工作
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
想这里关于 xml 文件的路径资源加载器以及初始化的一些设置,笔者这里同样的跳过,还是以核心主线为主。当然这里跳过的部分,里面设计到很多方面的细节都很有意思,也很有趣,
进入该方法之后,会对
loadBeanDefinitions
进行多次的重复调用。将之前笔者在XmlApplicationContext
启动类当中设置的 xml 配置文件字符串类型的路径转化成Resources
对象数组,为后续的文件加载做准备。
同样的经过多次的
loaderBeanDefinitons
重载调用,最终到达doLoaderBeanDefinitons
方法,开始核心业务的逻辑处理。可以看到的是当前的该类是
XmlBeanDefinitionReader
,同样的从基础的关系图中也是可以看到的里面主要的两个接口就是,
EnvironmentCapable
与BeanDefinitionReader
也就是表示这,看到这两个类的组合,其实大致就可以猜到该XmlBeanDefintionReader
该如何将系统环境变量当中的一些属性,读取到并完成一些处理,同时读取beanDefintion
并进行属性填充。
开始解析后,将通过
InputStream
输入流读取到的 xml 配置文件通过预设的文档解析格式[XSD/DTD] 解析成Domoet
对象,而该对象当中就包含了 xml 配置文件当中所有元素信息。因此后续的解析 xml 工作,其实就是对
Domoet
对象的内部元素解析。
这里可以看到有两个核心的资源对象,
Document
与Resource
. 这两个核心的资源对象后续具体发挥的分别就是 提供xml配置文件当中的标签元素信息以及之前在XmlReaderContext
对象调用过程当中加载的一些资源配置信息,这里包含了namespaceHandlerResolver
当前解析资源的命令空间坐标和环境监听器等。而这些资源将在接下来的标签解析流程当中起到关键性作用。
同样的,这里先忽略一些其他细节方面的处理,笔者还是以核心为主。
当前这里的
preProcessXml
与postProcessXml
的前置后置处理当中内部都没有具体的实现,同样的也是可以通过自定义的扩展,去完成这两个增强的处理的。
而进入该方法,便就开始了真正的核心解析工作
首先第一个 if delegate.isDefaultNamespace(root)
的具体作用是用于判断,当前根节点 <beans> 的命名空间是否是默认指定的。
当然这里的命名空间读取的就是 xmlns="http://www.springframework.org/schema/beans"
,一般使用 <beans>
标签,命名空间都是默认的。
而关于
xmlns="http://www.springframework.org/schema/beans
命名空间指定解析这一块,笔者会在后续关于自定义标签解析原理
部分进行补充。
关于第二个 if 判断 node instanceof Element
, 完成的主要功能就是判断当前的节点,是否是一个节点元素 。其实简单点说,就是判断当前的 node 节点是否是一个 新的根节点。如比:当前的 <beans>
是一个元素,而 <beans> 当中又包含一个 <bean>
而 <bean> 当中又包含 <list>
,类似于这样的都成为一个元素。
第三个 if 判断 delegate.isDefaultNamespace(ele)
与第一个判断过程原理是一样的。首先解析该标签是否是一个默认命名空间。
完成上述判断后,就开始了真正的解析工作
首先对当前的元素进行验证,是否是默认的元素标签类型,
IMPORT_ELEMENT
,ALIAS_ELEMENT
,BEAN_ELEMENT
,NESTED_BEANS_ELEMENT
。
- 因为我们的 xml 当中目前,只定义了 <bean> 标签,因此解析到的匹配元素也只有
BEAN_ELEMENT
开始关于当前 bean 标签的具体解析工作
而当这个方法处理完后,也就标签解析完成,将获得一个当前 xml 配置文件当中独属于该 bean 的定义描述信息
bdHolder
。里面就包含了当前解析到 bean 的类对象,以及别名和其他附属属性。
接下来,将开始 bean 标签内部的各个元素细节解析
java
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) { // 解析 bean 标签当中 class
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) { // 解析 bean 标签当中 parent
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
try {
// 创建装在 bean 信息的 AbstractBeanDefinition 对象, 实际的实现是 GenericBeanDefinition -> 开始处理 BeanDefinition 当中的其他属性值
// 这里处理的是解析到的当前 <bean> 标签下的可能会存在的属性值 例如像这样的
/**
* <bean class="com.peppa.dto.User">
* <meta key="xx" value="xx"/>
* <lookup-method></lookup-method>
* <property name="name" value="特兰克斯"/>
* <property name="email" value="3209487776@qq.com"/>
* </bean>
*/
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//解析 bean 标签的其他属性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 设置 description 信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析元数据
parseMetaElements(ele, bd);
// 解析 lookup-method 属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析 replaced-method 属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析构造函数
parseConstructorArgElements(ele, bd);
// 解析 property 子元素
parsePropertyElements(ele, bd);
// 解析 qualifier 子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}