Spring IOC 源码解析(七) BeanDefinition 的注册

一.项目准备

1.1 创建项目

首先创建一个简单的 maven 项目,并导入 spring 最基础的依赖包.
这里用到的 spring 版本是 5.1.3.RELEASE
在这里插入图片描述

因为我们只用到最简单 BeanFactory ,并没有用 ApplicationContext (高级封装的 BeanFactory),所以只要一个 spring-beans 的依赖即可

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>
    </dependencies>

1.2 创建基础类

我这里创建了2个基础类,一个启动 spring 的 main 类.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3 创建 spring.xml 配置文件

<?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">

    <bean id="student" class="com.zcsh.xmlbeanfactory.demo.bean.Student">
        <property name="name" value="zcsh"/>
        <property name="age" value="1"/>
    </bean>

    <bean class="com.zcsh.xmlbeanfactory.demo.bean.Teacher" >
        <property name="student" ref="student"/>
    </bean>

</beans>

可以看到我们在 xml 中创建了 student 的定义和 teacher 的定义,并将 teacher 中的 student 属性映射到前面定义的 student.

二.项目启动

我们直接运行 XmlBeanFactoryMain 的 main 方法,启动 XmlBeanFactory 启动容器初始化.

2.1 ClassPathResource

首先可以看到我们定义了一个 ClassPathResource .传的参数就是 xml 文件在类路径下的相对位置,因为我们的 spring.xml 放在的是 resources 根目录,所以可以直接传入文件名即可.

下面们来看看 ClassPathResource 是如何初始化的.
启动 Debug 模式开始解刨:

2.1.1 ClassPathResource 初始化

在这里插入图片描述

进入到下方主要构造器中,可以看到 ClassPathResource 在初始化的时候一共执行了3操作,

  1. 将转入的路径转化为类路径,在这里我们传入值 spring.xml 处理完还是原样,他会把一些特殊的转化,例如 /spring.xml -> spring.xml, Windows 下路径 \\spring.xml -> spring.xml
  2. 初始化属性值 path (也就是 spring.xml)
  3. 初始化 classLoader (也就是 AppClassLoader)

2.2 初始化 XmlBeanFactory

完成了 ClassPathResource 的创建后,就要用 XmlBeanFactory 解析这个资源路径的封装类来提取配置文件,然后进行解析,注册.

2.2.1 XmlBeanFactory 构造器

-w1144

第一步,我们的构造器会执行设置父工程(只有在使用 web IOC 容器的时候才会涉及父子容器).
XmlBeanFactory 的父类是 DefaultListableBeanFactory ,而 DefaultListableBeanFactory 在构造器中也是简单的调用父类的构造器,所以直接看有具体操作的构造器:
在这里插入图片描述

AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) 首先会调用自己的无参构造器. 无参构造器还是会调用父类 AbstractBeanFactory 的构造器,里面什么操作也没有,所以我们也不用进去看了.主要看调用的 ignoreDependencyInterface() 方法.

ignoreDependencyInterface() 功能是:设置忽略依赖指定接口的自动装配,也就是不会通过 @Autowired 注解注入这些接口的实现

下面的设置父工程方法,是指简单做了一个属性赋值:
在这里插入图片描述

第二步,创建 XmlBeanDefinitionReader 并执行 XmlBeanDefinitionReader 加载 BeanDefinition 方法.

XmlBeanDefinitionReader 的构造器需要传入一个 BeanDefinitionRegistry 的实现类,而 XmlBeanFactory 的父类 DefaultListableBeanFactory 是 BeanDefinitionRegistry 实现,所以 XmlBeanFactory 也具备 BeanDefinitionRegistry 的功能,即这里的构造器才会传入 this.

2.3 XmlBeanDefinitionReader 构造器

XmlBeanDefinitionReader 的构造器中直接调用的父类的构造器
在这里插入图片描述

父类 AbstractBeanDefinitionReader 的构造器
在这里插入图片描述

设置了 AbstractBeanDefinitionReader 的属性:

  1. registry (也就是 XmlBeanFactory 本身)
  2. resourceLoader (因为我们的 XmlBeanFactory 没有实现 ResourceLoader 所以是 PathMatchingResourcePatternResolver )
  3. environment ((因为我们的 XmlBeanFactory 没有实现 EnvironmentCapable 所以是 StandardEnvironment )

可以提前知道的是,我们在实际应用中都是使用的 ApplicationContext 的实现(BeanFactory 的高级封装), 而 ApplicationContext 都实现了 ResourceLoader 和 EnvironmentCapable 两个接口.所以如果传入的是 ApplicationContext 实现类,这里都会用 ApplicationContext 实现类的本身.

2.4 loadBeanDefinitions

结束了 XmlBeanDefinitionReader 创建,我们就要回到 2.1 中的第二步方法,执行 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法 this.reader.loadBeanDefinitions(resource);
在这里插入图片描述

可以看到实际执行的是下面的 loadBeanDefinitions 方法.将传入的 Resource (ClassPathResource) 封装成具有编码属性的 Resource;

根据图中的注解我们可以看到具体的流程,主要流程是 获取 Resource 的输入流 :看一看 ClassPathResource 如何重写getInputStream() 方法的:
在这里插入图片描述

最终我们的输入流是通过 this.classLoader.getResourceAsStream(this.path); 方式获取的.这里的 path 就是我们传入的 spring.xml.

获取完输入流,然后调用 doLoadBeanDefinitions 方法.所以我们进入 doLoadBeanDefinitions() 方法看一看

2.5 doLoadBeanDefinitions

看源码:
在这里插入图片描述

这里主要功能是将 io 流解析为 Docment 对象,然后将 Docment 传入到下一个处理环节.
Document 就是 xml 文件的封装类,我们来看一下 Document 对象中,我们熟悉的解析后的属性:
在这里插入图片描述

fNodeName 里面就是一系列的标签属性名等;而 fNodeValue 就是一系列的标签属性值;

2.6 registerBeanDefinitions

这里主要创建了解析 Document 对象的 DefaultBeanDefinitionDocumentReader 类.
在这里插入图片描述

2.7 DefaultBeanDefinitionDocumentReader.registerBeanDefinitions

获取 document 的元素
在这里插入图片描述
Element 的属性
在这里插入图片描述

将获取的 Element 传递给 doRegisterBeanDefinitions();

2.8 doRegisterBeanDefinitions

这里创建了通过 Element 作为基础的 BeanDefinition 解析委托类 delegate,
主要功能方法是 parseBeanDefinitions() ,这个方法是通过 BeanDefinition 解析委托类解析 Element 对象;
在这里插入图片描述

2.9 parseBeanDefinitions

这里主要作用是分离标签,分离 spring 自带标签和自定义的标签;
在这里插入图片描述

我们主要看解析 spring 自带标签的方法;

2.10 parseDefaultElement

这里是分离 spring 自带标签不同类别的地方,如 import,alias,bean,beans;
在这里插入图片描述

可以看到最下面的是解析 beans 标签的方法块,调用的是 2.8 的方法,也就是递归解析 beans 标签. 这里我们主要看解析 bean 标签的地方 processBeanDefinition()

2.11 processBeanDefinition

这个方法主要三个功能:

  1. 将 Element 真正的解析成 BeanDefinition 对象
  2. 将 BeanDefinition 注册到 BeanFactory 的容器中(Map 里面)
  3. 向 IOC 发送注册事件
    在这里插入图片描述
2.11.1 parseBeanDefinitionElement 解析 Element

这里直接上源码,源码上有注释;
在这里插入图片描述
这里面的主要方法是 parseBeanDefinitionElement() 看一下源码;
在这里插入图片描述
返回的就是解析完毕的 BeanDefinition 对象;

2.11.2 BeanDefinitionReaderUtils.registerBeanDefinition 注册 BeanDefinition

这里用传过来的 BeanDefinitionRegistry (也就是 DefaultListableBeanFactory)来注册 BeanDefinition
在这里插入图片描述
进入 registerBeanDefinition() 方法内
在这里插入图片描述

可以看到这里就是具体注册 BeanDefinition 的地方,在正常情况下都是会进入 860 行的方法块内;在这里会往 DefaultListableBeanFactory 的 beanDefinitionMap 和 beanDefinitionNames 集合中保存下来;

三.总结

经过一系列的摸索, 我们从创建 xml , 到通过类路径创建 resouce 封装类(ClassPathResource), 然后创建 XMLBeanFactory , 再获取 xml 输入流, 再将输入流解析为 document 对象, 然后解析 document 对象, 获取 element, 最后解析 element 获取 BeanDefinition , 注册 BeanDefinition.

BeanDefinition 的注册终于完毕了.当 BeanDefinition 注册完毕以后,在应用通过第一次调用 getBean() 方法获取 bean 实例的时候, 通过注册的 BeanDefinition 来创建具体的 bean 然后返回;这是我们后面要研究的方向.

Spring IOC 源码解析(八) Bean 的创建

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值