源代码计划: 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;
}
相关推荐
谦行37 分钟前
前端视角 Java Web 入门手册 4.4:Web 开发基础—— Listener
java·后端
非优秀程序员1 小时前
使用Python给自己网站生成llms.txt
人工智能·后端·架构
尘鹄1 小时前
一文讲懂Go语言如何使用配置文件连接数据库
开发语言·数据库·后端·golang
benben0441 小时前
Django小白级开发入门
后端·python·django
qw9492 小时前
SpringBoot3—场景整合:环境准备
java·后端
孟and平4 小时前
Flask 打包为exe 文件
后端·python·flask
大只因bug6 小时前
基于Django的协同过滤算法养老新闻推荐系统的设计与实现
后端·python·django·协同过滤算法推荐系统·新闻推荐网站系统·养老新闻推荐系统·个性化新闻推荐网站系统
_TokaiTeio10 小时前
JVM面试题100
java·开发语言·jvm·后端·虚拟机
计算机-秋大田10 小时前
基于Spring Boot的健美操评分管理系统设计与实现(LW+源码+讲解)
java·spring boot·后端
格子先生Lab10 小时前
Spring Boot 本地缓存工具类设计与实现
spring boot·后端·缓存