【有书共读】《Spring源码深度解读》第二章
第二章 容器的基本实现
笔记有github链接,用电脑看笔记效果更佳。
容器的基本用法:
Bean是Spring中最核心的东西,因为Spring就像是个大水桶,而bean就像容器里的水,水桶脱离了水,也就没什么用处了,所以此章主要就是讲bean的解析及注册。
在上面我们看到了bean的声明方式,尽管spring中bean的元素定义着N种属性来支撑我们业务的各种应用,但是只要我们声明成这样,基本上已经可以满足我们大多数的应用了。
类:
接下来写一下测试代码:
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
public void testSimpleLoad(){
BeanFactorybf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean bean = (MyTestBean)bf.getBean("myTestBean");
assertEquals("testStr", bean.getTestStr());
}
}
期望的结果:在Eclipse中显示Green Bar。
直接使用BeanFactory作为容器对Spring的使用来说不多见,甚至很少,企业中大多数使用ApplicationContext。
功能分析:
上面的测试代码究竟做了什么工作?无非一下几点:
(1)读取配置文件beanFactory.xml
(2)根据beanFactory.xml中的配置找到对应的类的配置,并实例化。
(3)调用实例化后的实例。
为了更清楚的描述,画一个类图。
ConfigReader:用于读取及验证配置文件。我们要用配置文件里边的东西,首先就是读取,然后放置在内存中。
ReflectionUtil:用于根据配置文件中的配置进行反射实例化。
App:用于完成整个逻辑的串联。
容器的基础XmlBeanFactory:
测试代码中:
BeanFactorybf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续就可以用Resource提供的各种服务来操作了,有了Resource后就可以进行XmlBeanFactory的初始化了。(Resource接口可以对所有的资源文件进行统一处理)
Resource接口封装底层资源的过程:https://github.com/spring-projects/spring-framework/blob/616a40adb64a09dc4caeee67f16aabb188eeae7a/spring-core/src/main/java/org/springframework/core/io/Resource.java
当通过Resource相关类完成了对配置文件进行封装,配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理。
了解了Spring中配置文件为Resource类型的实例方法后,可以继续探寻XmlBeanFactory的初始化过程,这里使用Resource实例作为构造函数参数的办法。
代码:
Public XmlBeanFactory(Resource resource)throws BeanException{
This(resource, null);
}
Public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws Beanexception{
Super(parentBeanFactory);
This.reader.loadBeanDefinitions(resource);
}
This.reader.loadBeanDefinitions(resource);才是资源加载的真正实现,绕了半天还没有切入正题,但是前边的资源封装工作也是非常必要的。
loadBeanDefinitions(resource)方法都做了什么?
(1)封住资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource(对资源文件的编码进行处理)类进行封装。
(2)获取输入流。从Resource中获取对应的InputStream并构造InputSource。
(3)通过InputSource实例和Resource实例继续调用函数doLoadBeanDefinitions。
loadBeanDefinitiontions方法链接:https://github.com/spring-projects/spring-framework/blob/a89e716cc71a8741385a0224b5d7eb7ce009e11a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java
再次梳理一下数据准备阶段的逻辑,首先对传入的Resource参数做封装,目的是考虑到Resource可能存在编码要求的情况,其实通过SAX读取XML文件的方式来准备InputSource对象,最后将准备的数据传入真正的狠心处理部分doLoadBeanDefinitions。
doLoadBeanDefinitions方法:
在上两段代码中,其实只做了三件事:
(1)获取对XML文件的验证模式。
(2)加载XML文件,并得到对应的Document。
(3)根据Document注册bean信息。
这三个步骤支撑着整个Spring容器部分的实现基础,尤其是第三部对配置文件的解析,逻辑非常复杂,那么,就从获取XML文件的验证模式开始。
获取XML的验证模式:
XML文件的验证模式保证了XML文件的正确性,而比较常用的模式有两种:DTD和XSD。
DTD(Document Type Definition)即文档性定义,DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签是否使用正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了xML文档的结构,可以用一个指定的XML Schema来雁阵给一个XML文档,以检查XML文档是否符合要求。在使用XML Schema文档对XML实例文档进行检验,除了要声明名称空间外,还必须指定该名称空间多对应的XML Schema文档的存储位置。包含两个部分,一部分是名称空间URI,另一部分就是该名称空间所标识的XML Schema文件位置或URL地址。
了解了DTD和XSD之后,看源码。
Spring通过getValidationModeForResource方法来获取对应资源的验证模式。
而自动检测验证模式的功能是在函数detectValidationMode方法中实现的
Spring用来检验验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
获取Document:
经过了验证模式准备的步骤就可以进行Document加载了,DocumentLoader去执行,DocumentLoader只是接口,而真正调用的是DefaultDocumentLoader:
通过SAX解析xML文档的套路大概差不多,首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。
对于传入参数EntityResolver,是什么?
官网这样解释:如果SAX程序要实现自定义处理外部实体,必须实现此接口并使用setEntityResolver方法向SAX驱动器注册一个实例。
EntityResolver的作用是项目本身就可以提供一个如何寻找DTD声明的方法,即由程序来实现寻找DTD声明的过程。
Spring中使用DelegatingEntityResolver类为EntityResolver的实现类,resolveEntity为实现方法。(publicID, systemID根据配置文件得到,此方法返回inputSource对象)
对于不同的加载模式Spring使用了不同解析器解析。最终得到了Document,现在要开始重头戏了,提取以及注册bean。
解析及注册BeanDefinitions:
这个方法中很好的运用了面向对象中的单一职责的原则,将逻辑处理委托给单一的类进行处理,而这个逻辑处理类就是BeanDefinitionDocumentReader。BeanDefinitionDocumentReader是一个接口,实例化工作则是在createBeanDefinitionDocumentReader()中完成,这个方法的重要目的之一就是提取root,以便于再次将root作为参数继续BeanDefinition的注册。
Public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext){
This.readerContext = readContext();
Logger.debug(“loading bean definitions”);
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
终于到了核心逻辑的底部doRegisterBeanDefinitions(root)之前都是在做准备工作。
截止目前,解析工作将要真正的开始了,具体内容一下笔记再见!
注: 另外一点就是:Spring对于实现某一块,并不是直接实现,是通过很多调用,和很多验证,
层层嵌套,直到do开头的方法中才会看到真正的实现逻辑。如registerBeanDefinitions,
registerBeanDefinitions这个方法内并没有真正的实现,只是进行一些操作,而真正的实
现是在doRegisterBeanDefinitions,其他的也是如此,如createBean,真正的实现逻辑是
在doCreateBean……