Spring-源码解析

从入职第一天起就得知咱公司的核心开发框架SOFA,而SOFA是基于Spring容器框架的。在使用SOFA一段时间以后,最近有兴趣研究一下Spring的源代码。本文主要将调试过程详细记录下来,一来加深印象,二来将经验沉淀下来,希望能对大家有所帮助。

本文结构如下:

1:新建Spring工程用于调试
2:IoC容器初始化代码调试

  2.1    资源的定位

  2.2    资源的解析

  2.3    资源的注册 

1.新建Spring工程用于调试

本人开发IDE为Intellij,所以本文基于Intellij建立一个Spring IOC实例用于调试IOC代码。

首先在Intellij上选择File->New->Project,如下图1所示:

blob.png

图1:新建Spring工程

选择Spring->Spring MVC->Next如下图2所示:

blob.png

图2:给Spring工程命名

项目生成好以后在src目录下新建package:com.alipay.spring.web 并新建几个类,如下图3所示

blob.png

图3:新建Spring工程相关类-UserDAO类

blob.png

图4:新建Spring工程相关类-UserDAOImpl类

blob.png

图5:新建Spring工程相关类-UserManager类

blob.png

图6:新建Spring工程相关类-UserTest类

在applicationContext.xml中声明相关bean,如图7所示:

blob.png

图7:在applicationContext.xml中声明bean

值得注意的是:applicationContext.xml必须放在src目录下,否则UserTest类会找不到applicationContext.xml而报错。

下面我们运行一下这个项目,运行结果如图8所示:

blob.png

图8:Spring工程运行结果

我们发现在控制台打印出了:”add a user”。那么这个是怎么打印出来的?这就是我们要研究的问题。

2.IOC容器初始化代码调试

    我们可以把IOC容器看做一个有隔间的水桶,这个水桶可以装各种各样的水——盐水,糖水,纯净水……但是要让这个水桶发挥作用,必须满足一些条件:首先,这个水桶能找到水源;其次,这个水桶能把这些水分门别类装配到水桶不同的隔间里面;最后这个水桶能给打水的人提供装配好的水。(自己理解的,不当之处还请指正)。

根据上面的类比,我们把IOC容器的初始化分为三个部分

1)         资源的定位

2)         资源的解析

3)         资源的注册

在我们这个项目中,资源就是applicationContext.xml。Spring容器首先会对它进行定位,确定他的位置。然后他会对applicationContext.xml进行解析,解析成Spring能够识别的数据结构。然后Spring会把解析好的数据结构注册到Spring容器中以备下次调用。下面我们分三步进行调试。

2.1 资源的定位

       我们在main函数的第一行打上断点,开启调试模式,如下图9所示:

blob.png

图9:开启调试模式

接下来,我们进入ClassPathXmlApplicationContext这个类,它有一个构造函数,入参是一个String,如图10所示:

blob.png

图10:一个入参的ClassPathXmlApplicationContext构造函数

它会调用另外一个3个入参的构造函数,如图11所示:

blob.png

图11:三个入参的ClassPathXmlApplicationContext构造函数

在这个构造函数中,会有一个setConfigLocation函数,入参就是我们传入的”applicationContext.xml”。我们进去看看这个函数的实现,如图12所示:

blob.png

图12:setConfigLocations函数实现

在这个函数中,会把传入的locations赋值给configLocations这个变量,这个变量就是用于存储资源位置的地方,以后想要获取资源,直接从这个读取这个变量就行了。这就相当于已经找到水源,并进行了标记,下面就要对这个水源进行装配。

我们可以画出资源定位的时序图,如图13所示:

blob.png

图13:资源定位流程图

2.2 资源的解析

这一步在IOC容器的初始化过程中至关重要,也是最核心的一步。我们继续从刚才的ClassPathXmlApplicationContext构造函数着手,如图14所示:

blob.png

图14:三个入参的ClassPathXmlApplicationContext构造函数

我们进入这个构造函数的refresh()函数,如下图15所示:

blob.png

图15:refresh()函数实现

refresh()很长,但是与我们资源的解析和注册都在这个函数里面,我们进入这个函数,如图16所示:

blob.png

图16: obtainFrshBeanFactory函数实现

在obtainFrshBeanFactory函数主要是构造好beanFactory,然后获取这个beanFactory返回给上层调用,我们先看getBeanFactory()函数实现,如图17所示:

blob.png

图17:getBeanFactory()函数实现

由代码可知,getBeanFactory基本上就是获得beanFactory这个字段并返回,也就是说refreshBeanFactory就是为了设置好beanFactory这个字段,至于如何设置这个beanFactory,我们继续看refreshBeanFactory函数代码实现,如图18所示:

blob.png

图18:refreshBeanFactory函数实现

这个函数的第一个if条件判断是用来清理已存在的BeanFactory,以便创建新的BeanFactory.接下来会创建一个新的空的beanFactory,如下截图19所示:

blob.png

图19:创建一个初始化的BeanFactory

这一步相当于在水桶中已经准备好一个隔间,下面就要根据标记的水源把资源load到隔间中,而这个load的过程就在loadBeanDefinitions.我们进去看它的实现,如图20所示

blob.png

图20:loadBeanDefinitions第一层实现

该函数首先创建一个XmlBeanDefinitionReader用于从标记的资源地获取资源。下面我们继续看看他是如何读取资源的,如图21所示

blob.png

图21:loadBeanDefinitions第二层实现

这个函数主要用于获取资源标记,根据上图12,我们把包含bean的applicationContext位置存放在configLocations变量中,所以该函数的getConfigLocation就能获取资源地址,函数实现如图22所示:

blob.png

图22:getConfigLocations函数实现

获得资源地址后如下图23所示:

blob.png

图23:获取资源地址

然后我们看reader是如何对这个资源进行解析的,如下图24所示:



blob.png

blob.png

blob.png

图24:初始化ResourceLoader

从图24可以看出,函数首先将String类型的location初始化一个ResourceLoader对象,并进一步得到Resouce对象。这个对象封装了对XML文件的I/O操作,所以读取器可以在打开I/O流后得到XML的文件对象。读取实现如图25所示:


blob.png

blob.png

图25:Resource的文件操作

正常情况下,文件操作会得到一个Document类型的XML文件对象,有了这个文件对象后,就可以按照Spring的Bean定义规则来对这个XML的文档树进行解析了,这个解析是交给BeanDefinitionParserDelegate来完成的。我们先来看看这个BeanDefinitionParserDelegate这个对象模型,如图26所示:


blob.png

blob.png

blob.png

图26:BeanDefinitionParserDelegate对象模型

从图26可以看出,BeanDefinitionParserDelegate对象模型就是在applicationContext.xml中定义一个bean的各个属性。下面我们看看如何根据BeanDefinitionParserDelegate来解析applicationContext.xml,如图27所示:

blob.png

图27:bean解析过程

这个函数里面就是详细解析xml文件各个节点的地方。首先,该函数会根据className生成一个初始化的BeanDefinition,然后给这个BeanDefinition一步步增加节点元素。比如parseMetaElements就是解析key-value的地方,parseBeanDefinitionAttributes就是详细解析bean的定义的地方。如图28所示:


blob.png

blob.png

图28:xml文件详细解析

经过图28的层层解析,就会得到一个具有详细节点信息的BeanDefinition,并设置到BeanDefinitionHolder中去,如图29所示:

blob.png

图29:BeanDefinitionHolder对象

上面介绍了对bean元素进行解析的过程,也就是BeanDefinition依据XML的定义被创建的过程。这个BeanDefinition可以看成是对定义的抽象。这个数据对象中封装的数据大多都是定义相关的,也有很多就是我们在定义bean时看到的那些spring标记,如key-value,init-method,destory-method等等。这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据,这些基本数据都是IoC容器需要的。有了这些基本数据,IoC容器才能对Bean配置进行处理,才能实现想应的容器特性。

经过上述载入过程,IoC容器大致完成了管理Bean对象的数据准备工作。也就是说基本上把水放进了隔间。但是,严格的说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需要完成数据向容器的注册。

我们可以根据上面的调试步骤画出资源解析的流程图,如图30所示:

blob.png

图30:资源解析流程图

2.3 资源的注册

IoC容器的注册过程是通过一个HashMap来持有载入的BeanDefinition的。具体的注册函数如图31所示:

blob.png

图31:容器注册入口

我们进入这个入口,看具体的函数实现,如图32所示:


blob.png

blob.png

图32:容器注册实现

这个容器注册过程其实并不复杂,就是把解析得到的BeanDefinition设置到hashMap中去。完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时这些BeanDefinition已经可以被容器使用了,他们都在beanDefinitionMap里被检索和使用,容器的作用就是对这些信息进行处理和维护,这些信息是容器建立依赖反转的基础。

最后,我们可以根据上述步骤画出资源注册的流程图,如图33所示

blob.png

图33:资源注册流程图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值