重新学习Spring IOC容器

【1】IOC容器与依赖反转模式

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

通过使用IOC容器,对象依赖关系的管理被反转了,转到IOC容器中来了,对象之间的相互依赖关系由IOC容器进行管理,并由IOC容器完成对象的注入。

这里的“反转”可以认为是“责任”的反转,把责任交给了容器

在Spring中,Spring IOC提供了一个基本的JavaBean容器,通过IOC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBean这样的POJO对象赋予事务管理、生命周期管理等基本功能。

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

BeanFactory体现了Spring为提供给用户使用的IOC容器所设定的最基本的功能规范。在这些Spring提供的基本IOC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及它们之间的相互依赖关系。BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。我们都知道,在计算机世界里,所有的功能都是建立在通过数据对现实进行抽象的基础上的。IOC容器是用来管理对象依赖关系的,对IOC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对整个BeanDefinition的处理来完成的。这些BeanDefinition就像是容器里装的水,有了这些基本数据,容器才能够发挥作用。

【2】Spring IOC容器的设计

下图描述了Spring IOC容器中的主要接口设计。
重新学习Spring IOC容器

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

第二条设计主线是以ApplicationContext应用上下文接口为核心的接口设计。这里涉及的主要设计接口有,从BeanFactoryListableBeanFactory,再到ApplicationContext,再到常用的WebApplicationContext或者ConfigurableApplicationContext接口。常用的应用上下文基本都是ConfigurableApplicationContext或者WebApplicationContext的实现。在这个接口体系中,ListableBeanFactory和HiearerchialBeanFactory两个接口,连接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、AutowireCapableBeanFactory、HiearerchialBeanFactory等BeanFactory的接口,具备了BeanFactory Ioc容器的基本功能。另外一方面,通过继承MessageSource、ResourceLoader、ApplicationEventPublisher这些接口,BeanFactory为ApplicationContext赋予了更高级的Ioc容器特性。对于ApplicationContext而言,为了在Web环境中使用它,还设计了WebApplicationContext接口,而这个接口通过继承ThemeSource接口来扩充功能。


【3】BeanFactory

BeanFactory接口定义了IOC容器最基本的形式,并且提供了IOC容器所应该遵守的最基本的服务契约。BeanFactory接口设计了getBean方法,这个方法是使用IOC容器API的主要方法,通过这个方法,可以取得IOC容器中管理的Bean,Bean的取得是通过指定名字来索引的。如果需要在获取Bean时对Bean的类型进行检查,BeanFactory接口定义了带有参数的getBean方法,这个方法的使用与不带参数的getBean方法类似,不同的是增加了对Bean检索的类型的要求。

有了BeanFactory的定义,用户可以执行以下操作:

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

BeanFactory接口源码

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, 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;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);

    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, Class<?> var2) throws NoSuchBeanDefinitionException;

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

    String[] getAliases(String var1);
}

可以看到,这里定义的只是一系列的接口方法,通过这一系列的BeanFactory接口,可以使用不同的Bean的检索方法,很方便地从IOC容器中得到需要的Bean,从而忽略具体的IOC容器的实现。


【4】BeanFactory容器的设计原理

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

XmlBeanFactory类继承关系

重新学习Spring IOC容器

可以看到,作为一个简单IOC容器系列最底层实现的XmlBeanFactory,与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点:它只提供最基本的IOC容器的功能。理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为之间的BeanFactory实现是IOC容器的基本形式,而各种ApplicationContext的实现是IOC容器的高级表现形式。

在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IOC容器来使用的。XmlBeanFactory在继承了DefaultListableBeanFactory容器的功能的同时,增加了新的功能–可以读取以XML文件方式定义的BeanDefinition。

在XmlBeanFactory中,初始化了一个XMLBeanDefinitionReader对象,有了这个Reader对象,那些以XML方式定义的BeanDefinition就有了处理的地方。构造XmlBeanFactory这个IOC容器时,需要指定BeanDefinition的信息来源,这个信息来源需要封装成Spring中的Resource类来给出。Resource是Spring用来封装IO操作的类。比如我们的BeanDefinition信息是以XML文件形式存在的,那么可以使用像ClassPathResource res=new ClassPathResource("beans.xml");这样具体的ClassPathResource来构造需要的Resource,然后将Resource作为构造参数传递给XmlBeanFactory构造函数。这样IOC容器就可以方便地定位到需要的BeanDefinition信息来对Bean完成容器的初始化和依赖注入过程。

XmlBeanFactory源码如下:

public class XmlBeanFactory extends DefaultListableBeanFactory {

//这里初始化了一个XmlBeanDefinitionReader对XML形式的信息进行处理
	private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);


	/**
	 * Create a new XmlBeanFactory with the given resource,
	 * which must be parsable using DOM.
	 * @param resource the 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 the 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这个基本容器的基础上的,并在这个基本容器的基础上实现了其他诸如XML读取的附加功能。如上源码所示,在XmlBeanFactory构造方法中需要得到Resource对象。对XmlBeanDefinitionReader对象的初始化以及使用这个对象来完成对loadBeanDefinitions的调用,就是这个调用启动从Resource中载入BeanDefinitions的过程,loadBeanDefinitions同时也是IOC容器初始化的重要组成部分。


【5】ApplicationContext

在Spring中,系统已经为用户提供了许多已经定义好的容器实现,而不需要开发人员事必躬亲。相比那些简单拓展BeanFactory的基本IOC容器,开发人员常用的ApplicationContext除了能够提供前面介绍的容器的基本功能外,还为用户提供了一些附加服务,所以说ApplicationContext是一个高级形态意义的IOC容器。

ApplicationContext接口继承图
重新学习Spring IOC容器
附加功能主要如下:

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

在ApplicationContext中Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而类似DefaultListableBeanFactory只是一个纯粹的IOC容器,需要为它配置特定的读取器才能完成这些功能。当然是用DefaultListableBeanFactory这种更底层的容器,能提高定制IOC容器的灵活性。


ApplicationContext容器的设计原理

这里以FileSystemXmlApplicationContext的实现为例来说明ApplicationContext容器的设计原理。

重新学习Spring IOC容器
在FileSystemlXmlApplicationContext的设计中,我们看到ApplicationContext应用上下文的主要功能已经在FileSystemXmlApplicaitonContext的基类AbstractXmlApplicationContext中实现了,在FileSystemXmlApplicationContext中,作为一个具体的应用上下文,只需要实现和它自身设计相关的两个功能。

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

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

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

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

通过这个过程,可以为在文件系统中读取以XML形式存在的BeanDefinition做准备,因为不同的应用上下文实现对应着不同的读取BeanDefinition的方式,在FileSystemlXmlApplicationContext中的实现代码如下:

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

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