Spring Ioc 容器

概述

IoC容器和依赖反转模式

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

依赖控制反转的实现有很多种方式,在Spring 中,IoC容器是实现这个模式的载体,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域的方式来注入对方法调用的依赖,这种依赖注入是可以递归的,对象被逐层注入。

通过IoC容器,对象依赖关系的管理被反转了,转到IoC容器中来了,对象之间的依赖关系由IoC容器进行管理,并由IoC容器完成对象的注入,这样就很大程度上简化了开发。把应用从复杂的对象依赖关系管理中解放出来。

IOC设计实现

springIOC设计中,有两个主要的容器系列,一个是实现BeanFactory 接口的简单容器系列,他们只实现了容器的基本功能,另一个ApplicationContext应用上下文,作为容器的高级形态而存在,它在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配。

作为IOC容器,需要为他的具体实现指定基本的功能规范,这个功能规范的设计表现为接口类BeanFactory,它体现了Spring为提供给用户使用的Ioc容器所设定的最基本的功能规范。也就说说,一个IoC容器最基本的就是要实现BeanFactory中定义的接口。

同时,Spring还通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系,BeanDefinition抽象了我们对于Bean的定义,是让容器起作用的主要数据类型。IoC容器是用来管理对象依赖关系的,对于IOC容器来说,BeanDefinition 就是对依赖反转模式中管理的对象依赖关系的数据抽象。也是实现依赖反转功能的核心数据结构。

下图是IoC容器中的主要接口设计

BeanfactoryHierarchicalBeanFactoryConfigurableBeanFacotry ,是一条BeanFactory的设计路径,其中,BeanFactory接口定义了基本的Ioc容器的规范,比如getBean()这样的Ioc容器的基本方法,HierarchicalBeanFactory在原来的接口基础上,增加了getparentBeanFactory()的接口功能,使BeanFactory具备了双亲IoC容器的管理功能。在ConfigurableBeanFacotry中,主要定义了一些对Beanfactory的配置功能。

还有一条主线是以ApplicationContext应用上下文接口为核心的接口设计。这里的主要接口设计有从BeanFactoryListableBeanFatory,再到ApplicationContext,再到我们常用的WebApplicationContext或者ConfigurableApplicationContext接口。我们常用的应用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的实现。

Beanfactory的应用场景

BeanFactory提供的是最基本的Ioc容器的功能,这些功能定义,我们可以在接口BeanFactory中看到。Beanfactory接口设计了getBean方法,这个方法是使用Ioc容器API的主要方法,通过这个方法,可以取得Ioc容器中管理的Bean.Bean 的取得是通过指定名字来索引的。下面是BeanFactory 接口中的方法。

这里定义的只是一系列的接口方法,通过不同的Bean检索方法,可以方便的从容器中取出需要的Bean。这些检索方法代表的是最为基本的容器入口。

BeanFactory容器设计原理

XMLBeanFactory为例,简单说明Ioc容器的设计原理。
在spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的Ioc容器来使用的,以XMLBeanFactory继承了DefaultListableBeanFactory容器的功能的同时,增加了新的功能,也就是它是一个可以读取以XML文件方式定义的BeanDefinition的Ioc容器。对这些XML方式定义的信息的处理并不是由XmlBeanFactory直接完成的,而是初始化了一个XmlBeanDefinitionReader对象,有了这个Reader对象,那些以XML方式定义的BeanDefinition就有了处理的地方。可以看到,对这些XML形式的信息的处理实际上是由这个XmlBeanDefinitionReader来完成的。
构造XMLBeanFactory这个容器的时候,需要指定BeanDefinition的信息来源,而这个消息来源需要封装成Spring中的Resource类来给出。Resource是Spring用来封装IO操作的类。
XMLBeanFactory的功能是建立在DefaultListableBeanFacory 这个基本容器的基础上的,并在这个容器的基础上实现了其他诸如XML读取的附加功能。

下面是编程方式使用DeaultListableBeanFactory的代码,从中可以看到IoC容器使用的一些基本过程。

1
2
3
4
ClassPathResource res =new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory=new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

ApplicationContext应用场景

ApplicationContext是一个高级形态意义的IoC容器。提供了以下新特性:

  • 支持不同的信息源(扩展了MessageSource接口,可以支持国际化)
  • 访问资源
  • 支持应用事件
  • 提供了附加服务

IOC容器的初始化过程

Ioc容器的初始化是由refresh方法来启动。这个方法标志着Ioc正式启动。具体来说,这个过程包括了BeanDefinition的Resource定位,载入和注册三个基本过程。

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

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

1
ClassPathResource res =new ClassPathResource("beans.xml");

这里定义的Resource并不能直接的使用。Spring通过BeanDefinitionReader来对这些信息进行处理。以FileSystemApplicationContext为例,对BeanDefinition资源定位的过程,最初是由refresh触发的,这个refresh()的调用是在FileSystemApplicationContext的构造函数中启动的。

定位过程完成后,就为BeanDefinition的载入创造了IO操作的条件,但是具体的数据还没有开始读入。相当于用水桶去打水,这时候水源就已经找到了。

第二个过程就是BeanDefinition的载入与解析。

对Ioc容器来说,这个载入过程,相当于把定义的BeanDefinition在Ioc容器中转化为一个Spring内部表示的数据结构的过程。Ioc容器对Bean的管理和依赖,都是通过对其持有的BeanDefinition进行各种相关操作完成的。这些BeanDefinition数据在Ioc容器中通过一个HashMap来保持和维护。

首先,先回到IoC容器的初始化入口,也就是看下Ioc容器的初始化方法refresh()。它的调用标志着容器初始化的开始。这些初始化兑现就是BeanDefinition数据。

对容器的启动来说,refresh 是一个很重要的方法,

Refresh 非常像重启动容器,就像重启动计算机那样,在建立好当前的IoC容器以后,开始了对容器的初始化过程,比如BeanDefinition的载入。

BeanDefinition 的载入分为两部分,首先通过调用XML的解析器得到document对象,但这些document对象并没有按照Spring 的bean规则进行解析,在完成通用的XML解析以后,才是按照Spring 的Bean规则进行解析的地方,这个按照Spring的Bean规则进行解析的过程是在documentReader 中实现的。具体的Spring BeanDefinition的解析是在BeanDifinitionParserDelegate中完成的,这个类里包含了对各种Spring Bean 定义规则的处理。

第三个过程就是BeanDefinition在Ioc容器中的注册这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册,实际在内部,是通过将BeanDefinition注入到一个HashMap 中去。

载入解析完成后,用户定义的BeanDefinition信息已经在Ioc容器中建立起了自己的数据结构以及相应的数据表示。但是这些数据还不能供IOc容器直接使用,需要在IOc容器中对这些BeanDefinition数据进行注册。注册为IoC容器提供了更加友好的使用方式,在DefaultListableBeanFactory中,通过HashMap来持有载入的BeanDefinition的。注册过程实际上就是把解析到BeanDefinition设置到HashMap中去。

完成了BeanDefinition的注册,就完成了IOc容器的初始化过程。

IOC容器的依赖注入

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

在Ioc容器基本接口中,有一个getBean()的接口定义,这个接口的实现就是触发依赖注入的地方。getBean()方法最终是通过调用doGetBean来实现的

spring技术内幕55page

主要的逻辑就是先尝试从缓存中去取得Bean,若取到,则不需要重复的创建。然后对容器中的BeanDefinition是否存在进行检查,检查是否能在当前的BeanFactory中取得需要的Bean,如果当前的工厂取不到,就从双亲BeanFactory中去取。如果还取不到,就沿着链一直往上取。然后根据Bean的名字取得BeanDefinition。接着获取当前Bean的所有依赖Bean,这样会触发getBean的递归调用,直到取到一个没有任何依赖的Bean为止。最后对创建的Bean进行类型检查,如果没有问题,则返回这个新创建的Bean.

依赖注入的发生就是在容器中的BeanDefinition数据已经建立好的前提下进行的。重点来说,getBean()是依赖注入的起点,getBean()调用doGetBean,doGetBean调用createBean,createBean不仅生成了需要的Bean,还对Bean初始化进行了处理。Bean对象根据BeanDefinition要求的定义生成。

CreateBean中调用doCreateBean来生成需要的Bean.真正的创建Bean是由createBeanInstance来完成的。在createBeanInstance中,生成了Bean所包含的Java对象,这个对象的生成有多种不同的形式,可以通过工厂方法形成,也可以通过容器的autowired特性生成。Spring中生成对象的默认类是SimpleInstantiationStrategy类,它提供了两种实例化Java对象的方法,一种是通过BeanUtils,它使用了JVM的反射功能,一种是通过前面提到的CGLIB来生成。

通过前面的描述,实例化Bean就已经完成了,接下来就要处理这些Bean的依赖关系,完成整个依赖注入过程。这个过程涉及对各种Bean对象的属性的处理过程。这些依赖关系处理的依据就是已经解析到的BeanDefinition.

docreatBean中的poulateBean就是处理Bean之间依赖关系的类.

populateBean中对属性注入使用了applyPropertyValues() 这个方法。方法中主要通过 BeanDefinitionResolver 来对BeanDefinition进行解析,然后注入到Property 中。

在配置Bean的属性的时候,属性可能有多种类型,我们再进行注入的时候,不同的属性类型我们不可能一概而论的进行处理,集合类型的属性和非集合类型具备很大的差别,对不同的类型应该有不同的解析处理过程,故该方法流程中首先判断value的类型然后在分别调用 resolveManagedList()resolveManagedSet()resolveManagedMap()等方法进行具体的解析。

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

通过Bean的创建和对象依赖注入后,这个Bean已经不是简单的Java对象了,该Bean系列以及Bean之间的依赖关系建立好以后,通过IOC容器的相关接口方法,就可以非常方便的进行上层调用了。