源代码计划: 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;
}
相关推荐
潘多编程16 分钟前
Spring Boot微服务架构设计与实战
spring boot·后端·微服务
2402_8575893621 分钟前
新闻推荐系统:Spring Boot框架详解
java·spring boot·后端
2401_8576226623 分钟前
新闻推荐系统:Spring Boot的可扩展性
java·spring boot·后端
Amagi.2 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589362 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊2 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso2 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52822 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
原机小子3 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码3 小时前
详解JVM类加载机制
后端