从FileSystemXmlApplicationContext窥探spring源码一角

一.一切从BeanFactory和ApplicationContext说起

 

beanfactory

beanFactory是Spring的一个顶级接口,为什么说他"顶级"?因为他是spring里面最高层次的一个接口,也就是说,他没有继承任何接口,他就是自成一派,就是最大的老大了。如下图:

有图有真相,绝对够顶级了,没有继承任何其他接口。

实际上这个beanFactory定义了ioc容器的一系列最最基本的功能,你可以理解他就是一个水桶,通用型的水桶。它的子类会将它扩展成为各种各样型号的水桶,比如塑料水桶,金属水桶。我们可以来看看这个接口下面定义来那些操作:

可以看到,定义了ioc的一系列最最基本的操作,比如判断ioc里面当前是否包含了某个bean,根据不同的参数,重载了getBean,主要是要摄适应多种不同的场景,取出对应bean。【可以根据bean的类型,bean的name等等····】

 

ApplicationContext

从名字上可以大概知道他的含义,应用程序上下文。这里的应用程序,指的就是spring的应用程序,而spring的应用程序又是指啥?没错,就是ioc容器,你可以理解为上面说的beanFactory。

那么到这里,他们的关系,大概也能总结为这么一句:ApplicationContext包含beanfactory。

关系详解:为什么说他们是一个包含的关系?不是说beanfactory是最顶级的吗?这里说的包含,指的是ApplicationContext持有一个beanfactory类型属性。而ApplicationContext又分为各种各样的ApplicationContext,比如FileSystemXmlApplicationContext。从名字我们能知道,这个类的作用,实际上就是从文件系统中的某个xml文件,读取出所有的bean。然后在调用内部持有的beanfactory属性,对这些bean做注册。从这里可以看出来,spring为了我们使用方便,为我们定义了一系列的ApplicationContext,为的就是不需要我们直接去操作beanfactory【当然,这中间不仅不需要直接操作beanfactory,还有很多操作spring都帮我们做了,比如读取资源,解析等等】。这个固然很好,但是存在一个不是很灵活的问题。例如:FileSystemXmlApplicationContext使用的beanfactory是DefaultListableBeanFactory,但是如果有一天,我们不想要用DefaultListableBeanFactory呢?我们想要用一个我们自定义的beanfactory呢?那这个就没法搞了。

 

我们可以来看看单独使用DefaultListableBeanFactory的过程:

可以看到,整个过程还是比较麻烦的,用户需要自己定义好beanfactory,自己读取出xml,自己向工厂去注册bean。但是他整个过程显得非常灵活,灵活在哪?第一行的这个xml文件的读取,你可以定义成其他的,比如从文件系统中读取,不一定要从类路径读取,从网络读取等等。最后把读取到的内容抽象成一个Resource,交给bean工厂。

 

下面我们看看如果使用ApplicationContext来搞,是怎么做的

什么?没看出区别?区别不大?好吧,听我解释。

首先,使用ApplicationContext来做,比自己去组建数据,少了很多步骤,你只需要定义好一个xml路径,放入FileSystemXmlApplicationContext的参数,就能得到一个工厂。【现在你应该能理解这个“上下文”具体值什么了把?没错,指的就是spring帮你把资源组建等琐碎事情包揽下来之后,形成的一个环境,你可以理解为,之前单独使用DefaultListableBeanFactory,我们自己的ClassPathResource,XmlBeanDefinitionReader,就是我们自己的上下文。而现在spring帮你做了这一切,那些这些就变成spring的上下文了】

从这里也能看出,通用型和定制化的区别。通用,意味着用户需要拥有搭积木的技能,能够熟悉每个组件,每个积木的功能。定制化就类似于傻瓜式组件,厂家给你组装好,你直接一键使用即可,但是有时候出了问题,不好处理,因为组件之间紧紧耦合在一起了。至于哪种好,还得看具体的场景,没有绝对的好坏之分。

【当然了,在实际开发中,这些我们是绝对不会直接使用的这些组件的,例如:使用FileSystemXmlApplicationContext读取一个xml文件,但是spring内部依然会使用这些组件来构建ioc容器,所以学习这些组件还是非常有必要的】

 

说了这么多,我们来看看ApplicationContext把:

 

下面看看从spring技术内幕摘出来的图

可以看到ApplicationContext衍生出了2个基本的ApplicationContext,一个是ConfigturationApplicationContext,一个是WebApplicationContext,而其他的ApplicationContext都是基于这两个接口去做的扩展。

 

二.图解FileSystemXmlApplicationContext的整个调用过程

下面我们就以FileSystemXmlApplicationContext为例,来康康spring是如何把bean加载进来,并且注册到ioc中。

【这个过程只是流程解析,不涉及具体的代码解析,看完整个流程,再去看具体的代码,相信你会有一个全局的把握】

在这里我们先做大模块的分析【有三个】,知道了每个模块具体干了什么事情,再具体深入到每个模块的细节去看看。

 

模块一【定位资源】

先来看看FileSystemXmlApplicationContext关系继承图【只列出基本的继承】

spring把功能区分的非常清晰,不同的类职责不同

在FileSystemXmlApplicationContext内部,会通过调用到它的父类的各种方法,来完成整个ioc的实例化【他有很多个父类,位高权重,事情自己的不用做,不同的老爸有不同的功能,分工合作】

具体的初始化过程看模块一的详细解释

 

模块二【读取定位到的资源】

这一部分可能有点绕,需要打开源码,debug去仔细看看。大体的调用流程如下:

上图详解:

实际上这里涉及到一个回调的过程,比较绕。

首先我们假设有一个类A,一个类B,一个接口C,接口C有一个getRosurce方法,A和B都实现了C,那么A和B都有getResources方法,然后我们让A持有一个B的引用。A的getResources方法,委托给B去执行。

这里的A指的就是AbstractApplicationContext,B指的是PathMatchingResourcePatternResolver,C指的是ResourceLoader。

同时,PathMatchingResourcePatternResolver内部需要持有一个DefaultResourceLoader实例,在无法加载Resource的时候,会调用DefaultResourceLoader的getRsourceByPath来获取到Resource。而AbstractApplicationContext就继承了DefaultResourceLoader,所以PathMatchingResourcePatternResolver内部又持有一个DefaultResourceLoader的引用,这里会吧AbstractApplicationContext转成一个DefaultResourceLoader,让PathMatchingResourcePatternResolver去持有。

说了那么多,来看个图,一图胜千言:

下面是他们的关系图

第三方调用时的关系图【这里的第三方就是Reader器的回调】:

 

这里对第三张图再总结一下:ApplicationContext实例化的时候,会实例化内部持有的一个ResourceLoader属性【实际为PathMatchingResourcePatternResolver】,然后PathMatchingResourcePatternResolver里面会持有一个ResourceLoader【实际为DefaultResourceLoader】。ApplicationContext实例化了一个XmlBeanDefinitionReader,然后把自己的this,set给XmlBeanDefinitionReader,然后XmlBeanDefinitionReader再loadBeanDefinitions的时候,会调用这个this的getResource方法,【this为ApplicationContext】,然后ApplicationContext会调用到内部的PathMatchingResourcePatternResolver,PathMatchingResourcePatternResolver如果发现加载不了Resource,那么会回调内部的ApplicationContext,调用其getResourceByPath。

具体的读取过程,看模块二的详细解释。

 

模块三

这里就比较简单的,就一句话:XmlBeanDefinitionReader把doc读取和注册的工作委托给了DefaultBeanDefinitionDocumentReader。那么这个DefaultBeanDefinitionDocumentReader怎么来的?我们可以看看源码,实际上是通过方法直接实例化出来的:

最后,我们来到注册的代码看看:

到这里整个流程就结束了,ioc的注册我们后面讲,其实无非就是向1个map,1个list,1个set,存放之前读取到的beanDef。

当然,这三个东西都是DefaultListableBeanFactory里面的三个属性。

 

 

三.模块一的解析

FileSystemXmlApplicationContext初始化调用父类的过程。

先来看看FileSystemXmlApplicationContext是怎么触发父类初始化的:

很明显,调用了refresh来刷新ioc容器的初始化。至于那个super(parent),是ioc的一个双亲机制,这个可以传入null,表示这个ioc没有父类。

 

我们来看看refresh

这里要注意的是,refresh是调用到了父类里面,也就是AbstractApplicationContext。

 

进入obtainFreshBeanFactory看看

 

继续跟入,这里调用到的是AbstractRefreshableApplicationContext

首先一上来,如果你之前有了新的ioc容器,直接干掉。然后创建一个DefaultListableBeanFactory类。然后马上开始加载beanDef。

 

跟入loadBeanDefinitions,上来直接创建一个XmlBeanDefinitionReader,用来解析xml文件。

比较重要的是读取器的setResourceLoader(this),把本类的引用设置进去,在Reader里面,才能回调到本类的ResourceLoad。

本身Reader并不作Resource的寻找,只会读取。

 

调用loadBeanDefinitions的一个重载方法,把Reader器传入。

直接调用到了reader器的loadBeanDefinitions。

 

来看看流程图:

 

四.模块二的解析

Reader的调用过程。

先来看看对应的结构继承图:

 

接着模块一的调用

 

进入loadBeanDefinitions看看,实际上这里是进入了父类,也就是AbstractBeanDefinitionReader

这里有几个重载的方法,我们整理一下:

loadBeanDefinitions(...String) A
loadBeanDefinitions(String) B
loadBeanDefinitions(...Resource) C
loadBeanDefinitions(Resource) => 子类实现 D

含义解析:首先A会遍历所有的路径【eg:classpath:spring.xml】,可以是一个数组。对于每个路径,调用B。B进行解析【上面已经说过了,解析会回调到AbstractApplicationContext的ResourceLoad器】,解析出来出来,得到一个Resource数组。调用C,C会再调用D。D在AbstractBeanDefinitionReader里面没有实现,是一个接口,它的实现在子类中,XmlBeanDefinitionReader就是其中一个。

 

关于XmlBeanDefinitionReader的loadBeanDefinitions,我们在上面也简单讲过了,就是委托了给了另外一个Read去进行doc的读取,然后进行ioc容器注册。

本次分析就到这里,下次分析我们将会对ioc的注册进行深入分析。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值