第一部分 Spring核心实现篇
本篇将对Spring的核心IoC容器和AOP的实现原理进行阐述。IoC容器和AOP是Spring的核心,是Spring系统中其他组件模块和应用开发的基础。从两个核心模块的设计和实现上可以了解到Spring倡导的对企业应用开发所应秉持的思路,比如使用POJO开发企业应用、提供一致的编程模型、强调对接口编程等。对于这些Spring背后的开发思想和设计理念,大家都不会陌生,在Rod Johnson的经典著作里都有全面和深刻的讲解。作为参考,我们可以看到Spring官方网站对Spring项目的描述。如下图所示,Spring的目标和愿景写得很清楚。
首先,Spring的目标在于让Java EE的开发变得更容易,这也就意味着Spring框架的使用也应该是容易的。对于开发人员而言,易用性是第一位的。为什么要让Java EE开发变得更容易,难道以前的Java EE开发很艰难?Spring究竟是如何让Java EE的开发变得更容易的呢?了解Java EE开发历史的读者都知道,正如Rod Johnson在他的著作Expert One-on-One Java EE Design and Development中提到的那样,EJB模型为Java EE开发引入了过度的复杂性,这个开发模型对Java EE的开发并不友好。有没有更好的开发模型呢?有,就是POJO!它让Java洗净铅华,恢复其自然的风采。使用POJO不仅能开发复杂的Java企业应用,而且还可以让Java EE开发在开发成本、开发周期、可维护性和性能上获得更大优势。对一般的企业应用需求而言,重要的是如何方便地使用应用需要的服务,而不是各种各样的开发模型和模式。虽然这些模式为我们描绘了设计高可靠性分布式应用的美妙场景,但这些场景是不是大多数企业应用开发者所要面对的呢?
世上都说Java好,唯有Spring忘不了。喜欢Java,是因为它简洁,不但包含了面向对象的语言特性,同时还可以跨平台,可谓是简洁而又强大。但是,进入到企业应用后,作为门外汉的自己一看到复杂的EJB模型就心生畏惧。这时候,我接触到了Spring,她给人的第一印象就是简洁却又具有丰富的内涵,就像第一次遇到Java一样,被她的这种特质深深地吸引了。她降低了企业应用开发的门槛,还原了POJO的本色,让我们直接依赖于Java语言,直接依赖于面向对象编程,使用无所不在的单元测试来保证代码质量,这样我们就有信心能够开发出高质量的企业应用。
也就是说,我们如何才能让开发既变得容易,又能享受到Java EE中提供的各种服务呢?Spring的目标就是通过自己的努力,让用户体会到这种简单之中的强大。同时,作为应用框架,Spring不想把自己作为另外一种复杂开发模型的替代,也就是说不是用另一种复杂性去替代现有的复杂性,那是换汤不换药,并不能解决问题。这就意味着需要有新的突破。要解决这个问题,需要降低应用的负载和框架的侵入性,Spring是怎样做到这一点的呢?
Spring为我们提供的解决方案就是IoC容器和AOP支持。作为依赖反转模式的具体实现,IoC容器很好地降低了框架的侵入性,同时也可以认为依赖反转模式是Spring体现出来的核心模式。这些核心模式是软件架构设计中非常重要的因素,比如说,我们常常看到的MVC模式就是这样的核心模式。不要小看这些体系结构模式的作用和影响,它们就是框架背后所谓的“道”。有了IoC容器和AOP的支持,用户的开发方式发生了很大的变化,具体说来,就是可以使用POJO来完成开发,对用户来说是简化了,但由于有平台的支持,依然能够实现复杂的企业应用开发。对于依赖反转,在Spring中,Java EE的服务都被抽象到IoC容器和AOP中并进行了有效地封装,而且因为依赖注入的特性,这些复杂的依赖关系的管理被反转了,它们的管理交给了容器。
Spring中各个模块的依赖关系可以用简单的IoC配置文件进行描述,信息集中并且明了。在使用其他组件服务时,只需要在配置文件中配置这些服务与应用组件的依赖关系。对应用开发而言,只需要了解服务的接口和依赖关系的配置。这样一来又很好地体现了Spring的第二个信条:让应用开发对接口编程,而不是对类编程。这样POJO使用Java EE服务时,可以将对这些服务实现的依赖降到最低,尽可能地降低框架的侵入性。
在处理与现有优秀解决方案的关系时,根据Spring的既定策略,它不会与这些第三方的解决方案发生竞争,而是致力于为应用提供使用优秀方案的集成平台。真正地把Spring定位在应用平台的地位,使得自己成为一个兼容并包的开放体系的同时,最大程度地降低开发者对Spring API的依赖,这是怎样实现的呢?答案还是IoC容器和AOP技术,也就是说,Spring API在开发过程中并不是必须使用的。
第2章 SpringFramework的核心:IoC容器的实现
朝辞白帝彩云间,千里江陵一日还。
两岸猿声啼不住,轻舟已过万重山。
—【唐】李白《早发白帝城》
2.1 Spring IoC容器概述
2.1.1 IoC容器和依赖反转模式
子曰:温故而知新。在这里,我们先简要地回顾一下有关依赖反转的相关概念。我们选取维基百科中关于依赖反转的叙述,把这些文字作为我们理解依赖反转概念的参考。这里不会对这些原理进行学理上的考究,只是希望提供一些有用的信息,以便给读者一些启示。这个模式非常重要,它是IoC容器得到广泛应用的基础。
维基百科对“依赖反转”相关概念的叙述
早在2004年,Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他得出的结论是:依赖对象的获得被反转了。基于这个结论,他为控制反转创造了一个更好的名字:依赖注入。许多非凡的应用(比HelloWorld.java更加优美、更加复杂)都是由两个或多个类通过彼此的合作来实现业务逻辑,这使得每个对象都需要与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么如你所见,这将导致代码高度耦合并且难以测试。
以上的这段话概括了依赖反转的要义,如果合作对象的引用或依赖关系的管理要由具体对象来完成,会导致代码的高度耦合和可测试性降低,这对复杂的面向对象系统的设计是非常不利的。在面向对象系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或IoC容器来完成,这种从具体对象手中交出控制的做法是非常有价值的,它可以在解耦代码的同时提高代码的可测试性。极限编程中对单元测试和重构等实践的强调体现了软件开发过程中对质量的承诺,这是软件项目成功的一个重要因素。
依赖控制反转的实现方式有很多种。在Spring中,IoC容器是实现这个模式的载体,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象被逐层注入。就此而言,这种方案有一种完整而简洁的美感,它把对象的依赖关系有序地建立起来,简化了对象依赖关系的管理,在很大程度上简化了面向对象系统的复杂性。
关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是解决面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效的解决方案。它促进了IoC设计模式的发展,是IoC容器要解决的核心问题。同时,也是产品化的IoC容器出现的推动力。
注意 IoC亦称为“依赖倒置原理”(Dependency Inversion Principle),几乎所有框架都使用了倒置注入(Martin Fowler)技巧,是IoC原理的一项应用。SmallTalk、C++、Java或.NET等面向对象语言的程序员已使用了这些原理。控制反转是Spring框架的核心。
IoC原理的应用在不同的语言中有许多实现,比如SmallTalk、C++、Java等。在同一语言的实现中也会有多个具体的产品,Spring是Java语言实现中最著名的一个。同时,IoC也是Spring框架要解决的核心问题。
注意 应用控制反转后,当对象被创建时,由一个调控系统内的所有对象的外界实体将其所依赖的对象的引用传递给它。也就是说,依赖被注入到对象中。所以,控制反转是关于一个对象如何获取它所依赖的对象的引用的,在这里,反转指的是责任的反转。
我们可以认为上面提到的调控系统是应用平台,或者更具体地说是IoC容器。通过使用IoC容器,对象依赖关系的管理被反转了,转到IoC容器中来了,对象之间的相互依赖关系由IoC容器进行管理,并由容器完成对象的注入。这样就在很大程度上简化了应用的开发,把应用从复杂的对象依赖关系管理中解放出来。简单地说,因为很多对象的依赖关系的建立和维护并不需要和系统运行状态有很强的关联性,所以可以把我们在面向对象编程中常常需要执行的诸如新建对象、给对象引用赋值等操作交由容器统一完成。这样一来,这些散落在不同代码中的功能相同的部分就集中成为容器的一部分,也就是成为面向对象系统的基础设施的一部分。
如果对面向对象系统中的对象进行简单地分类,会发现除了一部分是数据对象外,其他有很大一部分对象都是用来处理数据的。这些对象并不会经常发生变化,是系统中基础的部分。在很多情况下,这些对象在系统中以单件的形式存在就可以满足应用的需求,而且它们也不常涉及数据和状态共享的问题。如果涉及数据共享方面的问题,需要在这些单件的基础上做进一步的处理。
同时,这些对象之间的相互依赖关系也是比较稳定的,一般不会随着应用的运行状态的改变而改变。这些特性使得这些对象非常适合由IoC容器来管理,虽然它们存在于应用系统中,但是应用系统并不承担管理这些对象的责任,而是通过依赖反转把责任交给了容器(或者说平台)。了解了这些背景,Spring IoC容器的原理也就不难理解了。在原理的具体实现上,Spring有着自己的独特思路、实现技巧和丰富的产品特性。关于这些原理的实现,下面会进行详细的分析。
第1章中,我们已经对建立本地源代码环境做了简要的介绍,该源代码环境是我们分析Spring原理前要做的重要准备工作。同时,我们还需要针对IoC容器做一些额外的事情:根据Spring 3.0的源代码组织特点,每个模块作为独立的Eclipse项目存在,所以现在需要在Eclipse中建立与IoC容器和上下文相关的代码项目。这样就可以方便地使用Eclipse的代码分析工具来对相关模块的实现进行分析。这个额外的准备过程在分析其他模块时也是需要的,所以这里会做一个说明。
准备过程如图2-1所示,打开Eclipse,依次选择File→Import→General→Existing Projects into Workspace,然后再选择org.springframework.beans和org.springframework.context两个目录,并将其导入到Eclipse本地环境中。这时即可看到在Package Explorer View中的Spring IoC容器的源代码项目。
图2-1 打开IoC容器的源代码包
2.1.2 Spring的IoC容器系列
IoC容器为开发者管理对象之间的依赖关系提供了很多便利和基础服务,有许多IoC容器供开发者选择,SpringFramework的IoC核心就是其中的一个,它是开源的。那具体什么是IoC容器呢?它在Spring框架中到底长什么样?其实对IoC容器的使用者来说,我们经常接触到的BeanFactory和ApplicationContext都可以看成是容器的具体表现形式。我们通常所说的IoC容器,如果深入到Spring的实现去看,会发现IoC容器实际上代表着一系列功能各异的容器产品,只是容器的功能有大有小,有各自的特点。我们举水桶为例子,在商店中出售的水桶有大有小,制作材料也各不相同,有金属的、塑料的等,总之是各式各样,但只要能装水,具备水桶的基本特性,那就可以作为水桶来出售,来让用户使用。这在Spring中也是一样,Spring有各式各样的IoC容器的实现供用户选择和使用。使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那对容器的使用是非常有帮助的,就像我们在购买商品前对商品进行考察和挑选那样。图2-2展示了这个容器系列的概况。
图2-2 Spring的IoC容器系列概况
就像商品需要有产品规格说明一样,同样,作为IoC容器,也需要为它的具体实现指定基本的功能规范,这个功能规范的设计表现为接口类BeanFactory,它体现了Spring为提供给用户使用的IoC容器所设定的最基本功能规范。还是举前面我们说的百货商店出售的水桶为例子,如果把IoC容器看成一个水桶,那么这个BeanFactory就定义了可以作为水桶的基本功能,比如至少能装水,有个提手什么的。满足了基本的功能,为了不同场合的需要,水桶的生产厂家还在这个基础上为用户设计了其他各式各样的水桶产品,来满足不同的用户需求。这些水桶会提供更丰富的功能,有简约型的,有豪华型的,等等。但是,不管什么水桶,它都需要有一项最基本的功能:能够装水。那对Spring的具体IoC容器实现来说,它需要满足的基本特性是什么呢?它需要满足BeanFactory这个基本的接口定义,所以在图2-2中可以看到,这个BeanFactory接口在继承体系中的地位,它是作为一个最基本的接口类出现在Spring的IoC容器体系中的。
在这些Spring提供的基本IoC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系。BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。我们都知道,在计算机的世界里,所有的功能都是建立在用数据对现实进行抽象的基础上完成的。IoC容器是用来管理对象依赖关系的,对IoC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理上完成的。这些BeanDefinition就像是容器里装的水,有了这些基本数据,容器才能够发挥作用。在下面的分析中,BeanDefinition的上镜次数会很多,我们在这里先简单地打个招呼。
同时,在使用IoC容器时,了解BeanFactory和ApplicationContext之间的区别对我们理解和使用IoC容器也是比较重要的。弄清楚了这两种重要容器之间的区别和联系,意味着我们具备辨别容器系列中不同容器产品的能力。还有一个好处就是,如果需要定制特定功能的容器实现,也能比较方便地在容器系列中找到一款恰当的产品作为参考。
2.2 IoC容器系列的实现:BeanFactory和ApplicationContext
2.2.1 BeanFactory对IoC容器的功能定义
从前面的介绍,我们知道BeanFactory定义了IoC容器的基本功能规范,所以,下面我们就从BeanFactory这个最基本的容器定义来进入Spring的IoC容器体系,去了解IoC容器的实现原理。IoC容器的基本接口是由BeanFactory来定义的,也就是说,BeanFactory定义了IoC容器的最基本的形式,并且提供了IoC容器所应该遵守的最基本的服务契约。同时,这也是我们使用IoC容器所应遵守的最底层和最基本的编程规范,这些接口定义勾画出了IoC的基本轮廓。很显然,在Spring的代码实现中,BeanFactory只是一个接口类,并没有给出容器的具体实现,而我们在图2-2中看到的各种具体类,比如DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等都可以看成是容器的附加了某种功能的具体实现,也就是容器体系中的具体容器产品。下面我们来看看BeanFactory是怎样定义IoC容器的基本接口的。下面介绍这个基本接口为用户提供的基本功能。
用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分通过容器来获取FactoryBean产生的对象和获取FactoryBean本身。举例来说,如果myJndiObject是一个FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJndiObject这个FactoryBean产生出来的对象。
注意 理解上面这段话需要很好地区分FactoryBean和BeanFactory这两个在Spring中使用频率很高的类,它们在拼写上非常相似。一个是Factory,也就是IoC容器或对象工厂;一个是Bean。在Spring中,所有Bean都是由BeanFactory(也就是IoC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。
BeanFactory接口设计了getBean方法,这个方法是使用IoC容器API的主要方法,通过这个方法,可以取得IoC容器中管理的Bean, Bean的取得是通过指定名字来进行索引的。如果需要在获取Bean时对Bean的类型进行检查,BeanFactory接口定义了带有参数的getBean方法,这个方法的使用与getBean方法类似,不同的是增加了对Bean检索的类型的要求。
用户可以通过BeanFactory接口方法getBean来使用Bean名字,从而当获取Bean时,如果需要获取的Bean是prototype类型的,用户还可以为这个prototype类型的Bean生成指定构造函数的对应参数。这使得在一定程度上可以控制生成prototype类型的Bean。有了BeanFactory的定义,用户可以执行以下操作:
❑ 通过接口方法containsBean让用户能够判断容器是否含有指定名字的Bean。
❑ 通过接口方法isSingleton来查询指定了名字的Bean是否是Singleton类型的Bean。对于Singleton属性,用户可以在BeanDefinition中指定。
❑ 通过接口方法isPrototype来查询指定了名字的Bean是否是prototype类型的。与Singleton属性一样,这个属性也可以由用户在BeanDefinition中指定。
❑ 通过接口方法isTypeMatch来查询指定了名字的Bean的Class类型是否是特定的Class类型。这个Class类型可以由用户来指定。
❑ 通过接口方法getType来查询指定了名字的Bean的Class类型。
❑ 通过接口方法getAliases来查询指定了名字的Bean的所有别名,这些别名都是用户在BeanDefinition中定义的。
这些定义的接口方法勾画出了IoC容器的基本特性,因为BeanFactory接口定义了IoC容器,所以下面给出它定义的全部内容来让大家参考,如代码清单2-1所示。
代码清单2-1 BeanFactory接口
public interface BeanFactory { /** * Used to dereference a {@link FactoryBean} instance and distinguish it from * beans <i>created</i> by the FactoryBean. For example, if the bean named * <code>myJndiObject</code> is a FactoryBean, getting <code>&myJndiObject </code> * will return the factory, not the instance returned by the factory. */ String FACTORY_BEAN_PREFIX = "&"; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>This method allows a Spring BeanFactory to be used as a replacement for the * Singleton or Prototype design pattern. Callers may retain references to * returned objects in the case of Singleton beans. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. * */ Object getBean(String name) throws BeansException; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type * safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the * required type. This means that ClassCastException can' t be thrown on casting * the result correctly, as can happen with {@link #getBean(String)}. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. * */ <T> T getBean(String name, Class<T> requiredType) throws BeansException; /** * Return an instance, which may be shared or independent, of the specified bean. * <p>Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. * */ Object getBean(String name, Object... args) throws BeansException; /** * Does this bean factory contain a bean with the given name? More specifically, * is {@link #getBean} able to obtain a bean instance for the given name? * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean containsBean(String name); /** * Is this bean a shared singleton? That is, will {@link #getBean} always * return the same instance? * <p>Note: This method returning <code>false</code> does not clearly indicate * independent instances. It indicates non-singleton instances, which may correspond * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly * check for independent instances. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isSingleton(String name) throws NoSuchBeanDefinitionException; /** * Is this bean a prototype? That is, will {@link #getBean} always return * independent instances? * <p>Note: This method returning <code>false</code> does not clearly indicate * a singleton object. It indicates non-independent instances, which may correspond * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly * check for a shared singleton instance. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isPrototype(String name) throws NoSuchBeanDefinitionException; /** * Check whether the bean with the given name matches the specified type. * More specifically, check whether a {@link #getBean} call for the given name * would return an object that is assignable to the specified target type. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException; /** * Determine the type of the bean with the given name. More specifically, * determine the type of object that {@link #getBean} would return for the given name. * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates, * as exposed by {@link FactoryBean#getObjectType()}. * <p>Translates aliases back to the corresponding canonical bean name. * Will ask the parent factory if the bean cannot be found in this factory instance. */ Class getType(String name) throws NoSuchBeanDefinitionException; /** * Return the aliases for the given bean name, if any. * All of those aliases point to the same bean when used in a {@link #getBean} call. * <p>If the given name is an alias, the corresponding original bean name * and other aliases (if any) will be returned, with the original bean name * being the first element in the array. * <p>Will ask the parent factory if the bean cannot be found in this factory instance. */ String[] getAliases(String name); }
2.2.2 IoC容器XmlBeanFactory的工作原理
这个BeanFactory接口提供了使用IoC容器的规范。在这个基础上,Spring还提供了符合这个IoC容器接口的一系列容器的实现供开发人员使用。例如,在图2-2中,我们可以看到BeanFactory的相关部分的实现。为简单起见,我们浏览一下图2-2的BeanFactory的继承体系,注意AutowireCapableBeanFactory→AbstractAutowireCapableBeanFactory→DefaultListableBeanFactory→XmlBeanFactory IoC容器的实现系列。
我们从这个容器系列的最底层实现XmlBeanFactory开始,这个容器的实现与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点,它只提供了最基本的IoC容器的功能。从它的名字中可以看出,这个IoC容器可以读取以XML形式定义的BeanDefinition。理解这一点有助于理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。关于ApplicationContext的分析,以及它与BeanFactory相比的增强特性都会在下面进行详细的分析。
让我们回顾一下这个继承体系,从中可以清楚地看到它们之间的联系,它们都是IoC容器系列的组成部分。在设计这个容器系列时,我们可以从继承体系的发展上看到IoC容器各项功能的实现过程。如果要扩展自己的容器产品,建议读者最好在这个继承体系中检验一下,看看Spring是不是已经提供了现成的或相近的容器实现供我们参考。下面就从我们比较熟悉的XmlBeanFactory的实现入手进行分析,来看看一个基本的IoC容器是怎样实现的。
如果仔细阅读XmlBeanFactory的源码,在一开始的注释里面已经对XmlBeanFactory的功能做了简要的说明,从代码的注释还可以看到,这是Rod Johnson在2001年就写下的代码,可见这个类应该是Spring的元老类了。它是继承DefaultListableBeanFactory这个类的,而且它非常重要,在以后的分析中这个类会经常用到。我们会看到这个DefaultListableBeanFactory实际上包含了IoC容器的重要功能,也是在很多地方都会用到的容器系列中的一个基本产品。
从名字上就可以看出来,在Spring中,实际上是把它作为一个默认的完整功能的IoC容器来使用的。XmlBeanFactory在继承了DefaultListableBeanFactory容器的功能的同时,给DefaultListableBeanFactory增加的功能很容易从XmlBeanFactory的名字上猜到。它是一个与XML相关的BeanFactory,也就是说它可以读取以XML文件方式定义的BeanDefinition的一个IoC容器。
如果说XmlBeanFactory是一个可以读取XML文件方式定义的BeanDefinition的IoC容器,那么这些实现XML读取的功能是怎样实现的呢?对这些XML文件定义信息的处理并不是由XmlBeanFactory来直接处理的。在XmlBeanFactory中,初始化了一个XmlBeanDefini-tionReader对象,有了这个Reader对象,那些以XML的方式定义的BeanDefinition就有了处理的地方。我们可以看到,对这些XML形式的信息的处理实际上是由这个XmlBeanDefini-tionReader来完成的。
构造XmlBeanFactory这个IoC容器时,需要指定BeanDefinition的信息来源,而这个信息来源需要封装成Spring中的Resource类来给出。Resource是Spring用来封装IO操作的类。比如,我们的BeanDefinition信息是以xml文件形式存在的,那么可以使用像ClassPathResource res = new ClassPathResource("beans.xml");这样具体的ClassPathResource来构造需要的Resource,然后作为构造参数传递给XmlBeanFactory构造函数。这样,IoC容器就可以方便地定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程。
XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,在这个基本容器的基础上实现了其他诸如XML读取的附加功能。对于这些功能的实现原理,看一看XmlBeanFactory的代码实现就能很容易地理解。如代码清单2-2所示,在XmlBeanFactory构造方法中需要得到Resource对象。对XmlBeanDefinitionReader对象的初始化,以及使用这个对象来完成loadBeanDefinitions的调用,就是这个调用启动了从Resource中载入BeanDefinitions的过程,loadBeanDefinitions同时也是IoC容器初始化的重要组成部分。
代码清单2-2 XmlBeanFactory的实现
public class XmlBeanFactory extends DefaultListableBeanFactory { private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } 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容器实现中的那些关键的类(比如Resource、DefaultListableBeanFactory以及BeanDefinitionReader)之间的相互关系,例如它们是如何把IoC容器的功能解耦的,又是如何结合在一起为IoC容器服务的,等等。在代码清单2-3中可以看到编程式使用IoC容器的过程。
代码清单2-3编程式使用IoC容器
ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(res);
这样,我们就可以通过factory对象来使用DefaultListableBeanFactory这个IoC容器了。在使用IoC容器时,需要如下几个步骤:
1)创建IoC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息。
2)创建一个BeanFactory,这里使用DefaultListableBeanFactory。
3)创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。
4)从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这个时候IoC容器就可以直接使用了。
2.2.3 ApplicationContext的特点
我们了解了IoC容器建立的基本步骤。现在可以很方便地通过编程的方式来手工控制这些配置和容器的建立过程了。但是,在Spring中系统已经为用户提供了许多已经定义好的容器实现,而不需要开发人员事必躬亲。相比那些简单拓展BeanFactory的基本IoC容器,开发人员常用的ApplicationContext除了能够提供在上面看到的容器的基本功能外,还为用户提供了以下的附加服务,可以让客户更方便地使用。所以说,ApplicationContext是一个高级形态意义的IoC容器,如图2-3所示,可以看到ApplicationContext在BeanFactory的基础上添加的附加功能,这些功能为ApplicationContext提供了以下BeanFactory不具备的新特性。
图2-3 ApplicationContext的接口关系
❑ 支持不同的信息源。我们看到ApplicationContext扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务。
❑ 访问资源。体现在对ResourceLoader和Resource的支持上,这样我们可以从不同地方得到Bean定义资源。这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的IO途径得到Bean定义信息。这在接口关系上看不出来,一般来说,具体Applic-ationContext都是继承了DefaultResourceLoader的子类。因为DefaultResourc-eLoader是AbstractApplicationContext的基类,关于Resource在IoC容器中的使用,在2.3节中有详细的讲解。
❑ 支持应用事件。继承了接口ApplicationEventPublisher,这样在上下文中引入了事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
❑ 在ApplicationContext中提供的附加服务。这些服务使得基本IoC容器的功能更丰富。因为具备了这些丰富的附加功能,使得ApplicationContext与简单的BeanFactory相比,对它的使用是一种面向框架的使用风格,所以一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式。
2.3 IoC容器的初始化
IoC容器的初始化包括BeanDefinition的Resouce定位、载入和注册这三个基本的过程。在前面的编程式地使用DefaultListableBeanFactory中,我们可以看到定位和载入过程的接口调用。这里将详细分析这三个过程的实现。值得注意的是,Spring在实现中是把这三个过程分开并使用不同的模块来完成的,这样可以让用户更加灵活地对这三个过程进行剪裁和扩展,定义出最适合自己的IoC容器的初始化过程。
BeanDefinition的资源定位由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。对于这些BeanDefinition的存在形式,相信大家都不会感到陌生。比如说,在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象;在类路径中可以使用前面提到的ClassPathResource来使用,等等。这个过程类似于容器寻找数据的过程,就像用水桶装水先要把水找到一样。
第二个关键的部分是BeanDefinition的载入,该载入过程把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition,下面可以看到这个数据结构的详细定义。总地说来,这个BeanDefinition实际上就是POJO对象在IoC容器中的抽象,这个BeanDefinition定义了一系列的数据来使得IoC容器能够方便地对POJO对象也就是Spring的Bean进行管理。即BeanDefinition就是Spring的领域对象。下面我们会对这个载入的过程进行详细的分析,便于大家对整个过程有比较清楚的了解。
第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。可以看到,在IoC容器内部,是通过使用一个HashMap来持有这些BeanDefinition数据的。
值得注意的是,IoC容器和上下文的初始化一般不包含Bean依赖注入的实现。一般而言,依赖注入发生在应用第一次向容器通过getBean索取Bean时。但有一个例外值得注意,在使用IoC容器时有一个预实例化的配置,这个预实例化是可以配置的,具体来说可以通过在Bean定义信息中的lazyinit属性来设定;有了这个预实例化的特性,用户可以对容器初始化过程作一个微小的控制;从而改变这个被设置了lazyinit属性的Bean的依赖注入的发生,使得这个Bean的依赖注入在IoC容器初始化时就预先完成了。有了以上的一个大概的轮廓,下面就详细地看一看在IoC容器的初始化过程中,BeanDefinition的资源定位、载入和解析过程是怎么实现的。
2.3.1 BeanDefinition的Resource定位
以编程的方式使用DefaultListableBeanFactory时,我们可以看到,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,意味着Spring会在类路径中寻找以文件形式存在的BeanDefinition信息。
ClassPathResource res = new ClassPathResource("beans.xml");
这个定义的Resource并不能让DefaultListableBeanFactory直接使用,Spring是通过BeanDefinitionReader来对这些信息进行处理的。在这里,我们也可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它配置特定的读取器才能完成这些功能。当然,有利就有弊,使用DefaultListableBeanFactory这种更底层的容器,却能提高我们定制IoC容器的灵活性。
回到我们经常使用的ApplicationContext上来,例如FileSystemXmlApplication-Context、ClassPathXmlApplicationContext以及XmlWebApplicationContext等。简单地从这些类的名字上分析,可以清楚地看到它们可以提供哪些不同的Resource读入功能,比如FileSystemXmlApplicationContext可以从文件系统载入Resource, ClassPathXm-lApplicationContext可以从Class Path载入Resource, XmlWebApplicationContext可以在Web容器中载入Resource,等等。
下面以FileSystemXmlApplicationContext为例,通过分析这个ApplicationContext的实现来看看它是怎样完成这个Resource定位过程的。作为辅助,我们可以在图2-4中看到相应的ApplicationContext继承体系。
图2-4 FileSystemXmlApplicationContext的继承关系
从图2-4中可以看到,这个FileSystemXmlApplicationContext已经通过继承Abstra-ctApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefiniti-on的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。下面看看FileSystemXmlApplicationContext的具体实现,如代码清单2-4所示。
代码清单2-4 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的文件路径的同时, *还允许指定自己的双亲IoC容器。 */ public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } 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的loadBeanDefintion中被调用的。 *loadBeanDefintion采用了模板模式,具体的定位实现实际上是由各个子类完成的。 */ protected Resource getResourceByPath(String path) { if (path ! = null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
在FileSystemApplicationContext中,我们可以看到实现了两个部分的功能,一部分是在构造函数中,对configuration进行处理,使得所有在配置在文件系统中的XML文件方式的BeanDefnition都能够得到有效的处理,比如实现了getResourceByPath方法,这个方法是一个模板方法,是为读取Resource服务的。对于IoC容器功能的实现,这里没有涉及,因为它继承了AbstractXmlApplicationContext,关于IoC容器功能相关的实现,都是在FileSystemXmlApplicationContext中完成的,但是在构造函数中通过refresh来启动了IoC容器的初始化,这个refresh方法非常重要,也是我们以后分析容器初始化过程实现的一个重要入口。
注意 FileSystemApplicationContext是一个支持XML定义BeanDefinition的ApplicationContext,并且可以指定以文件形式的BeanDefinition的读入,这些文件可以使用文件路径和URL定义来表示。在测试环境和独立应用环境中,这个ApplicationContext是非常的有用的。
根据图2-5的调用关系分析,我们可以清楚地看到整个BeanDefinition资源定位的过程。这个对BeanDefinition资源定位的过程,最初是由refresh来触发的,这个refresh的调用是在FileSystemXmlBeanFactory的构造函数中启动的。
图2-5 getResourceByPath的调用关系
大家看了上面的调用过程可能会比较好奇,这个FileSystemXmlApplicationContext在什么地方定义了BeanDefinition的读入器BeanDefinitionReader,从而完成BeanDefi-nition信息的读入呢?在前面分析过,在IoC容器的初始化过程中,BeanDefinition资源的定位、读入和注册过程是分开进行的,这也是解耦的一个体现。关于这个读入器的配置,可以到FileSystemXmlApplicationContext的基类AbstractRefreshableApplicationContext中看看它是怎样实现的。
我们重点看看AbstractRefreshableApplicationContext的refreshBeanFactory方法的实现,这个refreshBeanFactory被FileSystemXmlApplicationContext构造函数中的refresh调用。在这个方法里,通过createBeanFactroy构建了一个IoC容器供Appl-icationContext使用。这个IoC容器就是我们前面提到过的DefaultListableBeanFactory,同时,它启动了loadBeanDefinitions来载入BeanDefinition,这个过程和我们前面看到的编程式的使用IoC容器(XmlBeanFactory)的过程非常类似。
从代码清单2-4中可以看到,在初始化FileSystmXmlApplicationContext的过程中,通过IoC容器的初始化的refresh来启动整个调用,使用的IoC容器是DefultListableBean Factory。具体的资源载入在XmlBeanDefinitionReader读入BeanDefinition时完成,在XmlBeanDefinitionReader的基类AbstractBeanDefinitionReader中可以看到这个载入过程的具体实现。对载入过程的启动,可以在AbstractRefreshableApplicationCont-ext的loadBeanDefinitions方法中看到,如代码清单2-5所示。
代码清单2-5 AbstractRefreshableApplicationContext对容器的初始化
protected final void refreshBeanFactory() throws BeansException { //这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory。 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } //这里是创建并设置持有的DefaultListableBeanFactor的地方。 //同时调用loadBeanDefinitions再载入BeanDefinitione的信息。 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 XML document for " + getDisplayName(), ex); } } /** *这就是在上下文中创建DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()的 *具体实现可以参看AbstractApplicationContext中的实现,会根据容器已有的双亲IoC容器的信息来 *生成DefaultListableBeanFactory的双亲IoC容器。 */ protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); } /** *这里是使用BeanDefinitionReader载入Bean定义的地方,因为允许有多种载入方式, *虽然用得最多的是XML定义的形式,这里通过一个抽象函数把具体的实现委托给子类来完成。 */ protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException, BeansException; public int loadBeanDefinitions(String location, Set 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 { // 调用DefaultResourceLoader的getResource完成具体的Resource定位。 Resource[] resources = ((ResourcePatternResolver) resourceLoader). getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources ! = null) { for (int i = 0; i < resources.length; i++) { actualResources.add(resources[i]); } } 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 { // Can only load single resources by absolute URL. // 调用DefaultResourceLoader的getResource完成具体的Resource定位。 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources ! = null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } } //对于取得Resource的具体过程,我们可以看看DefaultResourceLoader是怎样完成的: public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); //这里处理带有classpath标识的Resource。 if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX. length()), getClassLoader()); } else { try { // Try to parse the location as a URL... // 这里处理URL标识的Resource定位。 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. /** *如果既不是classpath,也不是URL标识的Resource定位,则把getResource的重任 *交给getResourceByPath,这个方法是一个protected方法,默认的实现是得到一 *个ClassPathContextResource,这个方法常常会用子类来实现。 */ return getResourceByPath(location); } } }
前面我们看到的getResourceByPath会被子类FileSystemXmlApplicationContext实现,这个方法返回的是一个FileSystemResource对象,通过这个对象Spring可以进行相关的IO操作,完成BeanDefinition的定位。分析到这里已经一目了然,它实现的就是对path进行解析,然后生成一个FileSystemResource对象并返回,如代码清单2-6所示。
代码清单2-6 FileSystemXmlApplicationContext生成Resource对象
protected Resource getResourceByPath(String path) { if (path ! = null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
如果是其他的ApplicationContext,那么对应地会生成其他种类的Resource,比如ClassPathResource、ServletContextResource等。关于Spring中Resource的种类,可以在图2-6中的Resource类的继承关系中了解。作为接口的Resource定义了许多与IO相关的操作,这些操作也都可以从图2-6中Resource的接口定义中看到。这些接口对不同的Resource实现代表着不同的意义,是Resource的实现需要考虑的。
图2-6 Resource的定义和继承关系
从图2-6中我们可以看到Resource的定义和它的继承关系,通过对前面的实现原理的分析,我们以FileSystemXmlApplicationContext的实现原理为例子,了解了Resource定位问题的解决方案,即以FileSystem方式存在的Resource的定位实现。在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行BeanDefinition的载入了。在定位过程完成以后,为BeanDefinition的载入创造了IO操作的条件,但是具体的数据还没有开始读入。这些数据的读入将在下面看到的BeanDefinition的载入和解析中来完成。仍然以水桶为例子,这里就像如果要用水桶去打水,那么先要找到水源。这里完成对Resource的定位,就类似于水源已经找到了,下面就是打水的过程了,类似于把找到的水装到水桶里的过程。找水不简单,但与打水相比,我们发现打水更需要技巧。
2.3.2 BeanDefinition的载入和解析
对IoC容器来说,BeanDefinition的载入过程相当于把我们定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程。IoC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关的操作来完成的。这些BeanDefinition数据在IoC容器里通过一个HashMap来保持和维护,当然这只是一种比较简单的维护方式,如果你觉得需要提高IoC容器的性能和容量,完全可以自己做一些扩展。我们从DefaultListableBeanFactory来入手看看IoC容器是怎样完成BeanDefinition载入的。这个DefaultListableBeanFactory已经是我们非常熟悉的基本IoC容器,在前面已经碰到过多次,相信大家对它一定不会感到陌生。为了了解这一点,我们先回到IoC容器的初始化入口,也就是到refresh()方法去看一看。这个方法的最初是在FileSystemXmlApplicationContext的构造函数中被调用的,它的调用意味着容器的初始化或数据更新,这些初始化和更新的数据当然就是BeanDefinition,如代码清单2-7所示。
代码清单2-7启动BeanDefinition的载入
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); //这里调用容器的refresh,是载入BeanDefinition的入口。 if (refresh) { refresh(); } }
对于容器的启动来说,refresh是一个很重要的方法,我们看看它的实现。在AbstractApplicationContext类(它是FileSystemXmlApplicationContext的基类)中找到这个方法,它详细地描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,messagesource和postprocessor的注册,等等。这里看起来更像是对ApplicationContext进行初始化的模板或执行提纲,这个执行过程为IoC容器Bean的生命周期管理提供了条件。这个IoC容器的refresh过程如代码清单2-8所示。
代码清单2-8对IoC容器的refresh的实现
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. // 这里是在子类中启动refreshBeanFactory()的地方。 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) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset ' active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
我们进入到AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,在这个方法里创建了BeanFactory。在创建IoC容器前,如果已经有容器存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立起来的IoC容器。这么看来,这个refresh非常像我们对容器的重启动,就像计算机的重启动那样。在建立好当前的IoC容器以后,开始了对容器的初始化过程,比如BeanDefinition的载入,具体的实现如代码清单2-9所示。
代码清单2-9 AbstractRefreshableApplicationContext的refreshBeanFactory方法
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //创建IoC容器,这里使用的是DefaultListableBeanFactory。 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //启动对BeanDefintion的载入。 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing XML document for " + getDisplayName(), ex); } }
这里调用的loadBeanDefinitions实际上是一个抽象方法,那么实际的载入过程是在哪里发生的呢?我们看看前面提到的loadBeanDefinitios在AbstractRefreshableApplicat ionContext的子类AbstractXmlApplicationContext中的实现,在这个loadBeanDefin itions中,初始化了读取器XmlBeanDefinitionReader,然后再把这个读取器在IoC容器中设置好(过程和编程式使用XmlBeanFactory是类似的),最后是启动读入器来完成BeanDefin ition在IoC容器中的载入,如代码清单2-10所示。
代码清单2-10 AbstractXmlApplicationContext中的loadBeanDefinitions
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext { public AbstractXmlApplicationContext() { } public AbstractXmlApplicationContext(ApplicationContext parent) { super(parent); } //这里是实现loadBeanDefinitions的地方。 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. /** *创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去, *创建BeanFactory的过程可以参考上文对编程式使用IoC容器的相关分析,这里和前面一样, *使用的也是DefaultListableBeanFactory。 */ XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader (beanFactory); /** *Configure the bean definition reader with this context' s *resource loading environment. */ /** *这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader *配置ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用。 */ 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. */ // 这是启动Bean定义信息载入的过程。 initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); } protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { }
接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中,如代码清单2-11所示。
代码清单2-11 XmlBeanDefinitionReader载入XmlBeanDefinitionReader
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources ! = null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations ! = null) { reader.loadBeanDefinitions(configLocations); } } protected Resource[] getConfigResources() { return null; } }
通过以上实现原理的分析,我们可以看到,在初始化FileSystmXmlApplicationContext的过程中,是通过调用IoC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XmlBeanDefinitionReader来完成的。同时,我们也知道实际使用的IoC容器是DefultListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现。因为Spring可以对应不同形式的BeanDefinition。由于这里使用的是XML方式的定义,所以需要使用XmlBeanDefinitionReader。如果使用了其他的BeanDefinition方式,就需要使用其他种类的BeanDefinitionReader来完成数据的载入工作。在XmlBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinitions中开始进行BeanDefinition的载入的,而这时XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader已经为BeanDefinition的载入做好了准备,如代码清单2-12所示。
代码清单2-12 AbstractBeanDefinitionReader载入BeanDefinitions
public int loadBeanDefinitions(Resource[] resources) throws BeanDefinitionStoreException { //如果Resource为空,则停止BeanDefinition的载入。 /** *然后启动载入BeanDefinition的过程,这个过程会遍历整个Resource集合所包含的 *BeanDefinition信息。 */ Assert.notNull(resources, "Resource array must not be null"); int counter = 0; for (int i = 0; i < resources.length; i++) { counter += loadBeanDefinitions(resources[i]); } return counter; }
这里调用的是loadBeanDefinitions(Resource res)方法,然而这个方法在AbstractBeanDefinitionReader类里是没有实现的,它是一个接口方法,具体的实现在XmlBeanDefinitionReader中。在读取器中,需要得到代表XML文件的Resource,因为这个Resource对象封装了对XML文件的IO操作,所以读取器可以在打开IO流后得到XML的文件对象。有了这个Document对象以后,就可以按照Spring的Bean定义规则来对这个XML的文档树进行解析了,这个解析是交给BeanDefinitionParserDelegate来完成的,看起来实现脉络很清楚。具体可以参考代码实现,如代码清单2-13所示。
代码清单2-13对BeanDefinition的载入实现
//这里是调用的入口。 public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } //这里是载入XML形式的BeanDefinition的地方。 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 recursive loading of " + encodedResource + " - check your import definitions! "); } //这里得到XML文件,并得到IO的InputSource准备进行读取。 try { InputStream inputStream = encodedResource.getResource().getInputStream(); try { InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() ! = null) { inputSource.setEncoding(encodedResource.getEncoding()); } 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.set(null); } } } //具体的读取过程可以在doLoadBeanDefinitions方法中找到。 //这是从特定的XML文件中实际载入BeanDefinition的地方。 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { int validationMode = getValidationModeForResource(resource); /** *这里取得XML文件的Document对象,这个解析过程是由documentLoader完成的, *这个documentLoader是DefaultDocumentLoader,在定义documentLoader的地方创建。 */ Document doc = this.documentLoader.loadDocument( inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware()); /** *这里启动的是对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); } }
感兴趣的读者,可以到DefaultDocumentLoader中去看看是怎样得到Document对象的,这里就不详细分析了。我们关心的是Spring的BeanDefinion是怎样按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的,这个过程是在registerBeanDefinitions (doc, resource)中完成的。具体的过程是由BeanDefinitionDocumentReader来完成的,这个registerBeanDefinition还对载入的Bean的数量进行了统计。具体的过程如代码清单2-14所示。
代码清单2-14 registerBeanDefinition的代码实现
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // Read document based on new BeanDefinitionDocumentReader SPI. // 这里得到BeanDefinitionDocumentReader来对xml的BeanDefinition进行解析。 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 具体的解析过程在这个registerBeanDefinitions中完成。 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
BeanDefinition的载入包括两部分,首先是通过调用XML的解析器得到document对象,但这些document对象并没有按照Spring的Bean规则进行解析。在完成通用的XML解析以后,才是按照Spring的Bean规则进行解析的地方,按照Spring的Bean规则进行解析的过程是在documentReader中实现的。这里使用的documentReader是默认设置好的DefaultBeanDe-finitionDocumentReader。这个DefaultBeanDefinitionDocumentReader的创建是在以下的方法里完成的,然后再完成BeanDefinition的处理,处理的结果由BeanDefinition-Holder对象来持有。这个BeanDefinitionHolder除了持有BeanDefinition对象外,还持有了其他与BeanDefinition的使用相关的信息,比如Bean的名字、别名集合等。这个Bean-DefinitionHolder的生成是通过对Document文档树的内容进行解析来完成的,可以看到这个解析过程是由BeanDefinitionParserDelegate来实现(具体在processBeanDefinition方法中实现)的,同时这个解析是与Spring对BeanDefinition的配置规则紧密相关的。具体的实现原理如代码清单2-15所示。
代码清单2-15创建BeanDefinitionDocumentReader
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass (this.documentReaderClass)); } //这样,得到了documentReader以后,为具体的Spring Bean的解析过程准备好了数据。 /** *这里是处理BeanDefinition的地方,具体的处理是委托给BeanDefinitionParserDelegate *来完成的,ele对应于我们的Spring BeanDefinition中定义的xml元素。 */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { /* BeanDefinitionHolder是BeanDefinition对象的封装类,封装了BeanDefinition, *Bean的名字和别名。用它来完成向IoC容器注册。得到这个BeanDefinitionHolder实际上就意 *味着获得了BeanDefinition,是通过BeanDefinitionParserDelegate对XML元素的信息按照 *Spring的Bean规则进行解析得到的。 */ BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder ! = null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 这里是向IoC容器注册解析得到的BeanDefinition的地方。 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name ' " + bdHolder.getBeanName() + "' ", ele, ex); } // 在BeanDefinition向IoC容器注册完以后,发送消息。 getReaderContext().fireComponentRegistered(new BeanComponentDefinition (bdHolder)); } }
具体的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate中完成的。这个类里包含了各种Spring Bean定义规则的处理,感兴趣的读者可以仔细研究。比如我们最熟悉的对Bean元素的处理是怎样完成的,也就是在XML定义文件中出现的<bean></bean>这个最常见的元素信息是怎样处理的。在这里,我们会看到那些熟悉的BeanDefinition定义的处理,比如id、name、aliase等属性元素。把这些元素的值从XML文件相应的元素的属性中读取出来以后,会被设置到生成的BeanDefinitionHolder中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种Bean的属性配置,通过一个较为复杂的解析过程,这个过程是由parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitionHolder中去,如代码清单2-16所示。
代码清单2-16 BeanDefinitionParserDelegate对bean元素定义的处理代码清单2-17对BeanDefinition定义元素的处理
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { //这里取得在<bean>元素中定义的id、name和aliase属性的值。 String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<String>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_ DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } String beanName = id; if (! StringUtils.hasText(beanName) && ! aliases.isEmpty()) { beanName = aliases.remove(0); if (logger.isDebugEnabled()) { logger.debug("No XML ' id' specified - using ' " + beanName + "' as bean name and " + aliases + " as aliases"); } } if (containingBean == null) { checkNameUniqueness(beanName, aliases, ele); } //这个方法会引发对bean元素的详细解析。 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); if (beanDefinition ! = null) { if (! StringUtils.hasText(beanName)) { try { if (containingBean ! = null) { beanName = BeanDefinitionReaderUtils.generateBeanName( beanDefinition, this.readerContext.getRegistry(), true); } else { beanName = this.readerContext.generateBeanName(beanDefinition); /** *Register an alias for the plain bean class name, if still *possible, if the generator returned the class name plus a *suffix.This is expected for Spring 1.2/2.0 backwards *compatibility. */ String beanClassName = beanDefinition.getBeanClassName(); if (beanClassName ! = null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { aliases.add(beanClassName); } } if (logger.isDebugEnabled()) { logger.debug("Neither XML ' id' nor ' name' specified - " + "using generated bean name [" + beanName + "]"); } } catch (Exception ex) { error(ex.getMessage(), ele); return null; } } String[] aliasesArray = StringUtils.toStringArray(aliases); return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } return null; }
我们看到了对Bean元素进行解析的过程,也就是BeanDefinition依据XML的<bean>定义被创建的过程。这个BeanDefinition可以看成是<bean>定义的抽象,如图2-7所示。这个数据对象里封装的数据大多都是与<bean>定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记,比如我们熟悉的init-method、destroy-method、factory-method,等等,这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据。有了这些基本数据,IoC容器才能对Bean配置进行处理,才能实现相应的容器特性。
图2-7 BeanDefinition的数据定义
看起来很熟悉吧,beanClass、description、lazyInit这些属性都是在配置Bean时经常碰到的,原来都跑到这里来了。这个BeanDefinition是IoC容器体系中非常重要的核心数据结构。通过解析以后,这些数据已经做好在IoC容器里大显身手的准备了。对BeanDefinition的元素的处理如代码清单2-17所示,在这个过程中可以看到对Bean定义的相关处理,比如对元素attribute值的处理,对元素属性值的处理,对构造函数设置的处理,等等。
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); /** *这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去, *只是做个记录,并不涉及对象的实例化过程,对象的实例化实际上是在依赖注入时完成的。 */ String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); } try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } //这里生成需要的BeanDefinition对象,为Bean定义信息的载入做准备。 AbstractBeanDefinition bd = createBeanDefinition(className, parent); //这里对当前的Bean元素进行属性解析,并设置description的信息。 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); //从名字可以清楚地看到,这里是对各种<bean>元素的信息进行解析的地方。 parseMetaElements(ele, bd); parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析<bean>的构造函数设置。 parseConstructorArgElements(ele, bd); //解析<bean>的property设置。 parsePropertyElements(ele, bd); parseQualifierElements(ele, bd); bd.setResource(this.readerContext.getResource()); bd.setSource(extractSource(ele)); return bd; } /** *下面这些异常是我们在配置bean出现问题时经常可以看到的,原来是在这里抛出的,这些检查是在 *createBeanDefinition时进行的,会检查bean的class设置是否正确,比如这个类是不是能找到。 */ catch (ClassNotFoundException ex) { error("Bean class [" + className + "] not found", ele, ex); } catch (NoClassDefFoundError err) { error("Class that bean class [" + className + "] depends on not found", ele, err); } catch (Throwable ex) { error("Unexpected failure during bean definition parsing", ele, ex); } finally { this.parseState.pop(); } return null; }
上面是具体生成BeanDefinition的地方。在这里,我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类BeanDefinitionParserDelegate的代码中,它对BeanDefinition中的定义一层一层地进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会封装成PropertyValue对象并设置到BeanDefinition对象中去,如代码清单2-18所示。
代码清单2-18对BeanDefinition中Property元素集合的处理
//这里对指定bean元素的property子元素集合进行解析。 public void parsePropertyElements(Element beanEle, BeanDefinition bd) { //遍历所有bean元素下定义的property元素。 NodeList nl = beanEle.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && DomUtils.nodeNameEquals(node, PROPERTY_ ELEMENT)) { //在判断是property元素后对该property元素进行解析的过程。 parsePropertyElement((Element) node, bd); } } } public void parsePropertyElement(Element ele, BeanDefinition bd) { //这里取得property的名字。 String propertyName = ele.getAttribute(NAME_ATTRIBUTE); if (! StringUtils.hasLength(propertyName)) { error("Tag ' property' must have a ' name' attribute", ele); return; } this.parseState.push(new PropertyEntry(propertyName)); try { /** *如果同一个bean中已经有同名property的存在,则不进行解析,直接返回。也就是说, *如果在同一个bean中有同名的property设置,那么起作用的只是第一个。 */ if (bd.getPropertyValues().contains(propertyName)) { error("Multiple ' property' definitions for property ' " + propertyName + "' ", ele); return; } /** *这里是解析property值的地方,返回的对象对应对Bean定义的property属性 *设置的解析结果,这个解析结果会封装到PropertyValue对象中,然后设置到 *BeanDefinitionHolder中去。 */ Object val = parsePropertyValue(ele, bd, propertyName); PropertyValue pv = new PropertyValue(propertyName, val); parseMetaElements(ele, pv); pv.setSource(extractSource(ele)); bd.getPropertyValues().addPropertyValue(pv); } finally { this.parseState.pop(); } } //这里取得property元素的值,也许是一个list或其他。 public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { String elementName = (propertyName ! = null) ? "<property> element for property ' " + propertyName + "' " : "<constructor-arg> element"; // Should only have one child element: ref, value, list, etc. NodeList nl = ele.getChildNodes(); Element subElement = null; for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element && ! DomUtils.nodeNameEquals(node, DESCRIPTION_ ELEMENT) && !DomUtils.nodeNameEquals(node, META_ELEMENT)) { // Child element is what we' re looking for. if (subElement ! = null) { error(elementName + " must not contain more than one sub-element", ele); } else { subElement = (Element) node; } } } //这里判断property的属性,是ref还是value,不允许同时是ref和value。 boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) —— ((hasRefAttribute —— hasValueAttribute) && subElement ! = null)) { error(elementName + " is only allowed to contain either ' ref' attribute OR ' value' attribute OR sub-element", ele); } //如果是ref,创建一个ref的数据对象RuntimeBeanReference,这个对象封装了ref的信息。 if (hasRefAttribute) { String refName = ele.getAttribute(REF_ATTRIBUTE); if (! StringUtils.hasText(refName)) { error(elementName + " contains empty ' ref' attribute", ele); } RuntimeBeanReference ref = new RuntimeBeanReference(refName); ref.setSource(extractSource(ele)); return ref; } //如果是value,创建一个它的数据对象TypedStringValue,这个对象封装了value的信息。 else if (hasValueAttribute) { TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute (VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele)); return valueHolder; } //如果还有子元素,触发对子元素的解析。 else if (subElement ! = null) { return parsePropertySubElement(subElement, bd); } else { // Neither child element nor "ref" or "value" attribute found. error(elementName + " must specify a ref or value", ele); return null; } }
这里是对property子元素的解析过程,Array、List、Set、Map、Prop等各种元素都会在这里进行解析,生成对应的数据对象,比如ManagedList、ManagedArray、ManagedSet等。这些Managed类是Spring对具体的BeanDefinition的数据封装。具体的解析过程读者可以去看看自己感兴趣的部分,比如parseArrayElement、parseListElement、parseSetElement、parseMapElement、parsePropElement对应着不同类型的数据解析,同时这些具体的解析方法在BeanDefinitionParserDelegate类中也都能够找到。因为方法命名很清晰,所以从方法名字上就能够很快地找到。下面,以对Property的元素进行解析的过程为例,通过它的实现来说明这个具体的解析过程是怎样完成的,如代码清单2-19所示。
代码清单2-19对属性元素进行解析
public Object parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType) { if (! isDefaultNamespace(ele.getNamespaceURI())) { return parseNestedCustomElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, BEAN_ELEMENT)) { BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); if (nestedBd ! = null) { nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); } return nestedBd; } else if (DomUtils.nodeNameEquals(ele, REF_ELEMENT)) { // A generic reference to any name of any bean. String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (! StringUtils.hasLength(refName)) { // A reference to the id of another bean in the same XML file. refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); if (! StringUtils.hasLength(refName)) { // A reference to the id of another bean in a parent context. refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; if (! StringUtils.hasLength(refName)) { error("' bean' , ' local' or ' parent' is required for <ref> element", ele); return null; } } } if (! StringUtils.hasText(refName)) { error("<ref> element contains empty target attribute", ele); return null; } RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); ref.setSource(extractSource(ele)); return ref; } else if (DomUtils.nodeNameEquals(ele, IDREF_ELEMENT)) { return parseIdRefElement(ele); } else if (DomUtils.nodeNameEquals(ele, VALUE_ELEMENT)) { return parseValueElement(ele, defaultValueType); } else if (DomUtils.nodeNameEquals(ele, NULL_ELEMENT)) { // It' s a distinguished null value. Let' s wrap it in a TypedStringValue // object in order to preserve the source location. TypedStringValue nullHolder = new TypedStringValue(null); nullHolder.setSource(extractSource(ele)); return nullHolder; } else if (DomUtils.nodeNameEquals(ele, ARRAY_ELEMENT)) { return parseArrayElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, LIST_ELEMENT)) { return parseListElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, SET_ELEMENT)) { return parseSetElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, MAP_ELEMENT)) { return parseMapElement(ele, bd); } else if (DomUtils.nodeNameEquals(ele, PROPS_ELEMENT)) { return parsePropsElement(ele); } else { error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); return null; } }
我们看看类似List这样的属性配置是怎样被解析的,依然在BeanDefinitionParser-Delegate中,返回的是一个List对象,这个List是Spring定义的ManagedList,作为封装List这类配置定义的数据封装,如代码清单2-20所示。
代码清单2-20解析BeanDefinition中的List元素
public List parseListElement(Element collectionEle, BeanDefinition bd) { String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); NodeList nl = collectionEle.getChildNodes(); ManagedList<Object> target = new ManagedList<Object>(nl.getLength()); target.setSource(extractSource(collectionEle)); target.setElementTypeName(defaultElementType); target.setMergeEnabled(parseMergeAttribute(collectionEle)); //具体的List元素的解析过程。 parseCollectionElements(nl, target, bd, defaultElementType); return target; } protected void parseCollectionElements( NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType) { //遍历所有的元素节点,并判断其类型是否为Element。 for (int i = 0; i < elementNodes.getLength(); i++) { Node node = elementNodes.item(i); if (node instanceof Element && ! DomUtils.nodeNameEquals(node, DESCRIPTION_ ELEMENT)) { /** *加入到target中,target是一个ManagedList,同时触发对下一层子元素的解析过程, *这是一个递归调用。 */ target.add(parsePropertySubElement((Element) node, bd, defaultElement Type)); } } }
经过这样逐层地解析,我们在XML文件中定义的BeanDefinition就被整个给载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的映像,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。简单的POJO操作背后其实并不简单,经过以上的载入过程,IoC容器大致完成了管理Bean对象的数据准备工作(或者说是初始化过程)。但是,重要的依赖注入实际上在这个时候还没有发生,现在,在IoC容器BeanDefinition中存在的还只是一些静态的配置信息。严格地来说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需完成数据向容器的注册。
2.3.3 BeanDefinition在IoC容器中的注册
我们已经分析过BeanDefinition在IoC容器中载入和解析的过程。在这些动作完成以后,用户定义的BeanDefinition信息已经在IoC容器内建立起了自己的数据结构以及相应的数据表示,但此时这些数据还不能让IoC容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。这个注册为IoC容器了提供更友好的使用方式,在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory可以看到,如下所示。
/** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap <String, BeanDefinition>();
将解析得到的BeanDefinition向IoC容器中的beanDefinitionMap注册的过程是在载入BeanDefinition完成后进行的,注册的调用过程如图2-8所示。
图2-8 registerBeanDefinition的调用过程
我们跟踪以上的代码调用去看一下具体的注册实现,在DefaultListableBeanFactory中实现了BeanDefinitionRegistry的接口,这个接口的实现完成BeanDefinition向容器的注册。这个注册过程不复杂,就是把解析得到的BeanDefinition设置到hashMap中去。需要注意的是,如果遇到同名的BeanDefinition的情况,进行处理的时候需要依据allowBeanDefinitionOverriding的配置来完成。具体的是实现如代码清单2-21所示。
代码清单2-21 BeanDefinitio注册的实现
//--------------------------------------------------------------------- // Implementation of BeanDefinitionRegistry interface. //--------------------------------------------------------------------- public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { Assert.hasText(beanName, "' beanName' must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); if (beanDefinition instanceof AbstractBeanDefinition) { try { ((AbstractBeanDefinition) beanDefinition).validate(); } catch (BeanDefinitionValidationException ex) { throw new BeanDefinitionStoreException( beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex); } } //注册的过程需要synchronized,保证数据的一致性。 synchronized (this.beanDefinitionMap) { /** *这里检查是不是有相同名字的BeanDefinition已经在IoC容器中注册了,如果有 *相同名字的BeanDefinition,但又不允许覆盖,那么抛出异常。 */ Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition ! = null) { if (! this.allowBeanDefinitionOverriding) { throw new BeanDefinitionStoreException( beanDefinition.getResource Description(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean ' " + beanName + "' : There is already [" + oldBeanDefinition + "] bound."); } else { if (this.logger.isInfoEnabled()) { this.logger.info("Overriding bean definition for bean ' " + beanName +"' : replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]"); } } } /** *这是正常注册BeanDefinition的过程,把Bean的名字存入到beanDefinitionNames的同时, *把beanName作为Map的key,把beanDefinition作为value存入到IoC容器持有的 *beanDefinitionMap中去。 */ else { this.beanDefinitionNames.add(beanName); this.frozenBeanDefinitionNames = null; } this.beanDefinitionMap.put(beanName, beanDefinition); resetBeanDefinition(beanName); } }
完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在我们使用的IoC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,它们都可在beanDefinitionMap里检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础,有了这些基础数据,下面我们接着看看在IoC容器中,依赖注入是怎样完成的。