源代码计划: 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="[email protected]"/>
	</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="[email protected]"/>
         * 	</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;
}
相关推荐
码小凡6 小时前
优雅!用了这两款插件,我成了整个公司代码写得最规范的码农
java·后端
星星电灯猴6 小时前
Charles抓包工具深度解析:如何高效调试HTTPHTTPS请求与API接口
后端
isfox6 小时前
Hadoop 版本进化论:从 1.0 到 2.0,架构革命全解析
大数据·后端
normaling7 小时前
四、go语言指针
后端
yeyong7 小时前
用springboot开发一个snmp采集程序,并最终生成拓扑图 (二)
后端
掉鱼的猫8 小时前
Solon AI 五步构建 RAG 服务:2025 最新 AI + 向量数据库实战
java·redis·后端
HyggeBest8 小时前
Mysql之undo log、redo log、binlog日志篇
后端·mysql
java金融8 小时前
FactoryBean 和BeanFactory的傻傻的总是分不清?
java·后端
独立开阀者_FwtCoder8 小时前
Nginx 部署负载均衡服务全解析
前端·javascript·后端