《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

尽管前两天已经对IOC进行一番收集和总结,但还是不能深刻理解IOC的原理与实现。

今天,在《Spring技术内幕》重新将IOC的原理与实现进行总结。

什么是依赖反转?

许多应用都是由两个或多个类通过彼此的合作来实现业务逻辑的,这使得每个对象都需要与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,这将导致代码高度耦合并且难以测试。

如果合作的对象引用或依赖关系的管理由具体对象来完成,会导致代码的高度耦合和可测试性的降低,这对复杂的面向对象系统的设计是非常不利的。在面向对象系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或IOC容器来完成,这种从具体对象中交出控制的做法是非常有价值的,它可以在解耦代码的同时提高代码的可测试性。

如何反转对依赖的控制?

把控制权从具体业务对象中转交到平台或者框架中,是降低面向对象系统设计性和提高面向对象系统可测试性的一个有效的解决方案。它促进了IOC设计模式的发展,是IOC容器要解决的核心问题。

注意:应用控制反转后,当对象被创建时,由一个调控系统内的所有对象的外界实体将所依赖的对象的引用传递给它,即依赖注入到对象中。所以,控制反转是关于一个对象如何获取它所依赖的对象的引用,在这里,反转指的是责任的反转。

SpringIOC的应用场景

setter注入和构造器注入是主要的注入方式;为了防止注入异常,Spring IOC容器还提供了对特定依赖的检查。

另一方面,在应用管理依赖关系时,可以通过IOC容器将控制进行反转,在反转的实现中,如果能通过可读的文本来完成配置,并且还能通过工具对这些配置信息进行可视化的管理和浏览,那么肯定是能够提高组件关系的管理水平,并且如果耦合关系需要变动,并不需要重新修改和编译Java源代码,这符合在面向对象设计中的开闭准则,并且能够提高对组件系统设计的灵活性,同时,如果结合OSGI的使用特性,还可以提高应用的动态部署能力。

IOC容器系列的设计与实现:BeanFactory和ApplicationContext

一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能;

一是ApplicationContext应用上下文,作为容器的高级形态而存在。

IOC容器的作用:

IOC容器是用来管理对象依赖关系的,对IOC容器来说,BeanDefinition就是对依赖反转模式中管理对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。

Spring IOC容器的设计:

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

  • 从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径。在这条接口设计路径中,BeanFactory接口定义了基本的IOC容器的规范。而HierarchicalBeanFactory接口在继承了BeanFactory的基本接口之后,增加了getParentBeanFactory()的接口功能,使BeanFactory具备了双亲IOC容器的管理功能。在接下来的ConfigurableBeanFactory接口中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲IOC容器,通过addBeanPostProcessor()配置Bean后置处理器,等等。通过这个接口设计的叠加,定义了BeanFactory就是简单IOC容器的基本功能。
  • 第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我们常用的WebApplicationContext或者ConfigurationApplicationContext或者WebApplicationContext的实现。在这个接口体系中,ListableBeanFactory和HierarchicalBeanFactory两个接口,连接BeanFactory接口定义和ApplicationContext上下文的接口定义。在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了getBeanDefinitionNames()接口方法;对于HierarchicalBeanFactory接口,我们在前文中已提到过;对于ApplicationContext接口,它通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IOC容器的基础上添加了许多对高级容器的特性支持。
  • 这里涉及的是主要接口关系,而具体的IOC容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory,这个基本IOC容器的实现就是实现了ConfigurableBeanFactory,从而成为一个简单IOC容器的实现。像其他IOC容器,比如XMLBeanFactory,都是在DefaultListableBeanFactory的基础上做扩展,同样地,ApplicationContext的实现也是如此
  • 这个接口系统是以BeanFactory和ApplicationContext为核心的。而BeanFactory又是IOC容器的最基本的接口,在ApplicationContext的设计中,一方面,可以看到它继承了BeanFactory接口体系中的ListableBeanFactory、AutowireCapbleBeanFactory、HierarchicalBeanFactory等BeanFactory的接口,具体BeanFactory IOC容器的基本功能;另一方面,通过继承MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,BeanFactory为ApplicationContext赋予了更高级的IOC容器特性。对于ApplicationContext而言,在web环境中使用它,还设计了WebApplicationContext接口,而这个接口通过继承ThemeSource接口来扩充功能。

1.BeanFactory的应用场景

        用户使用容器时,可以使用转义符“&”来得到FactoryBean本身,用来区分通过容器来获取FactoryBean产生的对象和获取FactoryBean产生的对象和和获取FactoryBean本身。举例来说,如果myJndiObject是一个FactoryBean,那么使用&myJndiObject得到的是FactoryBean,而不是myJndiObject这个FactoryBean产生出来的对象。

注意:FactoryBean和BeanFactory,一个Factory就是IOC容器或对象工厂,一个是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式相似。

BeanFactory接口设计了getBean方法,这个方法是使用IOC容器API的主要方法,通过这个方法,可以取得IOC容器中管理的Bean,Bean的取得是通过指定名字来索引的。如果需要在获取Bean时对Bean的类型进行检查,BeanFactory接口定义了带有参数的getBean的方法,这个方法的使用与不带参数的getBean方法类似,不同的是增加了对Bean检索的类型的要求。

用户可以通过BeanFacory接口方法中的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类型。这个类型可以由用户来指定。
  • 通过接口方法getType来查询指定名字的Bean的Class类型。
  • 通过接口方法getAliases来查询指定了名字的Bean的所有别名,这些别名都是用户在BeanDefinition中定义。

2.BeanFactory容器的设计原理

BeanFactory接口提供了使用IOC容器的规范。在这个基础上,Spring还提供了符合这个IOC容器接口的一系列容器的实现供开发人员使用。我们以XmlBeanFactory的实现为例来说明简单IOC容器的设计原理。

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

在使用IOC容器时,需要如下几个步骤:

  1. 创建IOC配置文件的抽象资料,这个抽象资料包含了BeanDefinition的定义信息。
  2. 创建一个BeanFactory,这里使用DefaultListableBeanFactory。
  3. 创建一个载入BeanDefinition的读取器,这里使用XMLBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。
  4. 从定义好的资源位置读取配置信息,具体的解析过程由XMLBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IOC容器就建立起来了。这个时候就可以直接使用IOC容器了。

3.ApplicationContext的应用场景

  • 支持不同的信息源。ApplicationContext扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务。
  • 访问资源。这一特性体现在对ResourceLoader和Resource的支持上,这样我们可以从不同地方得到Bean定义资源。这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的I/O途径得到的Bean定义信息。这在接口关系上看不出来,不过一般来说,具体ApplicationContext都是继承了DefaultResourceLoader的子类。因为说,具体ApplicationContext都是继承了DefaultResourceLoader的子类。因为DefaultResourceLoader是AbstractApplicationContext的基类。
  • 支持应用事件。继承接口ApplicationEventPublisher,从而在上下文中引入了事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
  • 在ApplicationContext中提供附加服务。这些服务使得基本IOC容器的功能更丰富。因为具备了这些丰富的附加功能,使得ApplicationContext与简单的BeanFactory相比,对它的使用是一种面向框架的使用风格,所以一般建议在开发应用时使用ApplicationContext作为IOC容器的基本形式。

4.ApplicationContext容器的设计原理

在ApplicationContext容器中,我们以常用的FileSystemXMLApplicationContext的实现为例来说明ApplicationContext容器的设计原理。

在FileSystemXMLApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXMLApplicationContext的基类AbstractXmlApplicationContext中实现了,在FileSystemXMLApplicationContext中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。

一个功能是,如果应用直接使用FileSystemXMLApplicationContext,对于实例化这个应用上下文的支持,同时启动IOC容器的refresh()过程。

这个refresh()过程会牵涉IOC容器启动的一系列复杂操作,同时,对于不同容器实现,这些操作都是类似的,因此在基类中将它们封装好。所以,我们在FileSystemXMLApplicationContext的设计中看到的只是一个简单的调用。

另一个功能是与FileSystemXMLApplicationContext设计具体相关的功能,这部分与怎样从文件系统中加载XML的Bean定义资源有关。

IOC容器的初始化过程

IOC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IOC容器的正式启动。这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程。

第一个过程是Resource定位过程。这个Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。

第二个过程是BeanDefinition的载入。这个载入过程是把用户定义好的Bean表示成IOC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。具体来说,这个BeanDefinition实际上就是POJO对象在IOC容器中的抽象,通过这个BeanDefinition定义的数据结构,使IOC容器能够方便地对POJO也就是Bean进行管理。

第三个过程是向IOC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegisty接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册。通过分析,在IOC容器内部将BeanDefinition注入到一个HashMap中去,IOC容器就是通过HashMap来持有这些BeanDefinition数据的。

在这个过程中,一般不包含Bean依赖注入的实现。在Spring IOC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。如果我们对某个Bean设置Lazyinit属性,那么这个Bean的依赖注入在IOC容器初始化时预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才会触发。

BeanResource的Resource定位

以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,这意味着Spring会在类路径中寻找以文件形式存在的BeanDefinition信息。

ClassPathResource res = new ClassPathResource("bean.xml");

这里定义的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在这里,我们也可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IOC容器,需要为它配置特定的读取器才能完成这些功能。当然,有利就有弊,使用DefaultListableBeanFactory这种更底层的容器,能提高定制IOC容器的灵活性。

FIleSystemXMLApplicationContext的继承体系:

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

BeanDefinition的载入和解析

在完成对代表BeanDefinition的Resource定位的分析后,下面来了解整个BeanDefinition信息的载入过程。对IOC容器来说,在这个载入过程,相当于把定义的BeanDefinition在IOC容器中转换成一个Spring内部表示的数据结构的过程。IOC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IOC容器中通过一个HashMap来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高IOC容器的性能和容量,完全可以自己做一些扩展。

进入到AbstractRefreshableApplicationContext的refreshBeanFactory()方法中,在这个方法中创建了BeanFactory。在创建IOC容器前,如果已经有了容器的存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立起来的IOC容器。这么看来,这个refresh非常像重新启动器,就像重启计算机那样。在建立好当前的IOC容器以后,开始对容器的初始化过程,比如BeanDefinition的载入。

可以从AbstractRefreshableApplicationContext的refreshBeanFactory方法开始。

调用的loadBeanDefinitions实际上是一个抽象方法,那么实际的载入过程发生在哪里呢?在这个loadBeanDefinition中,初始化了读取器XmlBeanDefinitionReader,然后把这个读取器在IOC容器中设置好,最后是启动读取器来完成BeanDefinition在IOC容器中的载入。

接着就是loadBeanDefinition调用的地方,首先得到BeanDefinition信息的Resource的定位,然后直接调用XMLBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XMLBeanDefinitionReader来载入BeanDefinition到容器中。

在初始化FileSystemXMLApplicationContext的过程中是通过调用IOC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XmlBeanDefinitionReader来完成的。同时,我们也知道实际使用IOC容器是DefaultListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现。因为Spring可以对应不同形式的BeanDefinition方式,就需要使用其他种类的BeanDefinition。由于这里使用的是XML方式的定义,所以需要使用XMLBeanDefinitionReader。如果使用了其他的BeanDefinition方式,就需要使用其他种类的BeanDefinitionReader来完成数据的载入工作。在XmlBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinitions中开始进行BeanDefinition的载入的,而这时XMLBeanDefinitionReader的父类AbstractBeanDefinitionReader已经为BeanDefinition的载入做好了准备。

BeanDefinition的载入分为两部分,首先通过调用XML的解析器得到document对象,但这些document对象并没有按照Spring的Bean规则进行解析。在完成通用的XML解析以后,才是按照Spring的Bean规则进行解析的地方,这个按照Spring的Bean规则进行解析的过程是在documentReader中实现的。这里使用的documentReader是默认设置好的DefaultBeanDefinitionDocumentReader。这个DefaultBeanDefinitionDocumentReader的创建在后面的方法中完成的,然后再完成BeanDefinition的处理,处理的结果由BeanDefinitionHolder对象来持有。这个BeanDefinitionHolder除了持有BeanDefinition对象外,还持有其他与BeanDefinition的使用的相关的信息,比如Bean的名字、别名集合等。这个BeanDefinitionHolder的生成是通过对Document文档树的内容进行解析来完成的,可以看到这个解析过程是由BeanDefinitionParaserDelegate来实现(具体在processBeanDefinition方法中实现)的,同时这个解析是与Spring对BeanDefinition的配置规则紧密相关的。

具体的Spring BeanDefinition的解析是在BeanDefinitionParaserDelegate中完成的。这个类里包含了对各种Spring Bean定义规则的处理。在这里我们会看到对那些熟悉的BeanDefinition定义的处理,比如id、name、aliase等属性元素。把这些元素的值从XML文件相应的元素的属性中读取出来以后,设置到生成的BeanDefinitionHolder中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种Bean的属性配置,通过一个较为复杂的解析过程,这个过程是由parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到BeanDefinition对象中并设置到BeanDefinitionHolder中。

在IOC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IOC容器中的抽象,这些数据结构可以以AbstractBeanDefinition为入口,让IOC容器执行索引、查询和操作。简单的POJO操作背后其实蕴含着一个复杂的抽象过程,经过以上的载入过程,IOC容器大致完成了管理Bean对象的数据准备工作(或者说初始化过程)。重要的依赖注入实际上在这个时候还没有发生,现在,在IOC容器BeanDefinition中存在的还只是一些静态的配置信息。严格地说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需要完成数据向容器的注册。

BeanDefinition在IOC容器中的注册

在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory中可以看到。

将解析得到的BeanDefinition向IOC容器中的BeanDefinitionMap注册的过程是在载入BeanDefinition完成后进行的,注册的调用过程如下:

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

在DefaultListableBeanFactory中实现了BeanDefinitionRegistry的接口,这个接口的实现完成BeanDefinition向容器的注册。这个注册过程不复杂,就是把解析得到的BeanDefinition设置到HashMap中去。需要注意的是,如果遇到同名的BeanDefinition,进行处理的时候需要依据allowBeanDefinitionOverriding的配置来完成的。

完成了BeanDefinition的注册,就完成了IOC容器的初始化过程。此时,在使用IOC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,它们都在BeanDefinitionMap里检索和使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础,有了这些基础数据,,下面看一下在IOC容器,依赖注入是怎样完成的。

IOC容器的依赖注入

假设当前IOC容器已经载入了用户定义的Bean信息,开始分析依赖注入的原理。首先,注意到依赖注入的过程是用户第一次向IOC容器索要Bean时触发,我们可以在BeanDefinition信息中通过控制lazy-init属性来让容器完成对Bean的预实例化。这个预实例化实际上也是一个完成依赖注入的过程,但它是在初始化的过程完成的。当用户向IOC容器索要Bean时,有一个getBean的接口定义,这个接口的实现就是触发依赖注入发生的地方。

Spring IOC容器作为一个产品,其价值体现用依赖反转提供便利,从而实现了一个完整的IOC容器产品。这些产品特性的实现并不是一个简单的过程,它提供了一个成熟的IOC容器产品提供用户使用。

getBean是依赖注入的起点,之后会调用createBean,下面通过createBean代码来了解这个实现过程。在这个过程中,Bean对象会依据BeanDefinition定义的要求生成。在AbstractAutowireCapableBeanFactory中实现这个createBean,createBean不但生成了需要的Bean,还对Bean初始化进行了处理,比如实现了在BeanDefinition中的init-method属性定义,Bean后置处理器等。

依赖注入的过程

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

在createBeanInstance中生成了Bean所包含的Java对象,这个对象的生成有很多不同的方式,可以通过工厂方法生成,也可以通过容器的autowire特性生成,这些生成方式都是由相关的BeanDefinition来制定的。

这里用CGLIB对Bean进行实例化。CGLIB是一个常用的字节码生成器的类库,它提供了一系列的API来提供生成和转换Java的字节码的功能。在Spring AOP中也使用CGLIB对Java的字节码进行增强。在IOC容器中,要了解怎样使用CGLIB来生成Bean对象,需要看一下SimpleInstantiationStrategy类。这个Strategy是Spring用来生成Bean对象的默认类,它提供了两种实例化Java对象的方法,一种是通过BeanUtils,它使用了JVM的反射功能,一种通过前面提到的CGLIB来生成。

在cglibSubclassingInstantiationStrategy中可以看到具体的实例化过程的和CGLIB的使用方法。这里的Enhancer类,已经是CGLIB的类了,通过这个Enhancer生成Java对象,使用的是Enhancer的create方法。

Bean对象生成后,如何把这些Bean对象的依赖关系设置好,完成整个依赖注入过程。这个过程涉及对各种Bean对象的属性的处理过程(即依赖关系处理的过程),这些依赖关系处理的依据就是已经解析得到的BeanDefinition。

容器其他相关特性的设计与实现

ApplicationContext和Bean的初始化及销毁

对于BeanFactory,特别是ApplicationContext,容器自身也有一个初始化和销毁关闭的过程。

容器初始化和关闭过程

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

在应用开发中,常常需要执行一些特定的初始化工作,这些工作都是相对比较固定的,比如建立数据库连接,打开网络连接等,同时,在结束服务时,也有一些相对固定的销毁工作需要执行。为了便于这些工作的设计,Spring IOC容器提供了相关的功能,可以让应用定制Bean的初始化和销毁过程。

容器的实现是通过IOC管理Bean的生命周期来实现的。Spring IOC容器在对Bean的生命周期进行管理时提供了Bean生命周期各个时间点的回调。在分析Bean初始化和销毁过程的设计之前,简要介绍一下IOC容器中的Bean的生命周期。

  • Bean实例的创建
  • 为Bean实例创建属性
  • 调用Bean的初始化方法
  • 应用可以通过IOC容器使用Bean
  • 当容器关闭时,调用Bean的销毁方法

在调用Bean的初始化方法时,会调用一系列的aware的接口实现,把相关的BeanName、BeanClassLoader、以及BeanFactory注入到Bean中去。接着会看到对invokeInitMethods的调用,这时还会看到启动afterPropertiesSet的过程,当然,这需要Bean实现InitializingBean的接口,对应的初始化处理可以在InitializingBean接口的afterPropertiesSet方法中实现,这里是对Bean的一个回调。

最后,还会看到判断Bean是否配置有initMethod,如果有,那么通过invokeCustomInitMethod方法直接调用,最终完成Bean的初始化。

在这个对initMethod的调用中,可以看到首先需要得到Bean定义的initMethod,然后通过JDK的反射机制得到Method对象,直接调用在Bean定义中声明的初始化方法。

lazy-init属性和预实例化

在IOC容器的初始化过程中,主要的工作是对BeanDefinition的定位、载入、解析和注册。此时依赖注入并没有发生,依赖注入发生在应用第一次向容器索要Bean时。向容器索要Bean是通过getBean的调用来完成的,该getBean是容器提供Bean服务的最基本的接口。在前面的分析中也提到,对于容器的初始化,也是一种例外情况,就是用户可以通过设置Bean的lazy-init属性来控制预实例化的过程。这个预实例化在初始化容器时完成Bean的依赖注入。这种容器的使用方式会对容器的初始化性能有一些影响,但却能够提高应用第一次获取Bean的性能。因为应用第一次取得Bean时,依赖注入已经结束,应用可能取得已有的Bean。

从lazy-init属性配置实现的角度进行分析。对这个属性的处理也是容器refresh的一部分。在finishBeanFactoryInitialization的方法中,封装了对lazy-init属性的处理,实际的处理是在DefaultListableBeanFactory这个基本容器的preInstantiateSingletons方法中完成的。该方法对单件Bean完成预实例化,这个预实例化的完成巧妙地委托容器来实现。如果需要预实例化,那么就直接在这里采用getBean去触发依赖注入,与正常依赖注入的触发相比,只有触发的时间和场合不同。在这里,依赖注入发生在容器执行refresh的过程中,也就是发生在IOC容器初始化的过程中,而不像一般的依赖注入一样发生在IOC容器初始化完成以后,第一次向容器执行getBean时。

可以通过lazy-init属性对整个IOC容器的初始化和依赖注入过程进行一些简单的控制。这些控制是可以由容器的使用者来决定的,具体来说,可以通过在BeanDefinition中设置lazy-init属性来进行控制。

FactoryBean的实现

FactoryBean机制提供了一个很好的封装机制,比如封装Proxy、RMI、JNDI等。这个方法就是主要的FactoryBean的接口,需要实现特定的工厂的生产过程,至于这个生产过程是怎样和IOC容器整合的,就是上面分析的内容。

工厂模式

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

对比两者的实现,可以看到FactoryBean类似于AbstractFactory抽象工厂,getObjectForBeanInstance()方法类似createProductA()这样的生产接口,而具体的FactoryBean实现,如TransactionProxyFactoryBean,就是具体的工厂实现,其生成出的TransactionProxy就是“抽象工厂”模式中对应的ConcreteProduct。有了抽象工厂设计模式的参考和对比,对FactoryBean的设计和实现就更容易理解一些。

BeanPostProcessor的实现

BeanPostProcessor是使用IOC容器时经常会遇到的一个特性,这个Bean的后置处理器是一个监听器,它可以监听容器触发的事件。将它向IOC容器注册后,容器中管理的Bean具备了接收IOC容器事件回调的能力。BeanPostProcessor的使用非常简单,只需要通过设计一个具体的后置处理起来实现。同时,这个具体的后置处理器需要实现接口类BeanPostProcessor,然后设置到XML的Bean配置文件中。这个BeanPostProcessor是一个接口类,它有两个接口方法,一个是postProcessBeforeInitialization,在Bean的初始化前提提供回调入口;一个是postProcessAfterInitialization,在Bean的初始化后提供回调入口,这两个回调的触发都是和容器管理Bean的生命周期相关的。这两个回调方法的参数都是一样的,分别是Bean的实例化对象和Bean的名字。BeanPostProcessor为具体的处理提供基本的回调输入。

以getBean方法为起始的调用过程

《Spring技术内幕:深入解析Spring架构与设计原理》笔记之二(Spring的IOC实现)

postProcessBeforeInitialization是在populateBean完成之后被调用。从BeanPostProcessor中的一个回调接口入手,对另一个回调接口postProcessAfterInitialization方法的调用,实际上也是在同一个地方封装完成的,这个地方就是populateBean方法中的initialization调用。在前面对IOC的依赖注入进行分析时,对这个populateBean有过分析,这个方法实际上完成了Bean的依赖注入。在容器中建立Bean的依赖关系,是容器功能实现的一个重要的部分。

在initializationBean方法中,需要使用Bean的名字,完成依赖注入以后的Bean对象,以及这个Bean对应的BeanDefinition。在这些输入的帮助下,完成Bean的初始化工作,这些工作包括类型是BeanNameAware的Bean设置Bean的名字,类型是BeanClassLoaderAware的Bean设置类装载器,类型是BeanFactoryAware的Bean设置自身所在的IOC容器以供回调使用,当然,还有对postProcessBeforeInitialization/postProcessAfterInitialization的回调和初始化属性init-method的处理等。经过这一系列的初始化处理之后,得到的结果就是可以正常使用的IOC容器托管的Bean了。

autowiring(自动依赖装配)的实现

一直是通过BeanDefinition的属性值和构造函数以显示的方式对Bean的依赖关系进行管理的。在Spring中,相对这种显示的依赖管理方式,IOC容器还提供了自动依赖装配的方式,为应用使用容器提供更大的方便。在自动装配中,不需要对Bean属性做显示的依赖关系的声明,只需要配置好autowiring属性,IOC容器会根据这个属性的配置,使用反射自动查找属性的类型或者名字,然后基于属性的类型或者名字来自动匹配IOC容器的Bean,从而自动的完成依赖注入。

从autowiring使用上可以知道,这个autowiring属性在对Bean属性进行依赖注入的时起作用。对autowiring属性进行处理,从而完成对Bean属性的自动依赖装配,是在populateBean中实现的。如果当前的Bean配置了auto_by_name和auto_by_type属性,那么调用相应的autowireByName方法和autowireByType方法。这两个方法很巧妙的应用了IOC容器的特性。对于auto_by_name,它首先通过反射机制从当前Bean中得到需要注入的属性名,然后使用这个属性向容器申请与之同名的bean,这样实际又触发了另一个Bean的生成和依赖注入的过程。

Bean的依赖检查

在一般情况下,Bean的依赖注入是在应用第一次向容器索取Bean的时候发生,在这个时候,不能保证注入一定能成功,如果需要重新检查这些依赖关系的有效性,会是一件很繁琐的事情。为了解决这样的问题,在Spring IOC容器中,设计了一个依赖检查特性,通过它,Spring可以帮助应用检查是否所有的属性都已经被正确设置。在具体使用的时候,应用只需要在Bean定义中设置dependency-check属性来指定依赖检查模式即可,这里可以将属性设置为none、simple、object、all四种模式,默认的模式是none。如果对检查模式进行了设置,通过下面的分析,可以更好地理解这个特性的使用。具体的实现代码是在AbstractAutowireCapableBeanFactory实现createBean的过程中完成的。在这个过程中,会对Bean的Dependencies属性进行检查,如果发现不满足要求,就会抛出异常通知应用。

Bean对IOC容器的感知

容器管理的Bean一般不需要了解容器的状态和直接使用容器,但在某些情况下,是需要在Bean中直接对IOC容器进行操作的,这时候,就需要在Bean中设计对容器的感知。Spring IOC容器也提供了该功能,它是通过特定的aware接口来完成的。aware接口有以下这些:

  • BeanNameAware,可以在Bean中得到它在IOC容器中的Bean实例名称。
  • BeanFactoryAware,可以在Bean中得到Bean所在的IOC容器,从而直接在Bean中使用IOC容器的服务
  • ApplicationContextAware,可以在Bean中得到Bean所在的应用上下文,从而直接在Bean中使用应用上下文的服务
  • MessageSourceAware,在Bean中可以得到消息源
  • ApplicationEventPublisherAware,在Bean中可以得到应用上下文的事件发布器,从而可以在Bean中发布应用上下文的事件
  • ResourceLoaderAware,在Bean中可以得到ResourceLoader,从而在Bean中使用ResourceLoader加载外部对应的Resource资源

在设置Bean的属性之后,调用初始化回调方法之前,Spring会调用Aware接口中的setter方法。以ApplicationContextAware为例,分析对应的设计和实现。这个接口定义得很简单。

这里只有一个方法setApplicationContext(ApplicationContext applicaitonContext),它是一个回调函数,在Bean中通过实现这个函数,可以在容器回调该aware接口方法时使注入的applicationContext引用在Bean保存下来,供Bean需要使用ApplicationContext的基本服务时使用。这个对setApplicationContext方法的回调是由容器自动完成的。可以看到,一个ApplicationContextAwareProcessor作为BeanPostProcessor的实现,对一系列的aware回调进行了调用,比如对ResourceLoaderAware接口的调用,对ApplicationEventPublisherAware接口的调用,以及对MessageSourceAware和ApplicationContextAware的接口调用等。

而作为依赖注入的一部分,postProcessBeforeInitialization会在initializeBean的实现过程中被调用,从而实现对aware接口的相关注入。

小结:

关于容器的基本工作原理,可以大致整理以下几个方面:

  • BeanDefinition的定位。对IOC容器来说,它为管理POJO之间的依赖关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。我们可以使用各种形式的Bean定义信息,其中比较熟悉和常用的使用XML的文件格式。在Bean定义方面,Spring为用户提供了很大的灵活性。在初始化IOC容器的过程中,首先需要定位到这些有效的Bean定义信息,这里Spring使用Resource接口来统一这些Bean定义信息,而这个定位由ResourceLoader来完成。如果使用上下文,ApplicationContext本身就为客户提供了定位的功能。因为上下文本身就是DefaultResourceLoader的子类。如果使用基本的BeanFactory作为IOC容器,客户需要做的额外工作就是BeanFactory指定相应的Resource来完成Bean信息的定位。
  • 容器的初始化。在使用上下文时,需要一个对它进行初始化的过程,完成初始化以后,这个IOC容器才是可用的。这个过程的入口是在refresh中实现的,这个refresh相当于容器的初始化函数。在初始化过程中,比较重要的部分是对BeanDefinition信息的载入和注册工作。相当于在IOC容器中需要建立一个BeanDefinition定义的数据映像,Spring为了达到载入的灵活性,把载入的功能从IOC容器中分离出来,由BeanDefinitionReader来完成Bean定义信息的读取、解析和IOC容器内部BeanDefinition的建立。在DefaultListableBeanFactory中,这些BeanDefinition被维护在一个HashMap中,以后的IOC容器对Bean的管理和操作就是通过这些BeanDefinition来完成的。