IOC 概述

Spring IOC是一个容器,用于生成和管理Bean的实例,以及实例之间的依赖关系,然后注入相关的依赖。这里我们可以把IOC容器想象成一个餐馆。我们去餐馆点菜的话,不需要关心菜的生成过程,不需要关心菜的原材料从哪里来。我们只需要最终做好的菜。这里的菜就是我们的需要的Bean。不同的菜对应不同的Bean。有了IOC容器之后,如果A类依赖B类,只需要通过IOC容器帮我们创建A类的实例和B类的实例,然后IOC容器会将B类的实例注入到A类中。

还是回到餐馆那个例子,做菜的话就需要与原材料和菜谱,同样的IOC容器想要管理各个业务对象以及他们之间的依赖关系,就需要通过某种途径来记录和管理这些信息,而BeanDefinition对象就承担了这个责任。IOC容器中每一个Bean都会有一个对应的BeanDefinition实例,该实例负责保存bean对象的所有必要信息,包括Bean对象的class类型,是否是抽象类,构造方法和参数,以及其他属性等,这里的BeanDefinition就相当于原材料。而BeanDefinitionRegistry对象和BeanFactory对象就相当于菜谱,告诉我们如何将原材料加工成相应的菜肴。

下面我们就来看看这些比较核心的类和接口:

作用
BeanFactory BeanFactory接口主要包括了getBean,containsBean,getAllases等方法,作用是操作Bean
BeanDefinitionRegistry BeanDefinitionRegistry接口抽象出了Bean的注册逻辑,其主要包括了registerBeanDefinition,removeBeanDefinition,getBeanDefinition等方法
ConfigurableListableBeanFactory ConfigurableListableBeanFactory接口包括了getBeanDefinition等方法,可以获取BeanDefinition实例
DefaultListableBeanFactory DefaultListableBeanFactory类同时实现了ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,说明其承担了Bean的注册和管理工作
BeanDefinition BeanDefinition接口是用来存储Bean对象的必要信息,包括Bean对象的class类型,是否是抽象类,构造方法和参数,依赖关系,以及其他属性等
PropertyValue 这个类就是具体存放Bean对象的属性信息以及其依赖信息

在Spring中,ApplicationContext是IOC容器的承载体,而BeanFactory是操作这个容器的工具,两者关系紧密,相互协作,refresh方法实现了ApplicationContext和BeanFactory相互协作的过程,不同之处主要在于子类 AbstractRefreshableApplicationContext 和 GenericApplicationContext 中实现,两者使用的 BeanFactory 都为 DefaultListableBeanFactory,它构建在BeanFactory之上,属于更⾼级的容器,除了具有BeanFactory的所有能⼒之外,还提供对事件监听机制以及国际化的⽀持等。它管理的bean,在容器启动时全部完成初始化和依赖注⼊操作。

IOC容器的启动/初始化过程

  • 定位:定位配置文件,通过BeadDefinetionReader读入配置
  • 加载/解析:对配置文件的加载,将配置好的文件进行加载并根据配置的原理以及规则对配置进行解析,如各种Bean之间的关系描述。
  • 注册: 将BeanDefinition注册到beanDefinitionMap中

资源定位,找到配置文件

这里定位资源有两种方式,一种是通过ClassPathXmlApplicationContext类来解析Spring的配置文件的形式,就是通过配置文件来定义Bean的情况,另外,一种情况就是通过注解的方式来定义Bean的情况,这种情况是通过AnnotationConfigApplicationContext类解析的。

BeanDefinition的载入和解析,将配置文件解析成BeanDefiniton

说完了配置文件的解析之后,接下来,我们来看看BeanDefinition的载入和解析。我们直接找到parseDefaultElement方法。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //省略部分非核心代码
  //如果节点是bean节点,说明是一个Bean
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
  }
}

这个方法按照节点名,调用不同的处理方法,在此处我们只看节点为bean时调用的方法processBeanDefinition方法。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// Register the final decorated instance.(注册BeanDefinition)
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		}
		catch (BeanDefinitionStoreException ex) {
			getReaderContext().error("Failed to register bean definition with name '" +
					bdHolder.getBeanName() + "'", ele, ex);
		}
		// Send registration event.
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}

我们重点看BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());这个方法,这个方法才是真正的将传入BeanDefinitionRegistry类,载入并解析BeanDefinition,然后对BeanDefinition进行注册。

将BeanDefinition注册至beanDefinitionMap

接下来就到了我们的重头戏,注册BeanDefinition到beanDefinitionMap中,其中key就是Bean的id,其中beanDefinitionMap是一个定义在DefaultListableBeanFactory类中全局的线程安全的map,用于存放解析到的BeanDefinition。

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

让我们来看看registerBeanDefinition方法吧,这个方法核心的步骤有两步:

  1. 根据传入的beanName检查beanDefinition是否存在,如果存在就是一系列的校验,主要是保证BeanDefinition的单例性,就是说IOC容器中每个Bean的实例时单例的。
  2. 将传入的beanDefinition实例放到beanDefinitionMap中。
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  throws BeanDefinitionStoreException {
  if (hasBeanCreationStarted()) {
    // Cannot modify startup-time collection elements anymore (for stable iteration)
    //加锁,保证线程安全
    synchronized (this.beanDefinitionMap) {
      // 将beanDefinition值设置到beanDefinitionMap中
      this.beanDefinitionMap.put(beanName, beanDefinition);
      List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
      updatedDefinitions.addAll(this.beanDefinitionNames);
      updatedDefinitions.add(beanName);
      this.beanDefinitionNames = updatedDefinitions;
      if (this.manualSingletonNames.contains(beanName)) {
        Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
        updatedSingletons.remove(beanName);
        this.manualSingletonNames = updatedSingletons;
      }
    }
  }
  else {
    // Still in startup registration phase,将beanDefinition值设置到beanDefinitionMap中
    this.beanDefinitionMap.put(beanName, beanDefinition);
    this.beanDefinitionNames.add(beanName);
    this.manualSingletonNames.remove(beanName);
  }
  this.frozenBeanDefinitionNames = null;
}

小结

  • 资源定位,找到配置文件。这里定位资源有两种方式,一种是通过ClassPathXmlApplicationContext类来解析Spring的配置文件的形式,就是通过配置文件来定义Bean的情况,另外,一种情况就是通过注解的方式来定义Bean的情况,这种情况是通过AnnotationConfigApplicationContext类解析的。
  • BeanDefinition的载入和解析,将配置文件解析成BeanDefiniton。
  • 将BeanDefinition注册至beanDefinitionMap,其中key就是Bean的id,其中beanDefinitionMap是一个定义在DefaultListableBeanFactory类中全局的线程安全的ConcurrentHashMap,用于存放解析到的BeanDefinition。
    • 根据传入的beanName检查beanDefinition是否存在,如果存在就是一系列的校验,主要是保证BeanDefinition的单例性,就是说IOC容器中每个Bean的实例时单例的。
    • 将传入的beanDefinition实例放到beanDefinitionMap中

Bean的实例化和依赖注入

说完了IOC容器的初始化过程,接下来,我们来看看IOC容器的实例化过程。经过上一个阶段,所有Bean定义都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求通过容器的getBean方法请求某个对象,或者因为依赖关系容器需要隐式的调用getBean方法时,就会触发第二阶段的活动,IOC容器首先检查所请求的对象是否已经实例化完成,如果没有,则会根据注册的BeanDefinition所提供的信息实例化请求对象。并为其注入依赖,当该对象装配完成后,容器会立即返回给请求方法。

详细可以参考Spring Bean的生命周期这篇博文。