(一)Spring源码解析:容器的基本实现

一、Spring的整体架构

Spring的整体架构图如下所示:

二、容器的基本实现

2.1> 核心类介绍

2.1.1> DefaultListableBeanFactory

DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册加载bean的默认实现。

XmlBeanFactory集成自DefaultListableBeanFactory,不同的地方是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取。源码部分如下图所示:

 DefaultListableBeanFactory类的继承关系如下图所示:

2.1.2> XmlBeanDefinitionReader

XML配置文件的读取是Spring中重要的功能,而XmlBeanDefinitionReader可以实现该功能。那么我们先来看一下这个类的继承关系:

  • ResourceLoader(接口:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource。
  • DocumentLoader(接口:定义从资源文件加载到转换为Document的功能。
  • BeanDefinitionDocumentReader(接口:定义读取Document并注册BeanDefinition功能。
  • BeanDefinitionParserDelegate(接口:定义解析Element的各种方法。
  • BeanDefinitionReader(接口:主要定义资源文件读取并转换为BeanDefinition的各个功能。
  • EnvironmentCapable(接口:定义获取Environment方法。
  • AbstractBeanDefinitionReader:对EnvironmentCapable、BeanDefinitionReader类定义的功能进行实现。

2.2> XmlBeanFactory的创建

如果想使用Spring,我们可以通过创建BeanFactory示例,然后调用getBean(...)来获得相应的实例对象,如下图所示:

  但是在获得BeanFactory的过程中,我们其实大体是经历了如下的几个步骤:

我们可以看到,第一步是获得了Resource。那么Resource是Spring用于封装底层资源,比如:FileURLClasspath等。对于不同来源的资源文件都有相应的Resource实现,比如:

针对文件资源:FileSystemResource
针对Classpath资源:ClassPathResource
针对URL资源:UrlResource
针对InputStream资源:InputStreamResource
针对Byte数组资源:ByteArrayResource
……

我们在日常开发工作中,如果需要对资源文件进行加载,也可以直接使用Spring提供的XxxResource类。比如,我们要加载oldbean.xml文件,则可以使用如下方式将其先转化为Resource ,然后再调用getInputStream() ,就可以或得到输入流了。那么针对于输入流的后续操作,与我们以往的处理方式是一样的。当然,除了能从Resource中获得InputStream之外,还可以获得FileURIURL等。具体请见下图所示:

 将Spring相关的配置文件封装为Resource类型实例之后,我们就可以继续创建XMLBeanFactory实例对象。如下是其构造函数源码:

2.3> loadBeanDefinitions(resource)加载bean

我们来看一个loadBeanDefinitions(...)方法的源码实现:

主要代码逻辑是用来根据xml配置文件的内容进行解析,然后使Spring加载bean。函数执行的时序图如下所示:

2.3.1> EncodedResource解析

我们发现resource又被EncodedResource类包装了一层。在构造EncodedResource实例的时候,我们可以指定resourceencodingcharset。具体源码如下图所示:

 那么当调用它的getReader()方法时,就会使用相应的字符集charset编码encoding作为输入流的charsetencoding

2.3.2> loadBeanDefinitions(EncodedResource encodedResource)

本方法就是针对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的注册。

 a> doLoadDocument(...)

doLoadDocument(...)方法中,我们需要关注下图中红框的两部分代码:

首先,我们来看一下getValidationModeForResource(Resource resource),具体源码逻辑如下图所示:

默认值为:VALIDATION_AUTO,如果发现现在的Mode不是VALIDATION_AUTO了,则说明有人自定义了,那么就返回自定义的Mode。如果没有被自定义,那么则通过detectValidationMode(resource)方法根据xml配置文件的格式,来确定Mode是DTD还是XSD

最后,我们来看一下detectValidationMode(resource)方法的具体实现,它到底是如何判断Mode的:

XML文件的验证模式保证了XML文件的正确性,而比较常用的有两种,即:DTD 和 XSD

DTDDocument Type Definition):它是一种XML约束模式语言,要使用DTD验证模式的时候需要在XML文件的头部声明****,并且它引用的是后缀名为.dtd的文件。如下所示:

XSDXML 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(...)方法,源码如下所示:

那么publicIdsystemId是什么呢?如果是下图中的XSD的配置文件,那么publicId=nullsystemId=http://www.springframework.org/schema/beans/spring-beans.xsd

如果是下图中的DTD的配置文件,那么publicId=-//SPRING//DTD BEAN 2.0//ENsystemId=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文件并加载。

b> registerBeanDefinitions(...)

在上面的内容中,我们已经将xml配置文件通过SAX解析成了Document实例对象了。那么下面,我们就要操作这个Document实例对象doc进行bean的注册操作了。在介绍具体操作细节之前,我们先看一下相关源码部分:

由于BeanDefinitionDocumentReader只是接口,所以通过createBeanDefinitionDocumentReader()方法其实创建的是它的实现类——DefaultBeanDefinitionDocumentReader。具体代码如下所示:

好了,看完了createBeanDefinitionDocumentReader()方法创建了DefaultBeanDefinitionDocumentReader实例之后,我们再继续看documentReader.registerBeanDefinitions(...)方法:

profile最主要的目的就是可以区分不同的环境,进而对不同环境进行配置。例如在devtestpreonlineonline等环境有各自的配置文件,我们就可以通过设置profile来在特定的环境下读取对应的配置文件。使用方式如下所示:

了解了profile之后,我们来看一下要执行xml解析的关键方法parseBeanDefinitions(root, this.delegate),它会根据表空间(如果命名空间等于"http://www.springframework.org/schema/beans",则表示是默认表空间,否则是自定义表空间)来判断,是进行默认标签解析还是自定义标签解析。相关源码如下所示:

默认标签<bean id="m416" class="com.muse.springbootdemo.entity.Gun">

自定义标签: <tx:annotation-driven>

对于默认标签来说,Spring自己就知道如何去解析;而对于自定义标签来说,就需要用户实现一些接口及配置了。那么关于这两种标签类型的解析,也就是我们后续关注的重点了。具体内容请见下一篇文章:默认标签解析

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值