Spring技术内幕(2)Spring FrameWork的核心:IoC容器的实现

本章内容:

Spring IoC容器概述、IoC容器系列的设计与实现:BeanFactory和ApplicationContext、IoC容器初始化过程、IoC容器的依赖注入、容器其他相关特性的设计与实现。

2.1 Spring IoC容器概述

2.1.1 IoC容器和依赖反转模式

在面向对象中,对象封装了数据和对数据处理,对象的依赖关系体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或IoC容器来完成,这样做可以解耦代码也可以提高代码的可测试性。依赖控制反转的实现方式很多,在Spring中IoC容器就是这个模式的载体(或实现),可它以在对象生成和初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖,这种依赖注入是可以递归的,对象被逐层注入。控制反转是关于一个对象如何获取他所依赖对象的引用,在这里是责任的反转

IoC容器进行对象关系的管理,并由IoC容器完成对象的注入

2.1.2 Spring IoC的应用场景

在Spring中,SpringIoC提供了基本的JavaBean容器,通过IoC模式管理以来关系,并通过依赖注入和AOP切面增强了为JavaBean这样的POJO对象赋予事务管理、生命周期管理等基本功能。具体的注入方式有:接口注入、构造器注入、setter注入

 

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

Spring IoC容器中有两个主要的容器系列:(1)BeanFactory:实现了容器的基本功能;(2)ApplicationContext:在Beanfactory基础上添加了功能,是容器的高级形式。

2.2.1 Spring IoC容器系列

Spring IoC容器系列概况:

BeanFactory和ApplicationContext都是IoC容器的具体表现形式。BeanFactory体现了Spring为用户使用IoC容器所设计的最基本的功能规范。在Spring提供的基本IoC容器接口定义和实现基础上,Spring通过BeanDifinition来管理基于Spring的应用中的各种对象以及他们之间的相互依赖关系,BeanDefinition抽象了对Bean的定义,是容器起作用的主要数据类型。

2.2.2 Spring 的IoC容器的设计

  • 从BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,这是一条主要的Beanfactory设计路径,该路径定义了基本的IoC容器的规范。
  • 第二条接口设计主线是以ApplicationContext为主的接口设计。
  • 这里主要涉及的是接口关心,具体的IoC实现都是在这个接口体系下实现的,例如DefaultListableBeanFactory,是一个简单的IoC容器的实现。像其他的IoC容器,例如XmlBeanFactory,都是在DefaultListableBeanFactory的基础上扩展的,同样ApplicationContext也是如此。

1、BeanFactory的应用场景

BeanFactory提供了最基本的IoC功能,这些可以在BeanFactory接口中看到。BeanFactory的具体实现类有:DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext都是BeanFactory基础上添加了相应的功能。

用户使用容器时,可以使用转义符“&”来获得FactoryBean本身,用来区分获取FactoryBean产生的对象和获取FactoryBean本身。

 

BeanFactory与FactoryBean区别

BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了 规范。
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(如果想了解装饰模式参考: 修饰者模式(装饰者模式,Decoration)  我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类。
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中, 所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的 。但对FactoryBean而言, 这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似 。

 2、BeanFactory容器的设计原理

以XmlBeanFactory的实现来讲解IoC容器的设计原理。XmlBeanFactory设计的类继承关系:

DefaultListableBeanFactory包含了基本IoC容器所具有的的重要功能,作为Spring中的默认的功能完整的IoC容器来使用。XmlBeanFactory继承DefaultListableBeanFactory,除了具备基本的功能之外,还具备读取以XML文件定义的BeanDefinition的能力。XML文件是由XmlBeanFactory初始化的XmlBeanDefinitionReader来处理以XML方式定义的BeanDefinition。构造XmlBeanFactory这个IoC容器需要Resource类给出BeanDefinition的信息来源。Resource是Spring用来封装I/O操作的类,例如ClassPathResource(“*.xml”)等。

XmlBeanFactory实现:

参考XmlBeanFactory的实现,以编程方式使用IoC容器:

  步骤:

1) 创建IoC配置文件的抽象资源,该象资源包含了BeanDefinition的定义信息。

2) 创建一个BeanFactory,这里使用DefaultListableBeanFactory。

3) 创建一个载入BeanDefinition的读入器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition,通过一个回调配置给BeanFactory。

4) 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这个时候就可以直接使用IoC容器了。

3、ApplicationContext的应用场景

ApplicationContext的接口关系:

 这些功能为ApplicationContext 提供了一些BeanFactory 所不具备的新特性:

1)支持不同的信息源。ApplicationContext 扩展了MessageResource 接口,这些信息源的功能可以支持国际化的实现。

2)访问资源。这一特性体验在Resource 与ResourceLoader 上,这样一来,我们可以从不同的地方得到Bean 定义资源。

3)支持应用事件。继承了接口ApplicationEventPublisher,从而在上下文中引入了事件机制。这些事件和Bean 的生命周期的结合为Bean 的管理提供了便利。

4)在ApplicationContext 中提供的附加服务。这些服务使得基本的IoC 容器的基本功能更加丰富。

 4、ApplicationContext 容器的设计原理

以FileSystemXmlApplicationContext 来说明ApplicationContext 容器的设计原理。

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

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

 

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

 

 2.3 IoC容器初始化过程

IoC 容器的初始化过程是通过refresh() 方法来启动的,该方法标识着IoC 容器正式启动。启动过程包括:BeanDefinition 的Resource 定位、载入和注册三个基本过程。Spring 把这三个过程分开,并使用不同的模块来完成,从而方便自己定义IoC 容器的初始化过程。

       1、BeanDefinition的Resource 的定位BeanDefinition 的资源定位,它由ResourceLoader 通过统一的Resource 接口来完成。对于BeanDefinition 的存在形式,可以是文件系统中的通过FileSystemResource 来进行抽象;类路径中定义的Bean 信息可以通过ClassPathResource 来进行抽象。这个定位过程类似于容器寻找数据的过程

       2、BeanDefinition 的载入载入过程是把用户定义好的Bean 定义成IoC 容器内部的数据结构BeanDefinition。BeanDefinition 实际上就是POJO 对象在IoC 容器中的抽象,通过这个BeanDefinition 定义的数据结构,使IoC 容器能够方便地管理Bean。

        3、BeanDefinition的注册。注册过程是通过BeanDefinitionRegistry 接口的实现来完成的。注册过程把载入过程中解析得到BeanDefinition 向IoC 容器进行注册。在IoC 容器内部将BeanDefinition 注入到一个HashMap 中去,IoC 容器就是通过这个HashMap 来持有这些Bean 数据的。

        以上IoC 容器的初始化过程,并不包含Bean 依赖注入的实现。在Spring IoC 的设计中,Bean 定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在第一个通过getBean() 向容器中获取Bean 的时候,也并不全是这样,例如某个Bean 设置了lazyinit 属性,那么Bean 的依赖注入在其初始化的过程中就已经完成了,而不需要等到整个容器初始化完成之后,第一次使用getBean()才会触发

2.3.1 BeanDefinition 的Resource 定位

以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。使用ClassPathResource,意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition信息 。ClassPathResource res = new ClassPathResource("beans.xml"); 

这里定义的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。回到常用的ApplicationContext上,例如:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext等。简单地从这些类的名字上分析,可以清楚地看到它们可以提供哪些不同的Resource读入功能。

以FileSystemXmlApplicationContext 为例,来分析ApplicationContext 的实现是如何完成Resource 定位的,下面是这个类对应的继承体系。

下面从源码角度,更为详细的继承体系:

从上图中可以看出FileSystemXmlApplicationContext 已经通过继承AbstractApplicationContext 具备了ResourceLoader 的功能,因为AbstractApplicationContext 继承自DefaultResourceLoader。下面是其源代码:

对BeanDefinition 资源定位的过程,是由refresh() 方法触发的,refresh() 方法的调用是在FileSystemXmlApplicationContext 的构造函数中启动的。下图可清楚的看出整个BeanDefinition资源定位的过程:

资源定位时序图如下:

在BeanDefinition 定位完成基础上,就可通过返回的Resource 对象进行BeanDefinition 载入。在完成定位过程后,为BeanDefinition 的载入创造了I/O 操作的条件,但具体的数据还没有开始读入,读入将在BeanDefinition的载入和解析中完成

 2.3.2 BeanDifinition的载入和解析

完成BeanDefinition的Resource定位的后,便可进行BeanDefinition信息的载入过程。该过程相当于把定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程IoC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护

从DefaultListableBeanFactory的设计入手来看IoC容器是怎样完成BeanDefinition载入的。先回到IoC容器的初始化入口refresh()方法。该方法最初是在FileSystemXmlApplicationContext的构造函数中被调用的,它的调用标志着容器初始化的开始,这些初始化对象就BeanDefinition数据,初始化入口函数:

IoC容器的refresh过程代码:

FileSystemXmlApplicationContext 是怎么完成信息的读入的呢?读入器的配置,可以在FileSystemXmlApplicationContext 的基类AbstractRefreshableApplicationContext 中的refreshBeanFactory() 方法的实现中查看。refreshBeanFactory() 方法被FileSystemXmlApplicationContext 构造函数中的refresh() 方法所调用。在该方法中,通过createBeanFactory() 创建一个DefaultListableBeanFactory 的IoC 容器供ApplicationContext 使用。同时,还启动了loadBeanDefinitions() 来载入BeanDefinition

AbstractRefreshableApplicationContext 中的refreshBeanFactory() :

 

AbstractXmlApplicationContext的loadBeanDefinitions():

XmlBeanDefinitionReader载入BeanDefinition(),代码清单如下:

AbstractBeanDefinitionReader载入BeanDefinition:

调用的loadBeanDefinitions(Resource res)方法在AbstractBeanDefinitionReader类里是没有实现的,具体的实现在XmlBeanDefinitionReader中。在读取器中,需要得到封装了对XML文件的I/O操作的代表XML文件的Resource,读取器可以在打开I/O流后得到XML的文件对象。有了这个文件对象以后,就可按照Spring的Bean定义规则来对这个XML的文档树进行解析了,该解析是交给BeanDefinitionParserDelegate来完成的,  对BeanDefinition的载入实现:

 

BeanDefinition载入时序图:

完成资源的载入后,需要将载入的BeanDefinition进行解析并转化为IoC容器内部数据结构,这个过程在registerBeanDefinitions()成,具体是由BeanDefinitionsDocumentReader来完成。

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

 创建BeanDefinitionDocumentReader:

具体的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate中完成的:


上面介绍了对Bean元素进行解析的过程,也就是BeanDefinition依据XML的定义被创建的过程。这个BeanDefinition可以看成是对定义的抽象。这个数据对象中封装的数据大多都是与定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记,比如常见的init-method、destroy-method、factory-method等。

beanClass、description、lazyInit这些属性都是在配置bean时经常碰到的,都集中在BeanDefinition。通过解析以后,便可对BeanDefinition元素进行处理,在这个过程中可以看到对Bean定义的相关处理,比如对元素attribute值的处理,对元素属性值的处理,对构造函数设置的处理,等等。对BeanDefinition定义元素的处理,代码如下:


上面是具体生成BeanDefinition的地方。在这里,我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类BeanDefinitionParserDelegate的代码中,一层一层地对BeanDefinition中的定义进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会被封装成PropertyValue对象并设置到BeanDefinition对象中去,对BeanDefinition中Property元素集合的处理。

2.3.3 BeanDefinition在IoC容器中的注册

BeanDefinition在IoC容器中载入和解析的过程完成以后,用户定义的BeanDefinition信息已经在IoC容器内建立起了自己的数据结构以及相应的数据表示,但此时这些数据还不能供IoC容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory中可以看到,如下所示。

 private final Map beanDefinitionMap = new  ConcurrentHashMap();

 将解析得到的BeanDefinition向IoC容器中的beanDefinitionMap注册的过程是在载入BeanDefinition完成后进行的,注册的调用过程如图所示:

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

 

完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。此时,在使用的IoC容器DefaultListableBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用了,它们都在beanDefinitionMap里被检索和使用。容器的作用就是对这些信息进行处理和维护

2.4 IoC容器的依赖注入

当前IoC容器已经载入了用户定义的Bean信息,用户第一次向IoC容器索要Bean时,即调用getBean()的接口定义,触发依赖注入。下面从DefaultListableBeanFactory的基类AbstractBeanFactory入手去看看getBean的实现, getBean触发的依赖注入,代码如下:


 

 

依赖注入的大致过程:

 

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


 

与依赖注入关系特别密切的方法有createBeanInstance()和populateBean()。在createBeanInstance()中生成了Bean所包含的Java对象,这个对象的生成有很多种不同的方式,可以通过工厂方法生成,也可以通过容器的autowire特性生成,这些生成方式都是由相关的BeanDefinition来指定的。 Bean包含的Java对象的生成,代码如下:

 

这里用CGLIB对Bean进行实例化,IoC容器中,要了解怎样使用cglib来生成Bean对象,需要看一下SimpleInstantiationStrategy类。这个Strategy是Spring用来生成Bean对象的默认类,它提供了两种实例化Java对象的方法,一种是通过BeanUtils,它使用了JDK的反射功能,一种是通过前面提到的CGLIB来生成, 使用SimpleInstantiationStrategy生成Java对象,代码如下:

以上分析了实例化Bean对象的整个过程。在实例化Bean对象生成的基础上,怎样把这些Bean对象的依赖关系设置好,完成整个依赖注入过程。这个过程涉及对各种Bean对象的属性的处理过程(即依赖关系处理的过程),这些依赖关系处理的依据就是已经解析得到的BeanDefinition。要详细了解这个过程,需要回到前面的populateBean()方法,这个方法在AbstractAutowireCapableBeanFactory中的populateBean()实现如代码如下:

 

 

 

 

这里通过使用BeanDefinitionResolver来对BeanDefinition进行解析然后注入到property中。下面到BeanDefinitionValueResolver中去看一下解析过程的实现,以对Bean reference进行解析为例具体的对Bean reference进行解析的过程如代码清单2-28所示:

 

这两种属性的注入都调用了resolveValueIfNecessary这个方法包含了所有对注入类型的处理。下面看一下resolveValueIfNecessary的实现,如代码清单如下:

 

 

 

 

在完成这个解析过程后,已经为依赖注入准备好了条件,这是真正把Bean对象设置到它所依赖的另一个Bean的属性中去的地方,其中处理的属性是各种各样的。依赖注入的发生是在BeanWrapper的setPropertyValues中实现的,具体的完成却是在BeanWrapper的子类BeanWrapperImpl中实现的,如代码清单如下:

 

 

 

 

这样就完成了对各种Bean属性的依赖注入过程。在Bean的创建和对象依赖注入的过程中,需要依据BeanDefinition中的信息来递归地完成依赖注入。从上面的几个递归过程中可以看到,这些递归都是以getBean为入口的。一个递归是在上下文体系中查找需要的Bean和创建Bean的递归调用;另一个递归是在依赖注入时,通过递归调用容器的getBean方法,得到当前Bean的依赖Bean,同时也触发对依赖Bean的创建和注入。在对Bean的属性进行依赖注入时,解析的过程也是一个递归的过程。这样,根据依赖关系,一层一层地完成Bean的创建和注入,直到最后完成当前Bean的创建。有了这个顶层Bean的创建和对它的属性依赖注入的完成,意味着和当前Bean相关的整个依赖链的注入也完成了。

在Bean创建和依赖注入完成以后,在IoC容器中建立起一系列依靠依赖关系联系起来的Bean,这个Bean已经不是简单的Java对象了。该Bean系列以及Bean之间的依赖关系建立完成以后,通过IoC容器的相关接口方法,就可以非常方便地供上层应用使用了。

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

2.5.1 ApplicationContext和Bean的初始化及销毁

对于BeanFactory,特别是ApplicationContext,容器自身也有一个初始化和销毁关闭的过程。下面详细看看在这两个过程中,应用上下文完成了什么,可以让我们更多地理解应用上下文的工作,容器初始化和关闭过程可以简要地通过下图表现:

从图中可以看到,对ApplicationContext启动的过程是在AbstractApplicationContext中实现的。在使用应用上下文时需要做一些准备工作,这些准备工作在prepareBeanFactory()方法中实现。在这个方法中,为容器配置了ClassLoader、PropertyEditor和BeanPostProcessor等,从而为容器的启动做好了必要的准备工作。

 

同样,在容器要关闭时,也需要完成一系列的工作,这些工作在doClose( )方法中完成。在这个方法中,先发出容器关闭的信号,然后将Bean逐个关闭,最后关闭容器自身。

 

以上是容器的初始化和销毁的设计与实现。在这个过程中需要区分Bean的初始化和销毁过程。Spring IoC容器提供了相关的功能,可以让应用定制Bean的初始化和销毁过程。容器的实现是通过IoC管理Bean的生命周期来实现的。Spring IoC容器在对Bean的生命周期进行管理时提供了Bean生命周期各个时间点的回调。在分析Bean初始化和销毁过程的设计之前,简要介绍一下IoC容器中的Bean生命周期:

Bean实例的创建->为Bean实例设置属性->调用Bean的初始化方法->通过IoC容器使用Bean ->容器关闭时调用Bean的销毁方法 

Bean的初始化方法调用是在以下的initializeBean方法中实现的:

 

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

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

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

与Bean初始化类似,当容器关闭时,可以看到对Bean销毁方法的调用。Bean销毁过程是这样的:

 

其中的destroy方法,对Bean进行销毁处理。最终在DisposableBeanAdapter类中可以看到destroy方法的实现。

 

 

这里可以看到对Bean的销毁过程,首先对postProcessBeforeDestruction进行调用,然后调用Bean的destroy方法,最后是对Bean的自定义销毁方法的调用,整个过程和前面的初始化过程很类似。

 2.5.2 lazy-init属性和预实例化

通过设置Bean的lazy-init属性来控制预实例化的过程,预实例化在初始化容器时完成Bean的依赖注入。这种容器的使用方式会对容器初始化的性能有一些影响,但却能够提高应用第一次取得Bean的性能。

在refresh()中的代码实现,可以看到预实例化是整个refresh初始化IoC容器的一个步骤。在finishBeanFactoryInitialization的方法中,封装了对lazy-init属性的处理,实际的处理是在DefaultListableBeanFactory这个基本容器的preInstantiateSingletons方法中完成的。该方法对单件Bean完成预实例化,这个预实例化的完成巧妙地委托给容器来实现。如果需要预实例化,那么就直接在这里采用getBean去触发依赖注入,与正常依赖注入的触发相比,只有触发的时间和场合不同。refresh()中的预实例化代码如下:

 

2.5.3 FactoryBean的实现

FactoryBean为应用生成需要的对象,这些对象往往是经过特殊处理的,如ProxyFactoryBean这样的特殊Bean。FactoryBean的生产特性是在getBean中起作用的,看下面的调用:

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 

getObjectForBeanInstance做了哪些处理?在getObjectForBeanInstance的实现方法中可以看到在FactoryBean中常见的getObject方法的接口,详细的实现过程如代码如下所示:

 

 

 2.5.4 BeanPostProcessor的实现

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

对于这些接口是在什么地方与IoC结合在一起的,可以查看一下以getBean方法为起始的调用关系,其调用过程如图所示:

 

postProcessBeforeInitialization是在populateBean完成之后被调用的。从BeanPostProcessor中的一个回调接口入手,对另一个回调接口postProcessAfterInitialization方法的调用,实际上也是在同一个地方封装完成的,这个地方就是populateBean方法中的initializeBean调用。在前面对IoC的依赖注入进行分析时,对这个populateBean有过分析,这个方法实际上完成了Bean的依赖注入。在容器中建立Bean的依赖关系,是容器功能实现的一个很重要的部分。节选doCreateBean中的代码就可以看到postProcessBeforeInitialization调用和populateBean调用的关系,如下所示。

 

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

 

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

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

autowiring属性是在对Bean属性进行依赖注入时起作用。对autowirng属性进行处理,从而完成对Bean属性的自动依赖装配,是在populateBean中实现的。对属性autowiring的处理是populateBean处理过程的一个部分。在populateBean的实现中,在处理一般的Bean之前,先对autowiring属性进行处理。如果当前的Bean配置了autowire_by_name和autowire_by_type属性,那么调用相应的autowireByName方法和autowireByType方法。例如,对于autowire_by_name,它首先通过反射机制从当前Bean中得到需要注入的属性名,然后使用这个属性名向容器申请与之同名的Bean,这样实际又触发了另一个Bean的生成和依赖注入的过程。populateBean对autowiring属性的处理,代码如下:

在对autowiring类型做了一些简单的逻辑判断以后,通过调用autowireByName和autowireByType来完成自动依赖装配。以autowireByName为例子来看看容器的自动依赖装配功能是怎样实现的。对autowireByName来说,它首先需要得到当前Bean的属性名,这些属性名已经在BeanWrapper和BeanDefinition中封装好了,然后是对这一系列属性名进行匹配的过程。在匹配的过程中,因为已经有了属性的名字,所以可以直接使用属性名作为Bean名字向容器索取Bean,这个getBean会触发当前Bean的依赖Bean的依赖注入,从而得到属性对应的依赖Bean。在执行完这个getBean后,把这个依赖Bean注入到当前Bean的属性中去,这样就完成了通过这个依赖属性名自动完成依赖注入的过程。autowireByType的实现和autowireByName的实现过程是非常类似的,感兴趣的读者可以自己进行分析。这些autowiring的实现如代码所示:

 

2.5.6   Bean的依赖检查 

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

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

 

而作为依赖注入的一部分,postProcessBeforeInitialization会在initializeBean的实现过程中被调用,从而实现对aware接口的相关注入。关于initializeBean的详细过程,感兴趣的读者可以参阅前面的章节进行回顾。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值