源代码计划: Spring 5.6 源码解析 【二】

源代码计划: Spring 5.6 源码解析 【二】

在之前笔者记录过关于 xml 配置文件的路径加载读取,以及关于 BeanFactory 工厂的创建过程。

而我们接下来要做的就是将创建的 BeanFactory 内填充定义的 BeanDefinition 描述信息,而这些 BeanDefinition 描述信息里面就包括了,每个单独的 Bean 的 Class 类路径,以及对于一些特殊的 Bean 例如工厂 Bean 的 factoryBeanNamefactoryMethodName 等这样的信息。

当有两这些 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 内部的 beanDefintionNamebeanDefinitionMap 这些属性内部的资源都是 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 ,同样的从基础的关系图中也是可以看到的里面主要的两个接口就是,

EnvironmentCapableBeanDefinitionReader 也就是表示这,看到这两个类的组合,其实大致就可以猜到该 XmlBeanDefintionReader 该如何将系统环境变量当中的一些属性,读取到并完成一些处理,同时读取 beanDefintion 并进行属性填充。

开始解析后,将通过InputStream 输入流读取到的 xml 配置文件通过预设的文档解析格式[XSD/DTD] 解析成 Domoet 对象,而该对象当中就包含了 xml 配置文件当中所有元素信息。

因此后续的解析 xml 工作,其实就是对 Domoet 对象的内部元素解析。

这里可以看到有两个核心的资源对象,DocumentResource . 这两个核心的资源对象后续具体发挥的分别就是 提供xml配置文件当中的标签元素信息以及之前在 XmlReaderContext 对象调用过程当中加载的一些资源配置信息,这里包含了 namespaceHandlerResolver 当前解析资源的命令空间坐标和环境监听器等。而这些资源将在接下来的标签解析流程当中起到关键性作用。

同样的,这里先忽略一些其他细节方面的处理,笔者还是以核心为主。

当前这里的 preProcessXmlpostProcessXml 的前置后置处理当中内部都没有具体的实现,同样的也是可以通过自定义的扩展,去完成这两个增强的处理的。

而进入该方法,便就开始了真正的核心解析工作

首先第一个 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;
}
相关推荐
源码12151 小时前
ASP.NET MVC宠物商城系统
后端·asp.net·宠物
Ai 编码助手2 小时前
Go语言 实现将中文转化为拼音
开发语言·后端·golang
hummhumm2 小时前
第 12 章 - Go语言 方法
java·开发语言·javascript·后端·python·sql·golang
杜杜的man2 小时前
【go从零单排】Directories、Temporary Files and Directories目录和临时目录、临时文件
开发语言·后端·golang
wywcool2 小时前
JVM学习之路(5)垃圾回收
java·jvm·后端·学习
喜欢打篮球的普通人3 小时前
rust高级特征
开发语言·后端·rust
代码小鑫4 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
豌豆花下猫4 小时前
REST API 已经 25 岁了:它是如何形成的,将来可能会怎样?
后端·python·ai
喔喔咿哈哈4 小时前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
夏微凉.5 小时前
【JavaEE进阶】Spring AOP 原理
java·spring boot·后端·spring·java-ee·maven