一.项目准备
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操作,
- 将转入的路径转化为类路径,在这里我们传入值
spring.xml
处理完还是原样,他会把一些特殊的转化,例如/spring.xml
->spring.xml
, Windows 下路径\\spring.xml
->spring.xml
- 初始化属性值 path (也就是 spring.xml)
- 初始化 classLoader (也就是 AppClassLoader)
2.2 初始化 XmlBeanFactory
完成了 ClassPathResource 的创建后,就要用 XmlBeanFactory 解析这个资源路径的封装类来提取配置文件,然后进行解析,注册.
2.2.1 XmlBeanFactory 构造器
第一步,我们的构造器会执行设置父工程(只有在使用 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 的属性:
- registry (也就是 XmlBeanFactory 本身)
- resourceLoader (因为我们的 XmlBeanFactory 没有实现 ResourceLoader 所以是 PathMatchingResourcePatternResolver )
- 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
这个方法主要三个功能:
- 将 Element 真正的解析成 BeanDefinition 对象
- 将 BeanDefinition 注册到 BeanFactory 的容器中(Map 里面)
- 向 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 然后返回;这是我们后面要研究的方向.