Spring的整体架构图如下所示:
DefaultListableBeanFactory
是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。
XmlBeanFactory
集成自DefaultListableBeanFactory,不同的地方是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader
,实现了个性化的BeanDefinitionReader读取。源码部分如下图所示:
DefaultListableBeanFactory
类的继承关系如下图所示:
XML配置文件的读取是Spring中重要的功能,而XmlBeanDefinitionReader
可以实现该功能。那么我们先来看一下这个类的继承关系:
• ResourceLoader(接口):定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。
• DocumentLoader(接口):定义从资源文件加载到转换为Document的功能。
• BeanDefinitionDocumentReader(接口):定义读取Document并注册BeanDefinition功能。
• BeanDefinitionParserDelegate(接口):定义解析Element的各种方法。
• BeanDefinitionReader(接口):主要定义资源文件读取并转换为BeanDefinition的各个功能。
• EnvironmentCapable(接口):定义获取Environment方法。
• AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。
如果想使用Spring,我们可以通过创建BeanFactory
示例,然后调用getBean(...)
来获得相应的实例对象,如下图所示:
但是在获得BeanFactory的过程中,我们其实大体是经历了如下的几个步骤:
我们可以看到,第一步是获得了Resource
。那么Resource是Spring用于封装底层资源,比如:File
、URL
、Classpath
等。对于不同来源的资源文件都有相应的Resource实现,比如:
针对文件资源:
FileSystemResource
针对Classpath资源:ClassPathResource
针对URL资源:UrlResource
针对InputStream资源:InputStreamResource
针对Byte数组资源:ByteArrayResource
……
我们在日常开发工作中,如果需要对资源文件进行加载,也可以直接使用Spring提供的XxxResource
类。比如,我们要加载oldbean.xml
文件,则可以使用如下方式将其先转化为Resource ,然后再调用getInputStream() ,就可以获得到输入流了。那么针对于输入流的后续操作,与我们以往的处理方式是一样的。当然,除了能从Resource中获得InputStream
之外,还可以获得File
、URI
和URL
等。具体请见下图所示:
将Spring相关的配置文件封装为Resource类型实例之后,我们就可以继续创建XMLBeanFactory实例对象。如下是其构造函数源码:
我们来看一个loadBeanDefinitions(...)
方法的源码实现:
主要代码逻辑是用来根据xml配置文件的内容进行解析,然后使Spring加载bean。函数执行的时序图如下所示:
我们发现resource又被EncodedResource
类包装了一层。在构造EncodedResource实例的时候,我们可以指定resource
、encoding
和charset
。具体源码如下图所示:
那么当调用它的getReader()
方法时,就会使用相应的字符集charset和编码encoding作为输入流的charset
和encoding
。
本方法就是针对EncodedResource实例对象进行bean的加载。具体执行如下3个步骤:
步骤1:将入参
encodedResource
保存到currentResources
中,用于记录当前被加载的资源。如果发现已经存在了,则抛异常,终止资源加载。
步骤2:从encodedResource
中获得输入流InputStream
,并创建inputSource
实例对象。如果在encodedResource
中配置了编码(encoding),则为inputSource
配置该编码。
步骤3:调用doLoadBeanDefinitions(...)
方法从资源中加载bean。
具体源码实现逻辑,请见下图:
需要注意的一点是,InputSource不是Spring提供的类,它的全路径名是
org.xml.sax.InputSource
,用于通过SAX读取XML文件的方式来创建InputSource对象。
下面我们来看一下doLoadBeanDefinitions(...)
方法的具体实现。在这个方法中,主要做了两件事件:
步骤1:加载
XML
配置文件,然后将其封装为Document
实例对象。
步骤2:根据Document
实例和Resource
实例,执行Bean的注册。
在doLoadDocument(...)
方法中,我们需要关注下图中红框的两部分代码:
首先,我们来看一下getValidationModeForResource(Resource resource)
,具体源码逻辑如下图所示:
默认值为:VALIDATION_AUTO,如果发现现在的Mode不是VALIDATION_AUTO了,则说明有人自定义了,那么就返回自定义的Mode。如果没有被自定义,那么则通过
detectValidationMode(resource)
方法根据xml配置文件的格式,来确定Mode是DTD还是XSD。
最后,我们来看一下detectValidationMode(resource)
方法的具体实现,它到底是如何判断Mode的:
XML文件的验证模式保证了XML文件的正确性,而比较常用的有两种,即:DTD 和 XSD。
DTD(
Document Type Definition
):它是一种XML约束模式语言,要使用DTD验证模式的时候需要在XML文件的头部声明****,并且它引用的是后缀名为.dtd
的文件。如下所示:
XSD(
XML Schemas Definition
):用于描述XML文档的结构。它引用的是后缀名为.xsd
的文件。如下所示:
看完getValidationModeForResource(resource)
方法之后,我们再来看一下documentLoader.loadDocument(...)
方法。为了便于理解,我们再次将相关代码粘贴出来:
loadDocument(...)
方法是通过SAX解析XML文档,这段代码是套路性的代码,没什么好说的。就是创建DocumentBuilderFactory——>创建DocumentBuilder——>调用parse(inputSource)方法解析inputSource实例然后返回Document对象。
在上面黄框圈中的EntityResolver
实例,它的作用是:DTD默认寻找规则是通过网络(即:声明的DTD的URI地址)来下载相应的DTD声明,并进行认证。由于网络原因,下载速度本身就是耗时的。那么,我们可以通过EntityResolver来实现寻找DTD声明的过程,比如:我们将DTD文件放到项目中的某个路径下,在实现时直接将此文档读取并返回给SAX即可。
黄框中的EntityResolver
实例,它是一个接口,并且提供了一个resovleEntity(...)
方法,源码如下所示:
那么publicId
和systemId
是什么呢?如果是下图中的XSD的配置文件,那么publicId=null
,systemId=http://www.springframework.org/schema/beans/spring-beans.xsd
如果是下图中的DTD的配置文件,那么publicId=-//SPRING//DTD BEAN 2.0//EN
,systemId=https://www.springframework.org/dtd/spring-beans-2.0.dtd
好了,了解了publicId和systemId之后,我们要将关注点放在对EntityResolver
实例的获取过程了,在doLoadDocument(...)
方法中,是通过 getEntityResolver()
获得的,那我们就来看一下getEntityResolver()
的具体实现:
既然最终都是要通过调用DelegatingEntityResolver
的构造方法,我们就来看看它的内部实现:
BeansDtdResolver:是直接截取systemId最后的xx.dtd,然后去当前路径下寻找。
PluggableSchemaResolver:是默认到META-INF/spring.schemas文件中找到systemId所对应的XSD文件并加载。
在上面的内容中,我们已经将xml配置文件
通过SAX解析成了Document实例对象
了。那么下面,我们就要操作这个Document实例对象doc进行bean的注册操作了。在介绍具体操作细节之前,我们先看一下相关源码部分:
由于BeanDefinitionDocumentReader
只是接口,所以通过createBeanDefinitionDocumentReader()
方法其实创建的是它的实现类——DefaultBeanDefinitionDocumentReader。具体代码如下所示:
好了,看完了createBeanDefinitionDocumentReader()
方法创建了DefaultBeanDefinitionDocumentReader
实例之后,我们再继续看documentReader.registerBeanDefinitions(...)
方法:
profile最主要的目的就是可以区分不同的环境,进而对不同环境进行配置。例如在dev
、test
、preonline
、online
等环境有各自的配置文件,我们就可以通过设置profile来在特定的环境下读取对应的配置文件。使用方式如下所示:
了解了profile之后,我们来看一下要执行xml解析的关键方法parseBeanDefinitions(root, this.delegate)
,它会根据表空间(如果命名空间等于"http://www.springframework.org/schema/beans
",则表示是默认表空间,否则是自定义表空间)来判断,是进行默认标签解析还是自定义标签解析。相关源码如下所示:
默认标签:
自定义标签:
对于默认标签来说,Spring自己就知道如何去解析;而对于自定义标签来说,就需要用户实现一些接口及配置了。那么关于这两种标签类型的解析,也就是我们后续关注的重点了。