详解Spring IoC容器

一、Spring IoC容器概述

  1.依赖反转(依赖注入):依赖对象的获得被反转了。

  如果合作对象的引用或依赖关系的管理由具体对象来完成,会导致代码的高度耦合和可测试性的降低,这对复杂的面向对象系统的设计是非常不利的。

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

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

  具体依赖注入的主要实现方式:接口注入(Type 1 IoC)、setter注入(Type 2 IoC)、构造器注入(Type 3 IoC),在Spring的IoC设计中,setter注入和构造器注入是主要的注入方式,相对而言,使用Spring时setter注入是常见的注入方式,而且为了防止注入异常,Spring IoC容器还提供了对特定依赖的检查。

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

  BeanFactory简单容器系列:这系列容器只实现了容器的最基本功能;

  ApplicationContext高级容器系列:ApplicationContext应用上下文,作为同期的高级形态存在。应用上下文在简单容器的基础上,增加了许多面向框架的特性,同时对应用环境做了许多适配。

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

详解Spring IoC容器

上图是IoC容器的接口设计图,从图中我们可以看到,IoC容器主要有两种设计路径:

1.从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径。在这条接口设计路径中,BeanFactory接口定义了基本的IoC容器规范。在这个接口定义中,包括了getBean()这样的IoC容器的基本方法(通过这个方法可以从容器中取得Bean)。

2.第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我们常用的WebApplicationContext或者ConfigurableApplicationContext接口。对于ApplicationContext接口,它通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IoC容器的基础上添加了许多对高级容器的特性支持。

(一)、BeanFactory

  BeanFactory接口定义了IoC容器最基本的形式,并且提供了IoC容器所应该遵守的最基本的服务契约,同时,这也是我们使用IoC容器所应遵守的最底层和最基本的编程规范,这些接口定义勾出了IoC的基本轮廓。

  BeanFactory和FactoryBean是在Spring中使用频率很高的类。它们在拼写上非常相似。一个是Factory,也就是IoC容器或者对象工厂;一个是Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IoC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能产生或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。

BeanFactory源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory;

import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

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

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

通过BeanFactory接口的定义,用户可以执行以下操作:

  1.通过接口方法getBean获取Bean,还可以通过参数方法对Bean类型进行检查;

  2.通过接口方法containsBean让用户能够判断容器是否含有制定名字的Bean;

  3.通过接口方法isSingleton来查询指定名字的Bean是否是Singleton类型的Bean。对于Singleton属性,用户可以在BeanDefinition中指定;

  4.通过接口方法isPrototype来查询指定名字的Bean是否是prototype类型的。与Singleton属性一样,这个属性也可以由用户在BeanDefinition中指定;

  5.通过接口方法isTypeMatch来查询指定了名字的Bean的Class类型是否是特定的Class类型。这个Class类型可以由用户指定;

  6.通过接口方法getType来查询指定名字的Bean的Class类型;

  7.通过接口方法getAliases来查询指定了名字的Bean的所有别名,这些别名都是用户在BeanDefinition中定义的;

这些定义的接口方法勾画出了IoC容器的基本特性。

  为了更清楚地了解BeanFactory作为容器的工作原理,我们来看一下BeanFanctory的一个实现类XmlBeanFactory的源代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory.xml;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.core.io.Resource;

/** @deprecated */
@Deprecated
public class XmlBeanFactory extends DefaultListableBeanFactory {
    private final XmlBeanDefinitionReader reader;

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, (BeanFactory)null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader = new XmlBeanDefinitionReader(this);
        this.reader.loadBeanDefinitions(resource);
    }
}

  我们看到XmlBeanFactory是用了DefaultListableBeanFactory作为基类,DefaultListableBeanFactory是很重要的一个IoC实现,在其他IoC容器中,比如ApplicationContext,其实现的基本原理和XmlBeanFactory一样,也是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。

  参考XmlBeanFactory的实现,我们以编程的方式使用DefaultListableBeanFactory。从中我们可以看到IoC容器使用的一些基本过程。

package com.xyfer.controller;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;

public class IoCDemo {
    public static void main(String[] args) {
        ClassPathResource res = new ClassPathResource("demo.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容器了。

(二)、ApplicationContext

  ApplicationContext除了提供BeanFactory提供的容器的基本功能外,还为用户提供了以下的附加服务,所以说ApplicationContext是一个高级形态意义的IoC容器。

详解Spring IoC容器

 从ApplicationContext继承关系中,可以看到ApplicationContext在BeanFactory的基础上通过实现不同的接口而添加不同的附加功能。

  1.支持不同的信息源。ApplicationContext扩展了MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务。

  2.访问资源。这一特性体现在对ResourceLoader和Resource的支持上,这样我们可以从不同的地方得到Bean定义资源。

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

  4.在ApplicationContext中提供的附加服务。这些服务使得基本IoC容器的功能更丰富。一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式。

三、IoC容器的初始化过程

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

  1.Resource定位过程。Resource定位指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供来统一的接口。在文件系统中的Bean定义信息可以使用FileSystemResource来进行抽象;在类路径中的Bean定义信息可以使用ClassPathResource来抽象。

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

  3.向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的。这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。通过分析,我们可以看到,在IoC容器内部将BeanDefinition注入到一个HashMap中去,IoC容器就是通过这个HashMap来持用这些BeanDefinition数据的。

  这里谈的是IoC容器初始化过程,这个过程一般不包含Bean依赖注入的实现。在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但是又一个例外的配置,在使用IoC容器时有一个预实例化的配置,通过这个预实例化的配置(具体来说,可以通过为Bean定义信息中的lazyinit属性),可以对容器初始化过程做一个微小的控制,从而改变这个被设置了lazyinit属性的Bean的依赖注入过程。举例来说,如果我们对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IoC容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才会触发。

四、IoC容器的依赖注入

  IoC容器的初始化过程完成的主要工作在IoC容器中建立BeanDefinition数据映射。但是在此过程中IoC容器并没有对Bean的依赖关系进行注入。

  当IoC容器已经载入了用户定义的Bean信息,容器中的BeanDefinition数据已经建立好的前提下,依赖注入的过程是在用户第一次向IoC容器索要Bean时触发的,也就是第一次调用getBean的时候触发,当然也有例外,就是当在BeanDefiniton中设置lazyinit属性来让容器完成对Bean的预实例化。这个预实例化实际上也是一个完成依赖注入的过程,但是这个依赖注入的过程是在初始化的过程中完成的。

  getBean是依赖注入的起点,之后会调用createBean,Bean对象会依据BeanDefinition定义的要求生成。createBean不但生成了需要的Bean,还对Bean初始化进行了处理,比如实现了在BeanDefinition中的init-method属性定义,Bean后置处理器等。CGLIB是一个常用的字节码生成器的类库,它提供了一系列的API来提供生成和转换JAVA的字节码的功能。在Spring AOP中也使用CGLIB对JAVA的字节码进行增强。在IoC容器中,Spring通过默认类SimpleInstantiationStrategy类来生成Bean对象,它提供了两种实例化Java对象的方法,一种是通过BeanUtils,它使用了JVM的反射功能,一种是通过CGLIB来生成。

  在实例化Bean对象生成的基础上,接下来就是各种依赖关系的处理。通过对BeanDefinition中的对象、value值、List、Map等进行解析,然后使用反射对属性进行注入。

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

五、IoC容器的其他相关特性

  1.ApplicationContext和Bean的初始化及销毁

    Bean的生命周期

    (1)Bean实例的创建

    (2)为Bean实例设置属性

    (3)调用Bean的初始化方法

    (4)应用可以通过IoC容器使用Bean

    (5)当容器关闭时,调用Bean的销毁方法

  2.lazy-init属性和预实例化

  3.FactoryBean的实现

  4.BeanPostProcessor的实现

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

    配置autowiring属性,IoC容器会根据这个属性的配置,使用反射自动查找属性的类型或者名字,然后基于属性的类型或名字来自动匹配IoC容器中的Bean,从而自动地完成依赖注入。

  6.Bean的依赖检查

    Spring通过依赖检查特性,帮助应用检查是否所有的属性都已经被正确设置。在Bean定义时设置dependency-check属性来指定依赖检查模式即可。属性可以设置为none、simple、object、all四种模式,默认的模式是none。

上一篇:Spring AOP自定义标签解析


下一篇:SYSTEM_INFO