Spring IOC解析(1)---IOC容器介绍

1.1 IOC容器和依赖反转模式

  如果合作对象的引用或依赖关系的管理由具体对象来完成,会导致代码的高度藕合和可测试性的降低,这对复杂的面向对象系统的设计是非常不利的。在面向对象系统中,对象封装了数据和对数据的处理,对象的依赖关系常常体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或IoC容器来完成,这种从具体对象手中交出控制的做法是非常有价值的,它可以在解藕代码的同时提高代码的可测试性。
  依赖控制反转的实现有很多种方式。在Spring 中, IoC容器是实现这个模式的载体,它可以在对象生成或初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方能调用的依赖。这种依赖注入是可以递归的,对象被逐层注入。就此而言,这种方案有一种完整而简洁的美感,它把对象的依赖关系有序地建立起来,简化了对象依赖关系的管理,在很大程度上简化了面向对象系统的复杂性.关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是降低面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效的解决方案。
通过使用IoC容器,对象依赖关系的管理被反转了,转到IoC容器中来了,对象之间的相直依赖关系由IoC容器进行管理,井由Ioc容器完成对象的注入。这样就在很大程度上简化了应用的开发,把应用从复杂的对象依赖关系管理中解放出来。简单地说,因为很多对象依赖关系的建立和维护并不需要和系统运行状态有很强的关联性,所以可以把在面向对象编程中需要执行的诸如新建对象、为对象引用赋值等操作交由容器统一完成。这样一来,这些散落在不同代码中的功能相同的部分就集中成为容器的一部分,也就是成为面向对象系统的基础设施的一部分。
  对象之间的相互依赖关系也是比较稳定的,一般不会随着应用的运行状态的改变而改变。这些特性使这些对象非常适合由IoC容器来管理,虽然它们存在于应用系统中,但是应用系统并不承担管理这些对象的责任, 而是通过依赖反转把责任交给了容器(或者说平台)。了解了这些背景, Spring IoC容器的原理也就不难理解了。

1.2 IOC容器的设计与实现-BeanFactory和ApplicationContext

  在Spring IoC容器的设计中,我们可以看到两个主要的容器系列

  • 一个是实现BeanFactory接口的简单容器系列,这系列容器只实现了容器的最基本功能
  • 一个是ApplicationContext应用上下文,它作为容器的高级形态而存在。应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境作了许多适配。

1.2.1 Spring 的loC容器系列

  IoC容器为开发者管理对象之间的依赖关系提供了很多便利和基础服务。我们经常接触到的BeanFactory和ApplicationContext都可以看成是容器的具体表现形式。如果深入到lSpring的实现中去看,我们通常所说的IoC容器,实际上代表着一系列功能各异的容器产品,只是容器的功能有大有小.有各自的特点。
  Spring有各式各样的IoC容器的实现供用户选择和使用。使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那对容器的使用是非常有帮助的,就像我们在购买商品前对商品进行考察和挑选那样。从代码的角度入手,我可以看到关于这一系列容器的设计情况。
  作为IoC容器,也需要为它的具体实现指定基本的功能规范,这个功能规范的设计表现为接口类BeanFactory ,它体现了Spring为提供给用户使用的IoC容器所设定的最基本的功能规范。对Spring的具体IoC容器实现来说,它需要满足的基本特性是需要满足Bean Factory这个基本的接口定义,它是作为一个最基本的接口类出现在Spring的IoC容器体系中的。
  在这些Spring 提供的基本IoC容器的接口定义和实现的基础上, Spring 通过定义BeanDefinition来管理基于Spring 的应用中的各种对象以及它们之间的相互依赖关系。BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。我们都知道,在计算机世界里,所有的功能都是建立在通过数据对现实进行抽象的基础上的。IoC容器是用来管理对象依赖关系的,对IoC容器来说, BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。这些BeanDefinition就像是容器里装的水,有了这些基本数据,容器才能够发挥作用。

1.2.2 Spring IoC容器的设计

下图是IoC容器的接口设计图
这里写图片描述
下面对接口关系做一些简要的分析:

  • 从接口BeanFactory歪lj HierarchicalBeanFactory ,再至ConfigurableBeanFactory ,是一条主要的BeanFactory设计路径。在这条接口设计路径中, BeanFactory接口定义了基本的Ioc容器的规范。在这个接口定义中,包括了getBean() 这样的IoC容器的基本方法(通过这个方站可以从容器中取得Bean)。而HierarchicalBeanFactory接口在继承了BeanFactory 的基本接口之后,增加了getParentBeanFactory () 的接口功能,使BeanFactory具备了双亲IoC容器的管理功能。在接下来的ConfigurableBeanFactory接口中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory() 设置双亲IoC容器,通过addBeanPostProcessor() 配置Bean后置处理器,等等。通过这些接口设计的叠加,定义了BeanFactory就是简单IoC容器的基本功能。关于BeanFactory简单IoC容器的设计。

  • 第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory 到ListableBeanFacto ry ,再到ApplicationContext ,再到我们常用的WebApplicationContext 或者ConfigurableAppIicationContext接口。我们常用的应用上下文基本上都是ConfigurableApplicationContext或者WebApplicationContext的实现。在这个接口体系中, ListableBeanFactory和HierarchicalBeanFactory两个接口,连接BeanFactory接口定义和ApplicationConext应用上下文的接口定义。在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了getBeanDefinitionNames () 接口方法,对于HierarchicalBeanFactory 接口,我们在前文中已经提到过,对于ApplicationContext接口,它通过继承MessageSource 、Resource Loader 、ApplicationEventPublisher接口, 在BeanFactory简单IoC容器的基础上添加了许多对高级容器的特性的支持。

  • 这里涉及的是主要接口关系,而具体的loC容器都是在这个接口体系下实现的,比如DefaultListableBeanFactory ,这个基本loC容器的实现就是实现ConfigurableBeanFactory , 从而成为一个简单IoC 容器的实现。像其他IoC 容器,比如XmlBeanFactory ,都是在DefaultListableBeanFactory的基础上做扩展,同样地,ApplicationContext的实现也是如此。

  • 这个接口系统是以BeanFactory和ApplicationContext为核心的。而BeanFactory又是loC容器的最基本接口.在ApplicationContext的设计中,一方面,可以看到它继承了BeanFactory接口体系中的ListableBeanFactory 、AutowireCapableBeanFactory 、HierarchicalBeanFactory等BeanFactory的接口,具备了BeanFactory IoC容器的基本功能,另一方面,通过继承MessageSource 、ResourceLoadr、ApplicationEventPublisher这些接口, BeanFactory为ApplicationContext赋予了更高级的loC容器特性。对于ApplicationContext而言,为了在Web环境中使用它,还设计了WebApplicationContext接口,而这个接口通过继承自themeSource接口来扩充功能。

  1 BeanFactory的应用场景
  BeanFactory接口定义了loC容器最基本的形式,并且提供了IoC容器所应该遵守的最基本的服务契约,同时,这也是我们使用IoC容器所应遵守的最底层和最基本的编程规范,这些接口定义勾画出了loC的基本轮廓。BeanFactory只是一个接口类,并没有给出容器的具体实现,DefaultListableBeanFactory 、XmlBeanFactory 、ApplicationContext等都可以看成是容器附加了某种功能的具体实现,也就是容器体系中的具体容器产品。
  BeanFactory接口设计了getBean方法,这个方桂是使用loC容器API的主要方毯,通过这个方法, 可以取得IoC容器中管理的Bean, Bean的取得是通过指定名字来索引的。如果需要在获取Bean时对Bean的类型进行检查, BeanFactory接口定义了带有参数的getBean方法,这个方法使用与不带参数的getBean方法类似,不同的是增加了对Bean检索的类型的要求。
  用户可以通过BeanFactory接口方怯中的getBean来使用Bean名字,从而在摄取Bean时,如果需要在取的Bean是prototype类型的,用户还可以为这个prototype类型的Bean生成指定构造凶数的对应参数。这使得在一定程度上可以控制生成prototype 类型的Bean 。源码如下:

public interface BeanFactory {

    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, Class<T> requiredType) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    Object getBean(String name, Object... args) throws BeansException;

    boolean containsBean(String name);

    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;

    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    String[] getAliases(String name);

}
  • 通过接口方也containsBean让用户能够判断容器是否含有指定名字的Bean.
  • 通过接口方法is Singleton来查询指定名字的Bean是否是Singleton类型的Bean 。对于Singleton属性,用户可以在BeanDefinition中指定。
  • 通过接口方法isPrototype来查询指定名字的Bean是否是prototype类型的。与Singleton属性一样,这个属性也可以由用户在BeanDefinition中指定。
  • 通过接口方怯- isTypeMatch来查询指定了名字的Bean的Class类型是否是特定的Class类型。这个Class类型可以由用户来指定。
  • 通过接口方法getType来查询指定名字的Bean的Class类型。
  • 通过接口方法getAliases来查询指定了名字的Bean的所有别名,这些别名都是用户在BeanDefinition中定义的。

这些定义的接口方法勾画出了loC容器的基本特性。因为这个BeanFactory接口定义了IoC容器
  2. BeanFactory容器的设计原理
  BeanFactory接口提供了使用loC容器的规范。在这个基础上, Spring还提供了符合这个IoC容器接口的一系列容器的实现供开发人员使用。我们以XmlBeanFactory的实现为例来说明简单IoC容器的设计原理。如下图所示为XmlBeanFactory设计的类继承关系。
这里写图片描述
  可以看到.作为一个简单loC容器系列最底层实现的XmlBeanFactory ,与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点:它只提供最基本的loC容器的功能。理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。关于ApplicationContext的分析,以及与BeanFactory相比的增强特性都会在下面进行详细的分析。
  XmlBeanFactory的源码,在一开始的注释里会看到对XmlBeanFactory功能的简要说明。XmlBeanFactory继承自DefaultListableBeanFactory这个类,后者非常重要,是我们经常要用到的一个loC 容器的实现,比如在设计应用上下文ApplicationContext时就会用到它。我们会看到这个DefaultListableBeanFactory实际上包含了基本IoC容器所具有的重要功能,也是在很多地方都会用到的容器系列中的一个基本产品。
  在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的。Xm!BeanFactory在继承了DefaultListableBeanFactory容器的功能的同时,增加了新的功能,这些功能很容易从XmlBeanFactory的名字上猜到。它是一个与XML相关的BeanFactory ,也就是说它是一个可以读取以XML文件方式定义的BeanDefinition的IoC容器。
对这些XML文件定义信息的处理并不是由
  XmlBeanFactor y 直接完成的。在X mlBeanFactor y 中,初始化了一个XmlBeanDefinitionReader 对象,有了这个Reader对象,那些以XML方式定义的BeanDefinition就有了处理的地方。我们可以看到,对这些XML形式的信息的处理实际上是由这个Xm!BeanDefinitionReader来完成的。
  构造XmlBeanFactory这个loC容器时,需要指定BeanDefinition的信息来源,而这个信息来需要封装成Spring中的Resource类来给出。Resource是Spring用来封装I/O操作的类。比如,我们的BeanDefinition信息是以XML文件形式存在的,那么可以使用像“ClassPath-Resourceres = new ClassPathResource (” beans.xml”);”这样具体的ClassPathResource来构造需要的Resource ,然后将Resource作为构造参数传递给XmlBeanFactory构造函数。这样, IoC容器就可以方便地定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程。
  XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,并在这个基本容器的基础上实现了其他诸如XML读取的附加功能。对于这些功能的实现原理.看一看XmlBeanFactory 的代码实现就能很容易地理解。如代码所示,在XmlBeanFactory构造方法中需要得到 Resource对象。对XmlBeanDefinitionReader对象的初始化,以及使用这个对象来完成对loadBeanDefinitions的调用,就是这个调用启动从Resource中载入BeanDefinitions的过程, LoadBeanDefinitions同时也是IoC容器初始化的重要组成部分。

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    /**
     * Create a new XmlBeanFactory with the given resource,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    /**
     * Create a new XmlBeanFactory with the given input stream,
     * which must be parsable using DOM.
     * @param resource XML resource to load bean definitions from
     * @param parentBeanFactory parent bean factory
     * @throws BeansException in case of loading or parsing errors
     */
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }

}

  我们看到XmlBeanFactory使用了DefaultListableBeanFactory作为基类, DefaultListableBeanFactory是很重要的一个IoC实现,在其他IoC容器中,比如ApplicationContext ,其实现的基本原理和XmlBeanFactory一样,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。
  参考Xm!BeanFactory的实现,我们以编程的方式使用DefaultListableBeanFactory. 从中我们可以看到IoC容器使用的一些基本过程。

ClassPathResource res = new ClassPathResource(" beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
xmlBeanDefinitionReader reader= new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

  这样,我们就可以通过factory对象来使用DefaultListableBeanFactory这个IoC容器了。在使用loC容器时, 需要如下几个步骤:
1)创建IoC配置文件的抽象资源,这个抽象资源包含了BeanDefinition的定义信息。
2)创建一个BeanFactory ,这里使用DefaultListableBeanFactory.
3)创建一个载入BeanDefinition的读取器,这里使用XmlBeanDefinitionReader来载入XML文件形式的BeanDefinition ,通过一个回调配置给BeanFactory 。
4)从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成。完成整个载入和注册Bean定义之后,需要的IoC容器就建立起来了。这个时候就可以直接使用IoC容器了。
  ApplicationContext的应用场景
  在Spring中,系统已经为用户提供了许多已经定义好的容器实现,而不需要开发人员事必躬亲。相比那些简单拓展BeanFactory的基本IoC容器,开发人员用的ApplicationContext除了能够提供前面介绍的容器的基本功能外,还为用户提供了以下的附加服务, 可以让客户更方便地使用。所以说ApplicationContext是一个高级形态意义的loC容器。
这里写图片描述

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

  4 . ApplicationContext容器的设计原理
  用的FileSystemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理。
  在FileSystemXmlApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXmlApplicationContext的基类AbstractXmlApplicationContext中实现了,在FileSystemXmlApplicationContext中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。一个功能是,如果应用直接使用FileSystemXmlApplicationContext ,对于实例化这个应用上下文的支持,同时启动IoC容器的refresh()过程。这在FileSystemApplicationContext的代码实现中可以看到,代码如下:

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

  这个refresh()过程会牵涉IoC容器启动的一系列复杂操作,同时,对于不同的容器实现,这些操作都是类似的,因此在基类中将它们封装好。所以,我们在FileSystemXml的设计中看到的只是一个简单的调用。关于这个refresh()在IoC容器启动时的具体实现,是后面要分析的主要内容,这里就不详细展开了。
  另一个功能是与FileSystemXmlApplicationContext设计具体相关的功能,这部分与怎样从文件系统中加载XML的Bean定义资源有关。通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的应用上下文实现对应着不同的读取BeanDefinition 的方式,在FileSystemXmlApplicationContext中的实现代码如下:

protected Resource getResourceByPath(String path) {
    if (path != null && path.startsWith ("/")){
        path= path.substring(1);
        return new FileSystemResource(path);
    }
}

调用这个方法,可以得到FileSystemResource的资源定位。

猜你喜欢

转载自blog.csdn.net/gchd19921992/article/details/79085561