Spring源码-IoC容器(一)

258 阅读7分钟

1.IoC容器概述

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

Spring中,IoC容器就是实现依赖控制反转(也称依赖注入)的载体,是一个JavaBean容器,它管理依赖关系。在具体的注入实现中,接口注入、setter注入、构造器注入是主要的注入方式。在Spring的IoC设计中,setter注入和构造器注入是主要的注入方式。

2.IoC容器的设计与实现:BeanFactory和ApplicationContext

2.1BeanFactory的设计原理

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


上图是IoC容器的接口设计图,可以看到所有接口的"源头"都是BeanFactory,该接口定义了IoC容器的基本规范,包含了

  • getBean():从容器中取得Bean
  • containsBean():判断容器是否含有指定名字的Bean
  • isSingleton():查询指定名字的Bean是否是Singleton类型的Bean
  • isPrototype():查询指定名字的Bean是否是prototype类型的Bean
  • isTypeMatch():查询指定了名字的Bean的Class类型是否是特定的Class类型
  • getType():查询指定名字的Bean的Class类型

BeanFactory定义了IoC容器最基本的方法,但我们在设计时经常将DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用,两者之间的关系可用下图来表示


我们熟知的很多IoC容器,都是通过持有或者扩展DefaultListableBeanFactory来获得基本的IoC容器的功能的。比如:XmlBeanFactory是一个可以读取以XML文件方式定义的BeanDefinition的IoC容器。在XmlBeanFactory中,初始化了一个XmlBeanDefinitionReader对象,对XML形式的信息的处理是由它来完成的。在构造XmlBeanFactory这个IoC容器时,需要指定BeanDefinition的信息来源,这个信息来源需要封装成Spring中的Resource类,完成此封装操作以后,可以通过XmlBeanDefinitionReader的loadBeanDefinitions()方法启动从Resource中载入BeanDefinitions的过程。

总的来看,要使用一个IoC容器,需要经历这样几个过程:

1) 创建IoC配置文件的抽象资源,这个抽象资源包含了BeanFactory的定义信息

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

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

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

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

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();

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

reader.loadBeanDefinitoins(res)

Spring将上面的步骤分开,并使用不同的模块来完成。上面的第4步是IoC容器的初始化过程,后面将详细解析

2.2ApplicationContext的设计原理

我们以FileSystemXmlApplicationContext的实现说明ApplicationContext容器的设计原理。FileSystemXmlApplicationContext继承自AbstractXmlApplicationContext,ApplicationContext的主要功能已经在AbstractXmlApplicationContext中实现,而FileSystemXmlApplicationContext作为一个具体的应用上下文,实现了两个功能,一个功能是:如果应用直接使用FileSystemXmlApplicationContext,则可以实例化这个应用上下文,同时启动IoC容器的refresh()过程。这正是上面描述的使用IoC容器的过程:

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

关于这个refresh的具体调用,后面详细解析

而FileSystemXmlApplicationContext的另一个功能就是为从文件系统中加载XML形式的BeanDefinition做准备,这个方法可以得到FileSystemXmlApplicationContext的资源定位

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

2.3IoC容器的初始化过程

IoC容器的初始化由前面的refresh()来启动,这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程。

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

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

向IoC容器注册这些BeanDifinition。这个过程是通过调用BeanDefinitionRegisty接口的实现完成的。实际上,ioC容器内部将BeanDefinition注入到一个hashMap中,通过这个HashMap持有BeanDifinition数据

2.3.1BeanDefinition的Resource定位

Resource用来定位容器使用的BeanDefinition,比如

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

这里的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在ApplicationContext继承体系中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的IoC容器。

不同的应用上下文提供不同的Resource读入功能。FileSystemXmlApplicationContext从文件系统载入Resource,ClassPathXmlApplicationContext从Class Path载入Resource,XmlWebApplicationContext在Web容器中载入Resource。下面以FileSystemXmlApplicationContext为例,分析Resource定位过程。FileSystemXmlApplicationContext的继承体系如下图:


从上图中我们可以看到,FileSystemXmlApplicationContext通过继承AbstractApplicationContext具备了ResourceLoader读入以Resource定义的BeanDefinition的能力,因为AbstractApplicationContext的基类是DefaultResourceLoader。FileSystemXmlApplicationContext的具体实现如图:

public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
   public FileSystemXmlApplicationContext() {   }
   public FileSystemXmlApplicationContext(ApplicationContext parent) {
      super(parent);
   }
   public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
      this(new String[] {configLocation}, true, null);
   }
   public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
         this(configLocations, true, null);
   }
   public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
      this(configLocations, true, parent);
   }
   public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
      this(configLocations, refresh, null);
   }
   //在对象的初始化过程中,通过refresh()载入BeanDefinition,refresh()启动了BeanDefinition的载入过程 
   public FileSystemXmlApplicationContext(
         String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
         throws BeansException {
      super(parent);
      setConfigLocations(configLocations);
      if (refresh) {
         refresh();
      }
   }
   //这是应用于文件系统中Resource的实现,通过构造一个FileSystemResource来得到一个
   //在文件系统中定位的BeanDefinition,这个方法在BeanDefinitionReader的loadBeanDefinition
   //中被调用,loadBeanDefinition采用了模板模式,具体的实现又子类完成
   protected Resource getResourceByPath(String path) {
      if (path.startsWith("/")) {
         path = path.substring(1);
      }
      return new FileSystemResource(path);
   }
}

在FileSystemXmlApplicationContext的构造函数中,实现了对configuration进行处理的功,让所有配置在文件系统中的,以XML文件方式存在的BeanDefinition都能够得到有效的处理。

根据下图,我们可以清楚地看到整个BeanDefinition资源定位的过程。这个过程由FileSystemXmlApplicationContext的构造函数中的refresh()触发,大致调用如下图所示:


refresh()继承自AbstractApplicationContext,其内部调用了obtainFreshBeanFactory(),而obtainFreshBeanFactory()调用了refreshBeanFactory(),它是抽象方法,在AbstractRefreshableApplicationContext中实现,在refreshBeanFactory()中通过createBeanFactory()创建了DefaultListableBeanFactory,这是前面提到了使用IoC容器的第二步;随后调用了AbstractXmlApplicationContext中实现的loadBeanDefinitions(),并在其中创建了XmlBeanDefinitionReader对象reader,这是第三步;最后通过reader.loadBeanDefinitions(Resource resource)完成第四步

下面看看XmlBeanDefinitionReader中loadBeanDefinitions如何工作的:

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
   if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
            "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
   }
   if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
         Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
         int count = loadBeanDefinitions(resources);
         if (actualResources != null) {
            Collections.addAll(actualResources, resources);
         } 
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
         }
         return count;
      }
      catch (IOException ex) {
         throw new BeanDefinitionStoreException(
               "Could not resolve bean definition resource pattern [" + location + "]", ex);
      }
   }
   else {
      // Can only load single resources by absolute URL.
      Resource resource = resourceLoader.getResource(location);
      int count = loadBeanDefinitions(resource);
      if (actualResources != null) {
         actualResources.add(resource);
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
      }      return count; 
  }
}

方法开始得到ResourceLoader对象,并调用getResourceByPath(),这个方法在FIleSystemXmlApplicationContext中已经实现,返回一个FileSystemResource对象,这里我们是以FileSystemXmlApplicationContext为例,如果使用的是其他ApplicationContext,则会返回不同的Resource对象。

至此,定位过程已经完成,后面一篇分析BeanDefinition的载入和解析