之前提到的在XMLBeanFactory
构造函数中调用了XmlBeanDefinitionReader
类型的reader
属性提供的方法this.reader.loadBeanDefinitions(resource)
,而这句代码则是整个资源加载的切入点。
XMLBeanFactory
初始化时序图
loadBeanDefinitions
方法的时序图
从这个开始看!
Bean bf = new XmlBeanFactory(new ClassPathResource("test.xml");
在测试的BeanFactoryTest中首先调用ClassPathResource的构造函数来构造Resource资源文件的实例对象,这样后续的资源处理就可以用Resource提供的各种服务来操作了,当我们有了Resource后就可以进行XmlBeanFactory的初始化。
ClassPathResource
类
1.带一个参数的构造方法
上面的构造方法调用下面的带两个参数的构造方法
2.带两个参数的构造方法
将path的默认的calssLoader封装起来
XMLBeanFactory
类
3.带一个和两个参数的构造方法
有了Resource接口便可以对所有资源文件进行统一处理。至于实现,其实非常简单,以getInputStream为例子,ClassPathResource中的实现方式是通过class或者classLoader提供的底层方法进行调用。
XMLBeanDefinitionReader
类下的
用于对资源文件的编码进行处理
该方法调用EncodeResource
类的构造方法
从EncodeResource
类中返回回来继续看XmlBeanDefinitionReader
类下的loadBeanDefinitions
方法。
注意第【337】行代码,才进入真正的逻辑核心部分。
再看看这个loadBeanDefinitions方法执行的时序图
从上面的时序图中我们可以总结梳理出如下处理流程。
- 封装资源文件。
当进入XmlBeanDefinitionReader
后首先对参数Resource
使用EncodedResource
类进行封装。 - 获取输入流
从Resource
中获取对应的InputStream
并构造InputSource
- 通过构造的
InputSource
实例和Resource
实例继续调用函数doLoadBeanDefinitions
。
图7中调用图8中的方法,该方法大部分代码都是异常处理,主要的方法就在第【392】和【393】行。
第【392】行做了两件事情,但是这两件事情是调用doLoadDocument
方法类实现的,因此我们继续看doLoadDocument
方法
这个方法做了两件事情:
- 获取对Xml文件的验证模式
- 加载对应xml文件,并得到对应的Document。
获取XML
的验证模式
DTD
与XSD
区别
在使用 XML Schema
文档对XML
实例文档进行验证,除了要声明空间外(xmlns = "http://www.springframework.org/schema/beans"
),还必须制定该名称空间所对应的 XML Schema 文档的存储位置。通过 schemaLocation
属性来指定名称空间锁对应的 XML Schema
文档的存储位置。它包含两个部分,一部分是名称空间的 URI
,另一部分就是该名称空间锁标识的 XML Schema
文件位置或URL地址(xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
)
从图8.1中我们可以看到,doLoadDocument
方法实现XML文件的验证是通过调用getValidationModeForResource
方法来获取对应资源的验证模式。接下来我们看getValidationModeForResource
方法的源码。
从图8.2中可以看出来,验证模式分为两种:一种是手动指定、另一种是自动检测。而自动检测验证模式的功能时在函数detectValidationMode
方法中实现的,在detectValidationMode
中又将自动检测验证模式的工作委托给了专门处理类validationModeDetector.detectValidationMode
,我们一层一层往下看。先看detectValidationMode
方法。
从【第487行】可以看出,这个detectValidationMode
方法是一个逻辑层面的方法,主要的XML验证是由return this.validationModeDetector.detectValidationMode(inputStream);
实现的。
从上面代码也可以看出来,Spring用来检测验证模式的办法就是判断是否包含 DOCTYPE
,如果包含就是 DTD
,否则就是 XSD
。
获取Document
经过了验证模式准备的步骤就可以进行Document
加载了,验证模式准备在【图8.1】中,同样 XmlBeanFactoryReader
类对于文档读取并没有亲力亲为,而是委托给了DocumentLoader
去执行,这里的DocumentLoader
是一个接口,而真正调用的是DefaultDocumentLoader
。
注意这个方法一共有5个参数。
InputSource
EntityResolver
- 对于这个参数,传入的是通过
getEntityResolver
方法获取的返回值
- 对于这个参数,传入的是通过
ErrorHandler
int validationMode
boolean namespaceAware
为了解释EntityResolver
参数,我又把【图8.1】拿回来再显示一遍。
现在我们看看它是如何从getEntityResolver
中获取的数据。
这里先不具体的分析它是怎么解析出来的了,我们主需要知道下面这段话就可以了:
如果SAX 应用程序需要实现自定义处理外部实体,则必须实现此接口并使用 setEntityResolver方法向 SAX驱动器注册一个实例。也就是说,对于解析一个XML, SAX首先读取该XML文档上的声明,根据声明去寻找响应的DTD定义,以便对文档进行一个验证。默认的寻找规则,即通过网络(实现上就是声明的DTD的URI地址)来下载响应的DTD声明,并进行认证。下载的过程是一个漫长的过程,而且当网络中断或不可用时,这里会报错,就是因为响应的DTD声明没有被找到的原因。
======================================================================================================
解析及注册BeanDefinitions
至此,我们已经把【图8】中调用的第一个方法的功能分析完了,接下来我们分析第二个功能,我再将【图8】重新贴一下。
现在我们来分析调用的第二个方法registerBeanDefinitions
BeanDefinitionDocumentReader
是一个接口,而实例化的工作是在 createBeanDefinitionDocumentReader()
中完成的,而通过此方法, BeanDefinitionDocumentReader
真正的类型其实已经是 DefaultBeanDefinitionDocumentReader
了,进入 DefaultBeanDefinitionDocumentReader
后,发现这个方法的重要目的之一就是提取 root
,以便于再次将 root
作为参数继续 BeanDefinition
的注册。
接下来找到BeanDefinitionDocumentReader
的实现类DefaultBeanDefinitionDocumentReader
类
如果说以前一直是 XML
加载解析的准备阶段,那么 doRegisterBeanDefinitions
算是真正地开始进行解析了。
如果继承自 DefaultBeanDefinitionDocumentReader
的子类需要在 Bean
解析前后做一些处理的话,那么只需要重写这两个方法就可以了。
profile 属性的使用
该特性保证我们可以同时在配置文件中部署两套配置来适用于生产环境和开发环境,这样可以方便的进行切换开发、部署环境,最常用的就是更换不同的数据库。
上面【图9.3】的执行过程如下:首先程序会获取 beans 字节是否定义了 profile 属性,如果定义了则会需要到环境变量中去寻找,所以这里首先断言 encironment 不可能为空格,因为 profile 是可以同时制定多个的,需要程序对其拆分,并解析每个 profile 是都符合环境变量中所定义的,不定义则不会浪费性能去解析。
在下一节 Spring源码深度解析(2)中会继续深入解析parseBeanDefinitions
方法