在Spring IOC容器的设计中,我们可以看到两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本的功能,另一个是ApplicationContext,他作为容器的高级形态而存在,应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配,有了这两种基本的容器系列,基本上可以满足用户对IOC容器使用的大部分需求了。
Spring IOC 容器系列
IOC容器作为开发者管理对象之间的依赖关系提供了许多便利和基础服务,有许多IOC容器供开发者选择,SpringFramework 的IOC 核心就是其中的一个,它是开源的,那具体什么是IOC容器呢?它在Spring框架之中到底长什么样?其实对IOC容器的使用者来说,我们经常接触到的BeanFactory和ApplicationContext都可以看成是容器的具体表现形式,如果深入到Spring的实现中去看,我们通常所说的IOC容器,实际上代表着一系列功能各异的容器产品,只是容器的功能有大有小,有各自的特点,以水桶为例,在商店中出售的水桶大小各异,制作材料也各不相同,但只要能装水,具备水桶的基本特性,那么就可以作为水桶出售,来让用户使用,是用什么样的水桶完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那对容器的使用是非常有帮助的,就像我们在购买商品之前,对商品进行观察和挑选那样,这次我们从代码的角度入手,可以看到关于这一系列容器的设计情况,
就像商品需要有产品基本规格一样,同样作为IOC容器,也需要为它的具体实现指定基本的功能规范,这个功能的基本规范的设计表现为接口类的BeanFactory,它体现了Spring为用户使用的IOC容器所设定的最基本的规范,还是以前面的水桶为例,如果把IOC容器看作为水桶,那么这个BeanFactory就定义了可以作为水桶的基本功能,比如至少能装水,有个提手等,除了满足基本的功能,为了不同场合的需要,水桶的生产者还需要提供更丰富的功能,有简约型的,有豪华型的、等,但是不管是什么水桶,它都需要一项最基本的功能,能装水,那对Spring的具体IOC容器实现来说,他需要满足什么样的基本特性呢?它需要满足BeanFactory这个基本接口的定义,它是作为一个最基本的接口类出现在Spring IOC容器系列中的,
在这些 Spring提供的基本IOC容器的接口定义和实现基础之上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间相互依赖关系,BeanDefinition抽象了我们对于Bean的定义,是让容器起作用的主要数据类型,我们都知道 在计算机世界类,所有的功能都是建立在通过 现实数据进行抽象的基础上的,IOC容器是用来管理对象依赖关系的,对IOC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕着对这个BeanDefinition的处理来完成的,这些BeanDefinition就像容器里装的水,有了这些基本数据,容器才能发挥作用,
同时在使用IOC容器时,连接BeanFactory和ApplicationContext之间的区别对我们理解和使用IOC容器也是比较重要的,弄清楚这两种重要容器之间的 区别和联系,意味着我们具备了辨别容器系列中不同容器产品的能力,还有一个好处就是,如果需要定制特定功能的容器实现,也能比较方便的在容器系列中找到一款恰当的产品作为参考,不需要重新设计。
Spring IOC容器的设计
- 从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory是一条主要的BeanFactory设计路径,在这条接口设计路径中,BeanFactory接口定义了基本的IOC容器的规范,在这个接口定义中,包括了getBean这样的IOC容器的基本方法,而HierarchicalBeanFactory接口在继承了BeanFactory基本接口之后,增加了getParentBeanFactory的接口功能,使BeanFactory具备了双亲IOC容器的管理功能,在接下来的ConfigurableBeanFactory中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory设置双亲IOC容器,通过addBeanPostProcessor配置Bean的后置处理器,等等,通过这些接口的叠加,定义的BeanFactory就实现了简单的IOC容器的基本功能
- 第二条设计的主线是:以ApplicationContext应用上下文为核心的接口设计,这里主要涉及的接口设计有:从BeanFactory到ListableBeanFactory,再到ApplicationContext,在到我们常用的WebApplicationContext或者ConfigurableBeanFactory接口,我们常用的应用上下文基本上都是ConfigurableBeanFactory或者 WebApplicationContext的实现,在这个接口体系中,ListableBeanFactory和HierarchicalBeanFactory两个接口,连接BeanFactory接口定义和ApplicationContext应用上下文的接口定义,在ListableBeanFactory中,细化了许多BeanFactory的接口功能,比如定义了getBeanDeifinitionNames接口方法。对于HierarchicalBeanFactory上面已经提到过了,这两个接口其实仔细看 是属于一类接口,纯属个人看法,对于ApplicationContext接口,它通过集成MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IOC容器基础上添加了许多对高级容器的特定的支持
- 上面涉及到的是主要接口关系,而具体的IOC容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory,这个基本IOC容器的实现就是 实现了ConfigurableBeanFactory,从而成为一个简单IOC容器的实现,像其他IOC容器,比如XMLBeanFactory,都是在DefaultListableBeanFactory的基础上 做扩展,同样的ApplicationContext的实现也是如此。
- 这个接口系统是以BeanFactory和ApplicationContext为核心的,而BeanFactory又是IOC容器的最基本接口,在ApplicationContext的设计中,可以看到它继承了BeanFactory接口体系中的ListableBeanFactory、AutowireCapableBeanFactory、HierarchicalBeanFactory等BeanFactory的接口,具备了BeanFactory IOC容器的基本功能,另一方面,通过继承了MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,ApplicationContext为BeanFactory赋予了更高级的IOC容器特性,对于ApplicationContext而言,为了在Web环境中使用它,还设计了WebApplicationContext接口,而这个接口通过集成ThemeSource来扩展功能。
-
BeanFactory定义的基本规范
BeanFactory提供的是最基本的IOC容器的功能,关于这些功能定义,我们可以在接口BeanFactory中看到,
BeanFactory接口定义了IOC容器最基本的形式,并且提供了IOC容器应该遵守的最基本的服务契约,同时,这也是我们使用IOC容器所应该遵守的最低层 和 最基本的编程规范,这些接口定义勾画出了IOC基本轮廓,很显然,在Spring的代码实现中,BeanFactory只是一个接口类,并没有给出容器的具体实现,而我们看到的各种具体类,比如DefalutListableBeanFactory、XMLBeanFactory、ApplicationContext等都可以看成是容器或者是BeanFactory附加了某种功能的具体实现,也就是容器体系中华的具体容器产品,下面我们来看看BeanFactory是怎么样定义IOC容器的基本接口的,
public interface BeanFactory { /** * 用户使用容器时,可以使用转义符“&”来得到FactoryBean本身 */ String FACTORY_BEAN_PREFIX = "&"; /** * 通过制定名字来取得IOC容器中管理的Bean */ Object getBean(String name) throws BeansException; /** * 通过指定名字来取得IOC容器中管理的Bean,必须是指定类型的Bean */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * 通过指定获取Bean的类型 来获取Bean */ <T> T getBean(Class<T> requiredType) throws BeansException; /** * 通过获取Bean的名字,还有指定的构造参数来获取Bean */ Object getBean(String name, Object... args) throws BeansException; /** * 在上一个方法基础上急了 类型检查 */ <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; /** * 让用户能够判断容器是否含有指定名字的Bean */ boolean containsBean(String name); /** * 查询指定名字的Bean是否是Singleton类型的Bean,对于Singleton属性, 用户可以在BeanDefinition中指定 */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * 查询指定名字的Bean是否是Prototype类型的,与Singleton属性一样也可以在BeanDefinition中指定 */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** 查询指定名字的Bean的Class类型 是否是特定的Class类型,这个Class类型通过第二个参数来指定 */ boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; /** *来查询指定名字的Bean的Class类型 */ Class<?> getType(String name) throws NoSuchBeanDefinitionException; /** * 查询指定名字的Bean的所有别名,这些别名是用户在BeanDefinition中定义的额 */ String[] getAliases(String name); }
可以看到这里定义的只是一系列的接口方法,通过这一系列的BeanFactory接口,可以使用不同的Bean的检索方法,很方便的从IOC容器中得到需要的Bean,从而忽略具体的IOC容器的实现,从这个角度看的话,这些检索方法代表的是最为基本的容器入口。
BeanFactory容器的设计原理
BeanFactory接口提供了使用IOC容器的基本规范,在这个基础上,Spring还提供了符合这个IOC容器接口的一系列容器的实现供开发人员使用,我们以XMLBeanFactory的实现为例,来说明简单IOC容器的设计原理,下面的图为XMLBeanFactory设计的累积成关系
可以看到,作为一个简单IOC容器系列最低层实现的XMLBeanFactory,与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点,它只提供最基本的IOC容器的功能,理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系,我们可以认为直接的BeanFactory是现实IOC容器的基本形式,而各种ApplicationContext的实现是IOC容器的高级表现形式,关于ApplicationContext的分析,以及他与BeanFactory相比的增强特性都会在下面进行详细的分析,
从上面的继承关系可以清楚的看到类之间的联系,他们都是IOC容器系列的组成部分,在设计这个容器系列时,我们可以从继承体系的发展看到IOC容器的各项功能的实现过程,如果需要扩展自己的容器产品,那么建议呢先看看继承体系中,是不是已经提供了现成的或者相近的容器实现供我们参考,
仔细阅读XMLBeanFactory的源码,从一开始的注释里就会看到对XMLBeanFactory功能的简要说明,从代码的注释还可以看到,这是Rod Johnson大佬在2001年就写下的代码,可见这个类应该是Spring的元老类了,XMLBeanFactory继承自DefaultListableBeanFactory这个类,后者非常重要,我们经常要用到的一个IoC容器的实现,比如在设计应用上下文ApplicationContext时,就会用到它,我们会看到这个DefaultListableBeanFactory实际上包含了基本IOC容器具有的重要功能,也是在很多地方都会用到的容器系列中的一个基本产品。
在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IOC容器来使用的,XmlBeanFactory在继承了DefaultListableBeanFactory容器的功能同时,增加了新的功能,这些功能很容易从XMLBeanFactory的名字上猜到,它是一个与XML相关的BeanFactory,也就是说它是一个可以读取XML文件方式定义BeanDefinition的IOC容器。
这些实现XML读取的功能是怎么样实现的呢?对这些XMl文件定义信息处理并不是由XMLBeanFactory直接完成的,在XMLBeanFactory中,初始化了一个XMLBeanDefinitionReader对象,有了这个Reader对象,那些以Xml方式定义的BeanDefinition就有了处理的地方,我们可以看到,对这些Xml形式的信息处理实际上是由这个XMLBeanDefinitionReader来完成的。
构造XMLBeanFactory这个IOC容器时,需要制定BeanDefinition的信息来源,而这个信息来源需要封装成Spring中的Resource类给出,Resource是Spring用来封装I/O操作的类,比如,我们的BeanDefinition信息是以Xml文件形式存在的,那么可以使用像
ClassPathResource res = new ClassPathResource("beans.xml");
,这样具体的ClassPathResource来构造需要的Resource,然后将Resource作为构造参数传递给XMLBeanFactory构造参数,这样IOC容器就可以方便的定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程。
XMLBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器之上的,并在这个基本容器的基础上实现了植入XML读取的附加功能,对于这些的功能实现原理,看一看XMLBeanFactory的代码就很容器的理解,如果下面代码所示:在XMLBeanFactory的构造方法需要得到Resource对象,对XMLBeanDefinitionReader对象的初始化,以及使用这个对象来完成对loadBeanDefinitions的调用,就是这个调用启动从Resource中载入BeanDefinitions的过程,LoadBeanDefinitions同时也是IOC容器初始化的重要组成部分。
@Deprecated @SuppressWarnings({"serial", "all"}) public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); /** * Create a new XmlBeanFactory with the given resource, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } /** * Create a new XmlBeanFactory with the given input stream, * which must be parsable using DOM. * @param resource XML resource to load bean definitions from * @param parentBeanFactory parent bean factory * @throws BeansException in case of loading or parsing errors */ public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); this.reader.loadBeanDefinitions(resource); } }
我们看到XMLBeanFactory使用了DefaultListableBeanFactory作为基类,DefaultListableBeanFactory是很重要的一个IOC实现,在其他IOC容器中,比如ApplicationContext,其实现的基本原理和XmlBeanFactory一样,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IOC容器的功能的。
参考XMLBeanFactory的实现,我们以编程的方式使用DefaultListableBeanFactory,从中我们可以看到IOC容器使用的一些基本过程,对我们了解IOC容器的工作原理是非常有帮助的,因为这个编程式使用IOC容器过程,很清楚的揭示了在IOC容器实现中那些关键的类,可以看到他们是如何把IOC容器功能解耦的,又是如何结合在一起为IOC容器服务的,
/** * <p>Title:MyBeanFactory.java</p> * <p>Description:纯属学习</p> * <p>Copyright:ChenDong 2019</p> * @author 陈东 * @date 2019年1月8日 * @version 1.0 */ package myPage.learn1.core; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; /** * <p> * <b>Title:MyBeanFactory.java</b> * </p> * <p> * Copyright:ChenDong 2019 * </p> * <p> * Company:仅学习时使用 * </p> * <p> * 类功能描述:以编程的方式 来揭示 IOC容器 是如何将 功能解耦 以及 又如何将这些功能进行组合一起为IOC容器服务 * </p> * * @author 陈东 * @date 2019年1月8日 下午8:19:53 * @version 1.0 */ public class MyBeanFactory { public static void main(String[] args) { // 以XmlBeanFactory为例 ,它的构造器必须传入一个Resource 对象,也就创建IOC配置文件的抽象资源 这个抽象资源包含了 // BeanDefinition的定义信息 ClassPathResource res = new ClassPathResource("beans.xml"); // 创建一个BeanFactory 这里用DefaultListableBeanFactory为例,XmlBeanFactory 继承了这个类 DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); // 创建一个载入 IOC容器配置文件的 读取器,这里使用XMLBeanFactory中使用的XmlBeanDefinitionReader // 读取器 来载入XML文件形式的BeanDefinition,通过一个回到配置给BeanFactory XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); // 从定义好的资源位置读入配置信息,具体的解析过程有XmlBeanDefinitionReader来完成,完成整个载入和注册Bean定义后 // 需要的IOC容器就建立起来了,这个时候就可以直接使用IOC容器了 reader.loadBeanDefinitions(res); } }
这样我们就可以通过Factory独享来使用DefaultListableBeanFactory这个IOC容器了,在使用IOC容器时 需要以下几个步骤:
- 创建IOC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息
- 创建一个BeanFactory,这里使用DefaultListableBeanFactory
- 创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。
- 从定义好的资源位置读取配置信息,具体的解析过程由 XmlBeanDefinitionReader来完成,完成整个载入和注册Bean定义后,需要的IOC容器就建立起来了,这个时候就可以使用IOC容器了。
ApplicationContext的应用场景
上面我们了解了IOC容器建立的基本步骤,理解这些步骤之后,可以很方便的通过编程的方式,来手工控制这些配置和容器的建立过程了,但是,在Spring中,系统已经为用户提供了许多已经定义好的容器实现,而不需要开发人员事必躬亲,相比那些简单扩展BeanFactory的基本IOC容器,开发人员常用的ApplicationContext除了能提供前面介绍的容器的基本功能之外,还为用户提供了一下的附加服务,可以让客户更方便的使用,所以说,ApplicationContext是一个高级形态意义的IOC容器,看下的图,可以看到ApplicationContext在BeanFactory的基础上添加的附加功能,这些功能为ApplicationContext提供了一下BeanFactory不具备的新特性:
- 支持不同的信息源,除了上面的类之外, ApplicationContext还扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务
- 访问资源,这一特性体现在对ResourceLoader 和 Resource 的支持上,这样我们可以从不同地方得到Bean的定义资源,这种抽象使 用户程序可以灵活的定义Bean的定义信息,尤其是从不同的I\O途径得到Bean的定义信息 也就BeanDefinition信息,这在接口关系上看不出来,不过一般来说,具体的ApplicationContext都是继承了DefaultResourceLoader的子类,因为DefaultResourceLoader是AbstractApplicationContext的基类。
- 支持应用事件,继承了接口ApplicationEventPublisher,从而在上下文中引入了事件机制,这些事件机制和Bean的声明周期的结合为Bean的管理提供了便利。
在ApplicationContext中提供的附加服务,这些服务使得基本IOC容器的功能更加丰富,因为具备了这些丰富的附加功能,使得ApplicationContext与简单的BeanFactory相比,对它的使用是一种面向框架的使用风格,所以一般建议在开发应用时使用ApplicationContext作为IOC容器的基本形式。
ApplicationContext容器的设计原理
在ApplicationContext容器中,我们以常用的额FileSystemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理。
在FileSystemXMLApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXMLApplicationContext的基类 AbstractXMLApplicationContext中实现了,在FileSystemXMLApplicationContext中,作为一个具体的应用上下文,只要实现和它自身相关的两个功能就可以了。
一个功能是,如果应用直接使用FileSystemXMLApplicationContext,对于实例化这个应用上下文的支持 其实就是构造器,同时启动IOC容器的refresh()过程,这在FileSystemXMLApplicationContext的代码实现中可以看到,
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
这个refresh过程会牵涉IOC容器启动的一系列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类中将他们封装好,所以我们在FileSystemXMLApplicationContext的设计中看到的只是一个简单的调用,关于这个refresh在IOC容器中的具体实现,是后面要分析的主要内容,这个到时候看后面的分析就行了。
另一个功能是 与FileSystemXMLApplicationContext设计具体相关的功能,这部分与怎么样从文件系统中加载XML的Bean定义资源有关。
通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的应用上下文实现对应着不同的读取BeanDefinition形式,在FileSystemXMLApplicationContext的实现如下:
@Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
可以看到,这个方法是一个 override方法 也就是 为了实现以不同的方式 获取资源定位 在某个接口定义的 getResourceByPath方法,调用这个方法可以得到FileSystemResource的资源定位。
IOC容器的初始化过程
简单来说,IOC容器的初始化时由前面介绍的refresh方法来启动的,这个方法标志着IOC容器正式启动,具体来说,这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程, 如果我们了解如何编程式的使用IOC容器,就可以清楚的看到Resource定义和载入过程的接口调用,在下面的内容中,我们将会详细分析这三个过程的实现。
在分析之前,要提醒大家的是,Spring把这三个过程分开,并使用不同的模块来完成,如使用相应的ResourceLoader、BeanDefinitionReader等模块,通过这样的设计方式,可以让用户更加灵活的对这三个过程进行剪裁和扩展,定义出最适合自己的IOC容器的初始化过程。
- 第一个过程:是Resource定位过程,这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口,对于这些BeanDefinition的存在形式,相信大家都不会感到默生,比如,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象,在类路径中的Bean定义信息可以使用前面提到的ClassPathResource来进行抽象,等等,这个定位过程类似于容器中寻找数据的过程,就像是用水桶装水,先要将水找到一样。
- 第二过程是:BeanDefinition的载入,这个载入过程是把用户定义好的Bean表示成IOC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition,下面介绍这个数据结构的详细定义,具体来说,这个BeanDefinition实际就是POJO对象在IOC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IOC容器能够方便的对POJO对象也就是Bean进行管理,
- 第三个过程就是向IOC容器注册这个鞋BeanDefinition的过程,这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程是把载入过程中解析得到的BeanDefinition向IOC容器进行注册,通过分析,我们可以看到,在IOC容器内部将BeanDefinition注入到一个HashMap中去,IOC容器就是通过这个HashMap来持有这些BeanDefinition数据的。
这里谈的是IOC容器的初始化过程,在这个过程中,一般不包含Bean依赖注入的实现,在Spring IOC的设计中,Bean定义的载入和依赖注入是两个独立的过程,依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候,但是有一个例外,在使用IOC容器时有一个预实例化的配置(具体来说,可以通过为Bean 定义信息中的lazyinit属性),用户可以对容器初始化过程做一个微小的控制,从而改变这个被设置了lazyinit属性的Bean的依赖注入过程,举例来说,如果我们对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IOC容器初始化的时候就预先完成了,而不需要等到整个初始化完成以后、第一次使用getBean时才会触发。
那么下面开始详细分析上面的三个步骤
BeanDefinition的Resource定位
以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition,这时使用是ClassPathResource,这意味着Spring会在类路径中去寻找以文件形式存在BeanDefinition。
ClassPathResource resource = new ClassPathResource("beans.xml");
这里的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理,在这里,我们也可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处,因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器实现,而DefaultListableBeanFactory只是一个纯粹的IOC容器,需要为它配置配置特定的读取器才能完成这些功能,当然了 利 和 弊是共存的,使用DefaultListableBeanFactory这样更底层的IOC容器,能提高定制IOC容器的的灵活性。
回到我们经常使用的ApplicationContext上来,例如FileSystemXMLApplicationContext、ClassPathXMLApplicationContext以及XMLWebApplicationContext等,简单的从这些类的名字上分析,可以清楚的看到他们可以提供哪些不同的Resource读入功能,比如FileSystemXMLApplicationContext可以从文件系统中载入Resource,ClassPathXMLApplicationContext可以从 class path载入Resource,XMLWebApplicationContext可以在Web容器中载入Resource等,
下面以FileSystemXMLApplicationContext为例,通过分析这个ApplicationContext的实现来看看它是怎么完成这个Resource定位的,
从源代码实现的角度,我们可以近距离关心以FileSystemXMLApplicationContext为核心的继承体系,这个FileSystemXMLApplicationContext已经通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader,下面让我们看看FileSystemXMLApplicationContext的具体实现:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { /** * */ public FileSystemXmlApplicationContext() { } /** * */ public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } /** * 这个构造函数的configLocation包含的是BeanDefinition所在的文件路径 */ public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } /** * 这个构造函数 的允许configLocation包含多个BeanDefinition的文件路径 */ public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } /*这个构造函数 允许configLocation包含多个BeanDefinition的文件路径,还允许指定自己的双亲容器 */ public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } /** *这个构造函数 在允许 configLocation包含多个BeanDefinition的文件路径基础上,还加了是否初始化容器的标志 */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } /** *在对象的初始化过程中,调用refresh函数载入BeanDefinition,这个refresh启动了BeanDefinition的载入过程 */ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } /** *这事应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个文件系统中定位的BeanDefinition,这个getResourceByPath是在BeanDefinitionReader的LoaderBeanDefinition中被调用的,loaderBeanDefinition采用了模板模式,
*具体的定位实现实际上是由各个子类来完成的 */ @Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
在FileSystemXMLApplicationContext中,我们可以看到在构造函数中,实现了对configuration进行处理的功能,让所有配置在文件系统中的,以XML文件形式存在的BeanDefinition都能够得到有效的处理。
实现了getResourceByPath方法,这个方法是一个模板方法,是为读取Resource服务的,对于IOC容器功能的实现,这里没有涉及,因为它继承了AbstractXMLApplicationContext,关于IOC容器功能的相关实现,都是在AbstractXMLApplicationContext中完成的,但是在构造函数中通过refresh来启动IOC容器的初始化,这个refresh方法非常重要,也是我们以后分析容器初始化过程实现的一个重要入口。
注意: FileSystemXMLApplicationContext是一个支持Xml定义BeanDefinition的ApplicationContext,并且可以指定以文件形式的BeanDefinition的读入,这些文件可以使用文件路径和URL定义来表示,在测试环境和独立应用环境中,这个ApplicationContext是非常有用的。
根据上面的调用关系分析 ,我们可以清楚的看到整个BeanDefinition资源定位的过程,对这个BeanDefinition资源定位的过程,最初是由refresh来触发的,这个refresh的调用是在FileSystemXMLApplicationContext的构造函数中启动的,
大致的调用过程是
可能大家看了上面的调用过程会比较好奇,这个FileSystemXMLApplicationContext在什么地方定义了BeanDefinitionReader,从而完成BeanDefinition的读入呢?在前面分析过,在IOC容器的额初始化过程中,BeanDefinition资源的定位、读入 和 注册 过程都是分开进行的,这也是一个解耦的体现,关于这个读入其的配置,可以到FileSystemXMLApplicationContext的基类AbstractRefreshableApplicationContext中看看它是怎么样实现的。
我们重点看看AbstractRefreshableApplicationContext的 refreshBeanFactory()方法的实现看下,这个refreshBeanFactory()方法被FileSystemXMLApplicationContext的构造函数refresh调用,在这个方法中通过 createBeanFactory()构建了一个IOC容器供给ApplicationContext使用,这个IOC容器就是之前我们提到过的DefaultListableBeanFactory,同时它启动了loadBeanDefinitions(beanFactory); 来载入BeanDefinition,这个过程和我们前面以编程的方式来使用IOC容器的过程非常相似。
在FileSystemXMLApplicationContext的源代码中我们可以看到,在初始化FileSystemXMLApplicationContext的过程中,通过refresh来启动整个调用,使用的IOC容器是DefaultListableBeanFactory,具体的资源载入在XmlBeanDefinitionReader读入BeanDefinition时完
成,在XmlBeanDefinitionReader的基类AbstractXmlBeanDefinitionReader中可以看到这个载入过程的具体实现,对载入过程的启动,可以在AbstractRefreshableApplicationContext的loadBeanDefinitions方法中看到。
我们先从 refresh启动开始:
具体实现的ApplicationContext也就是当前的FileSystemXMLApplicationContext调用的refresh方法 是其继承的基类 AbstractApplicationContext中的实现:
先跟踪第一步 AbStractApplicationContext中的refresh方法
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // 先着重看这个方法 这是初始化容器且 进行BeanDefinition载入的入口 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
跟踪第二步: obtainFreshBeanFactory()这个方法还是在AbstractApplicationContext中实现:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //初始化容器 refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
但是 refreshBeanFactory(); 这个具体初始化IOC容器的实现呢 AbstractApplicationContext 没有做具体实现,而是交给其子类完成,实现了解耦。让初始化IOC容器变得更加灵活,那么咱们在回顾下FileSystemXMLApplicationContext所继承的父类的实现,
跟踪第三步:
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
refreshBeanFactory定义成了abstract 那么也就是继承它的 子类必须实现的,那么看下FileSystemXMLApplicationContext所处在的继承体系,那么很显然我们应该知道去那个类中去找这个方法的实现了。没错就是AbstractRefreshableApplicationContext中
@Override protected final void refreshBeanFactory() throws BeansException { //这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } //这里是创建并设置持有的DefaultListableBeanFactory的地方同时调用loadBeanDefinitions载入BeanDefinition信息 try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
我们先看下创建DefaultListableBeanFactory的地方:createBeanFactory,这个方法 是在AbstractRefreshableApplicationContext中实现,所以AbstractApplicationContext 让我们可以充分自由的实例化自己想初始化的原始IOC容器,
protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
getInternalParentBeanFactory 获取当前容器已有的父亲容器,来作为新容器的父容器,这个方法是在AbstractApplicationContext中实现的。
那么我们在对代码进行继续跟踪 loadBeanDefinitions(beanFactory),这个方法就是BeanDefinitionReader载入 BeanDefinition的地方,因为允许有多种载入方式,虽然用的最多的是XML定义的形式,但是为了面向框架的扩展性,这里AbstractRefreshableApplicationContext将loadBeanDefinitions做成了一个抽象函数把具体的实现委托给其子类实现。
跟踪第四步
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
那么回过头来,还是看上面FileSystemXMLApplicationContext的类继承体系,我们可以看到 FileSystemXMLApplicationContext的基类AbstractXmlApplicationContext 是一个AbstractRefreshableApplicationContext的一个子类实现,那我们知道了该怎么去找这个loadBeanDefinitions的具体实现了把。
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 这就是初始一个BeanDefinitionReader的地方 当然了这利用的XMLBeanDefinitionReader的实现 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); // 这里开始就是 加载 获取BeanDefinition资源定位 并且 载入模块的开始了 loadBeanDefinitions(beanDefinitionReader); }
继续跟踪这个 loadBeanDefinitions(beanDefinitionReader);这个方法 AbstractXMLApplicationContext中有实现 我们看下:
跟踪第五步;
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //以Resource的方式获得配置文件的资源位置
Resource[] configResources = getConfigResources();
if (configResources != null) { reader.loadBeanDefinitions(configResources); }
//以String的方式获得配置文件的资源位置 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
跟踪第六步:这开始 就是BeanDefinitionReader模块的实现了
这里的具体实现应该去 XmlBeanDefinitionReader中的loadBeadDefinitions,但是阅读了源码发现 这个方法是由XmlBeanDefinitionReader的基类 AbstractBeanDefinitionReader 来实现的我们看下:
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); //一个普通到不能再普通的for循环遍历 int counter = 0; for (String location : locations) { //这是我们要继继续跟踪的方法 counter += loadBeanDefinitions(location); } return counter; }
也就是我们的第七步:
loadBeanDefinitions(String )这个方法 还在AbstractBeanDefinitionReader的类中实现:
@Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
得 继续往下看把 这没啥好说的了
继续跟踪 上面代码中的 loadBeanDefinitions(location, null);
第八步:
loadBeanDefinitions(String location, Set<Resource> actualResources);这个方法 还在AbstractBeanDefinitionReader类中:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //这里取到的ResourceLoader 是DefaultResourceLoader ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } // 这里对Resource的路径模式进行解析,比如我么设定的各种Ant格式的路径定义,得到 //需要的Resource集合,这些Resource集合指定我们已经定义好的BeanDefinition信息,可以是多个文件 if (resourceLoader instanceof ResourcePatternResolver) { // Resource pattern matching available. try { //使用ResourceReader的 getResource(String)来获取资源定位 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { // 调用DefaultResourceLoader的getResource(String)方法来获取资源定位 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) {
//看这里哈 对于成功找到的Resource定位 都会添加到这个 传入的 actualResources参数中 actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
继续跟踪第九步:DefaultResourceLoader 中的 getResource(String)实现
@Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); //重点来了 getResourceByPath是一个抽象方法 具体实现全部由具体子类实现 if (location.startsWith("/")) { return getResourceByPath(location); }// 这里处理带有classPath标识的Resource else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // 这里处理带有URL标识的Resource定位 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { //如果既不是classPath 也不是URL标识的Resource定位(那其实就是自己实现的了).则把getResource的重任交给getResourceByPath来完成,这个方法是一个protected方法,默认的实现是得到一个ClassPathContextResource,这个方法常常会用子类来实现也就是
//我们举例的FileSystemXMLApplicationContext return getResourceByPath(location); } } }
第十步:具体ApplicationContext实现中 且实现了DefaultResourceLoader的 容器 我们举例子是FileSystemXMLApplicationContext
在回顾一下FileSystemXMLApplicationContext的继承体系:
也就是说连AbstractApplicationContext 都是 DefaultResourceLoader的子类 那么 基本上所有ApplicationContext的具体实现 都继承了DefaultResourceLoader
@Override protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
上面我们可以看到FileSystemXMLApplicationContext的getResourceBypath会返回一个FileSystemResource对象,通过这个对象Spring可以进行相关的I/O操作,完成BeanDefinition的定位,分析到这里已经一目了然了吧,它实现的就是对path的解析然后生成一个FileSystemResource对象返回,那么以上呢 就从IOC容器初始化开始 一直到 具体的资源定位的流程就分析完了
如果是其他的ApplicationContext,那么回对应生成其他种类的Resource,比如说ClassPathResource、ServletContextResource等,关于Spring中的Resource的种类,可以从Resource的继承类中了解
一堆哈,这些接口对不同的Resource实现代表着不同的意义,是Resource的实现要考虑的,
通过对前面的实现原理的分析,我们以FileSystemXMLApplicationContext的实现原理为例子,了解了Resource定位问题的解决方案,即以FileSystemResource方式存在的Resource的定位实现,在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象进行BeanDefinition的载入了,在定位过程完成以后,为BeanDefinition的载入创造了I/O操作的条件,但是具体的数据还没有开始读取,这些数据的读入将在下面的介绍BeanDefinition的载入和解析中完成,仍然以水桶为例子,这里就像用水桶去打水,先要找到水源,这里完成对Resource的定位,就类似于水源已经找到了,下面就是打水的过程了,类似于把找到的水装到水桶里的过程,找水不简单,但是与打水相比,我们发现打水更复杂一些。
明天继续
BeanDefinition的载入 和 解析
在完成对代表BeanDefinition的Resource定位分析后,下面来了解一下整个BeanDefinition信息的载入过程,对IOC容器来说,这个载入过程,相当于把定义的BeanDefinition在IOC容器中转化成一个Spring内部表示的数据结构的过程,IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的,这些BeanDefinition在IOC容器中通过一个HAHSMAP来保持和维护,当然这只是一种比较简单的维护方法,如果需要提供IOC容器的性能 和 容量,完全可以自己做一些扩展,
下面就从DefaultListableBeanFactory的设计入手,来看看IOC容器是怎么样完成BeanDefinition载入的,这个DefaultListableBeanFactory在前面已经碰到过多次,在开始分析之前,下回到IOC容器的初始化绕口,也就是看一下refresh方法,这个方法的最初是在FileSystemXmmlApplicationContext的构造函数中被调用的,它的调用标志着容器初始化过程的开始,这些初始化对象就是BeanDefinition数据,
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
对容器的启动来说,refresh是一个很重要的方法,下面介绍一下它的实现,前面已经了解过这个这个方法 是在AbstractApplicationContext中实现的,他详细的描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册,等等,这里看起来更像是对ApplicationContext的初始化的模板 或者 指定大纲,这个执行过程为Bean声明周期管理提供了条件,熟悉IOC容器的赌注,从这一系列调用的名字就能大致了解应用上下文初始化的主要内容,
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { prepareRefresh(); //这里是在子类中启动refreshBeanFactory的地方 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); prepareBeanFactory(beanFactory); try { // 设置BeanFactory的后置处理器 postProcessBeanFactory(beanFactory); //调用这些BeanFactory的后置处理器,这些处理器是在Bean定义中想容器注册的 invokeBeanFactoryPostProcessors(beanFactory); // 注册BeanFactory的后置处理器,在Bean创建过程中调用 registerBeanPostProcessors(beanFactory); //初始化上下文中的所有消息源 initMessageSource(); //初始化上下文的事件机制 initApplicationEventMulticaster(); // 初始化其他特殊的Bean onRefresh(); // 检查是监听器实现的Bean 把他们注册到容器中 registerListeners(); // 实例化所有的(no-lazy-init)单例Bean finishBeanFactoryInitialization(beanFactory); //发布容器事件 结束refresh过程 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } //销毁之前创建的单例bean destroyBeans(); //重置‘active’标志 cancelRefresh(ex); throw ex; } } }
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; }
进入到AbstractRefreshableApplicationContext中的refreshBeanFactory方法中,,在这个方法中,在创建IOC容器之前,如果已经有容器存在,那么需要把已有的容器销毁和关闭的,保证在refresh方法以后使用的都是新建立起来的IOC容器,这么看,这个refresh非常像重启容器,就像重启计算机那样,在建立好当前的IOC容器之后,开始了对容器的初始化过程,比如BeanDefinition的载入、解析。
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //载入和解析BeanDefinition的入口 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
这里调用的loadBaenDefinitions 是一个抽象方法,我们看看前面提到的loadBeanDefinition在AbstractRefreshableApplicationContext的子类AbstractXMLApplicationContext中的实现,在这个loadBeanDefinitions中,初始化了一个XmlBeanDefinitionReader ,然后把这个读取器在IOC容器中设置还(过程和编程式使用XMLBeanFactory是类似),最后是启动读取器来完成BeanDefinition在IOC容器中的载入。
@Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中,这里使用的IOC容器 也是DefaultListableBeanFactory XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setEnvironment(this.getEnvironment()); //这里设置刚初始化的XmlBeanDefinitionReader,为其配置ResourceLoader,因为凡是以AbstractApplicationContext为基类的容器 都默认继承了DefaultResourceLoader,所以这里传一个this beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, //启动BeanDefinition的载入 initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的读取过程是委托给BeanDefinitionReader来完成的,因为这里的BeanDefinition信息是通过XML文件来定义的,所以使用XmlBeanDefinitionReader来载入BeanDefinition到容器中。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //以Resource的形式获取配置文件的资源定位 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } //以String的形式获取配置文件的资源定位 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
看上面的方法 其实就是获取到准确定位之后 使用XmlBeanDefinitionReader的loadBeanDefinitions方法 来载入BeanDefinition信息了,这和之前的编程式使用XMLBeanFactory就一样了。
小结: 通过以上实现原理的分析,我们可以看到,在初始化FileSystemXMLApplicationContext的过程中 调用 refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义一个XmlBeanDefinitionReader来作为一个BeanDefinitionReader来作为读取器,同时我们也知道了实际上使用的容器 也是DefaultListableBeanFactory,具体的Resource的载入在XmlBeanDefinitionReader读取BeanDefinition时 实现,因为Spring可以对应不同形式的BeanDefinition定义,由于这里使用的是Xml方式的定义,所以需要使用XmlBeanDefinitionReader,如果使用了其他种类形式的BeanDefinition定义,就需要使用其他对应的BeanDefinitionReader来完成数据的载入工作,在XmlBeanDefinitionReader 的实现中可以看到,是在reader.loadBeanDefinitions中开始记性BeanDefinition的载入的,而这时XmlBeanDefinitionReader的父亲AbstractBeanDefinitionReader已经为了BeanDefinition的载入做好了准备,
也就是 reader.loadBeanDefinitions(Resource[] re);reader.loadBeanDefinitions(String[] s);的具体实现 是在所有BeanDefinitionReader的基类中完成的AbstractBeanDefinitionRedaer。
@Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); } return counter; } @Override public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (Resource resource : resources) { counter += loadBeanDefinitions(resource); } return counter; }
上面这类方法 的作用其实就是做一个遍历 将获得的所有资源定位全都加载一遍,具体的调用时loadBeanDefinitions(String s)或者 loadBeanDefinitions(Resource r),这个方法 是一个抽象方法,这就是要交给其不同的子类去实现了,
那么因为我们使用的XmlBeanDefinitionReader 所以这个方法 我们去XmlBeanDefinitionReader去找下,在读取器中,需要得到代表XML文件的Resource对象,因为这个Resource对象封装了对XML文件的I/O操作,随意读取器可以在打开I/O留后得到Xml的文件对象,
有了这个文件之后,就可以照Spring的Bean定义规则来对这个xml的文档树进行解析了,这个解析是交给BeanDefinitionParserDelegate来完成的,看起来实现脉络很清楚,具体看代码把:
@Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { //根据指定资源对象来加载BeanDefinition的入口 return loadBeanDefinitions(new EncodedResource(resource)); }
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try {//这里得到定义BeanDefinition的Xml文件 的输入流 InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); }//这里是具体读取Xml文件的方法 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } catch (IOException ex) { throw new BeanDefinitionStoreException( "IOException parsing XML document from " + encodedResource.getResource(), ex); } finally { currentResources.remove(encodedResource); if (currentResources.isEmpty()) { this.resourcesCurrentlyBeingLoaded.remove(); } } }
下面就是看下 具体读取Xml文件的 方法,也是从指定xml文件中实际载入BeanDefinition的地方。当然了 这肯定是在XmlBeanDefinitionReader中的方法了,这属于具体BeanDefinitionReader自己要实现的功能;
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //这里取得的是XML文件的Document对象,具体的解析过程是由DocumentLoader完成的, //这里使用的DocumentLoader是DefaultDocumentLoader,在定义documentLoader对象时候创建的 Document doc = doLoadDocument(inputSource, resource); //这里启动的是对BeanDefinition解析的详细过程,这个解析会用到Spring的Bean配置规则,是我门下面详细讲解的内容,
return registerBeanDefinitions(doc, resource); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (SAXParseException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex); } catch (SAXException ex) { throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex); } catch (ParserConfigurationException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex); } catch (IOException ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex); } catch (Throwable ex) { throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex); } }
我们这次主要关心的是Spring的BeanDefinition是怎么样按照Spring的Bean语义要求进行解析 并转化为容器内部数据结构的,这个过程是在registerBeanDefinitions(doc, resource)中完成的,具体的过程是BeanDefinitionDocumentReader来完成的,这个registerBeanDefinitions还对载入的Bean数量进行了统计,这个方法也是在 XmlBeanDefinitionReader 中自己实现的,
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //这里得到的BeanDefinitionDocumentReader对象来对XML的BeanDefinition信息进行解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); documentReader.setEnvironment(getEnvironment()); int countBefore = getRegistry().getBeanDefinitionCount(); //具体的解析过程在这个方法中实现 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
小结:BeanDefinition的载入分成两部分,首先通过调用XML的解析器(XmlBeanDefinitionReader)得到document对象,但这些document对象并没有 按照Spring的Bean规则去进行解析,在完成通用XML解析之后才是按照Spring得 Bean规则进行解析的地方,这个按照Spring的Bean规则进行解析的过程是在documentReade中实现的,这里使用的documentReader是默认设置好的DefaultBeanDefinitionDocumentReader 创建的过程也是在XmlBeanDefinitionReader 中完成的,根据指定的默认private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class; class对象生成一个DefaultBeanDefinitionDocumentReader对象,具体的解析过程在DefaultBeanDefinitionDocumentReader来实现,我们继续跟踪:
*/ public interface BeanDefinitionDocumentReader { /** * Set the Environment to use when reading bean definitions. * <p>Used for evaluating profile information to determine whether a * {@code <beans/>} document/element should be included or ignored. * @deprecated in favor of {@link XmlReaderContext#getEnvironment()} */ @Deprecated void setEnvironment(Environment environment); /** * Read bean definitions from the given DOM document and * register them with the registry in the given reader context. * @param doc the DOM document * @param readerContext the current context of the reader * (includes the target registry and the resource being parsed) * @throws BeanDefinitionStoreException in case of parsing errors */ void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; }
BeanDefinitionDocumentReader中有两个抽象方法 registerBeanDefinitions 这个方法交给其子类去实现,所以 是DefaultBeanDefinitionDocumentReader中自己实现的,
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); Element root = doc.getDocumentElement(); doRegisterBeanDefinitions(root); }
这里只是将 XML中的元素取了出来 具体的活还是 doRegisterBeanDefinitions(root);来实现的 do开头的方法才是真正干活的方法,
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } //真正的解析BeanDefinition之前做的一些事情的接口触发 preProcessXml(root); parseBeanDefinitions(root, this.delegate);
// 解析BeanDefinition之后可以做的一些事情的触发 postProcessXml(root); this.delegate = parent; }
而真正去解析Document文档树的是 BeanDefinitionParserDelegate完成的,这个解析过程是与Spring对BeanDefinition的配置规则紧密相关的,
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
遍历