深入理解Spring IoC的原理(转发)

前言

本文转发自好好学java,作者:莫那·鲁道(点击蓝色字体即可跳转),本文有所修删改。
原作者的这篇文章对Spring IoC的实现原理讲的挺详细的。当然,也正因为如此,所以理解起来有些难度,估计本文不止需要二刷,三刷四刷都是免不了的!

广义的IOC

  1. IoC(Inversion of Control): 控制反转。
    两种实现方法: 依赖查找(DL)和依赖注入(DI)。
    IOC和DI、DL的关系(这个DL,Avalon和EJB就是使用这种方式实现的IoC):
    深入理解Spring IoC的原理(转发)
  2. DL已经被抛弃,因为它需要用户自己去使用API进行查找资源和组装对象。即有侵入性。
  3. DI是Spring使用的方式,容器负责组件的装配

注意:Java使用DI(依赖注入)方式实现IoC的不止Spring,包括Google和Guice,还有一个冷门的PicoContainer(极度轻量,但只提供IoC)。

Spring的IoC

Spring的IoC设计支持以下功能:

  1. 依赖注入
  2. 依赖检查
  3. 自动装配
  4. 支持集合
  5. 指定初始化方法和销毁方法
  6. 支持回调某些方法(但是需要实现Spring接口,略有侵入)

其中,最重要的就是依赖注入,从XML的配置上说,即ref标签。对应Spring RuntimeBeanReference对象

对于IoC来说,最重要的就是容器。容器管理着Bean的生命周期,控制着Bean的依赖注入

那么,Spring是如何设计容器的呢?
Spring作者Rod Johnson设计了两个接口用以表示容器。

  1. BeanFactory
  2. ApplicationContext

BeanFactory可以理解为就是个HashMap,Key是BeanName,Value是Bean实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为“低级容器”。

ApplicationContext可以称之为“高级容器”。因为他比BeanFactory多了更多的功能。他继承了多个接口,因此具备了更多的功能。

例如资源的获取,支持多种消息(例如JSP tag的支持),对BeanFactory多了工具级别的支持等等。所以你看它的名字,已经不是BeanFactory之类的工厂了,而是“应用上下文”,代表着整个大容器的所有功能。

该接口定义了一个refresh方法,此方法是所有阅读Spring源码的人最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的bean

为了更直观的展示“低级容器”和“高级容器”的关系,我这里通过常用的ClassPathXmlApplicationContext类,来展示整个容器的层级UML关系。
深入理解Spring IoC的原理(转发)
上图详解:

  1. 最上面的BeanFactory: 就是我们前面所说的“低级容器”,它由org.springframework.beans.factory.BeanFactory接口定义,是基础类型的IoC容器。
  2. 下面3个绿色的:HierarchicalBeanFactory、ListableBeanFactory、MessageSource:都是功能扩展接口。
  3. 下面的隶属ApplicationContext粉红色的“高级容器”,依赖着“低级容器”,这里说的是依赖,不是继承哦。它依赖着“低级容器”的getBean()功能。而高级容器有更多的功能:支持不同的信息源头可以访问文件资源支持应用事件(Observer模式)
  4. 通常用户看到的就是“高级容器”。但BeanFactory也非常够用啦!
  5. 左边灰色区域的是“低级容器”,只负责加载Bean,获取Bean。容器其他的高级功能是没有的。例如上图画的**refresh刷新Bean工厂所有配置生命周期事件回调**等。

解释了低级容器和高级容器,我们可以看看一个IoC启动过程是什么样子的。即:ClassPathXmlApplicationContext这个类在启动时,都做了啥。

下图是ClassPathXmlApplicationContext的构造过程,实际就是Spring IoC的初始化过程。
深入理解Spring IoC的原理(转发)
上图具体过程详解:

  1. 用户构造ClassPathXmlApplicationContext(简称CPAC)
  2. CPAC首先访问了“抽象高级容器”的final的refresh方法,这个方法是模板方法,所以要回调子类(低级容器)的refreshBeanFactory方法,这个方法的作用是使用低级容器加载所有BeanDefinition和Properties到容器中。
  3. 低级容器加载成功后,高级容器开始处理一些回调,例如Bean后置处理器。回调setBeanFactory方法,或者注册监听器等,发布事件、实例化单例Bean等等功能。

简单说就是:

  1. 低级容器加载配置文件(从XML、数据库、Applet),并解析成BeanDefinition到低级容器中。
  2. 加载完成后,高级容器启动高级功能,例如接口回调,监听器,自动实例化单例,发布事件等功能。

注: 一定要把“低级容器”和“高级容器”的区别弄清楚,不能一叶障目不见泰山。

当我们创建好容器,就会使用getBean方法获取Bean

getBean()的流程如下所示:
深入理解Spring IoC的原理(转发)
从图中可以看出,getBean的操作都是在低级容器里操作的。其中有个递归操作,这个是什么意思呢?

假设:当Bean_A依赖着Bean_B,而这个Bean_A在加载的时候,其配置的ref = "Bean_B"在解析的时候只是一个占位符,被放入了Bean_A的属性集合中,当调用getBean时,需要真正Bean_B注入到Bean_A内容时,就需要从容器中获取这个Bean_B,因此产生了递归。

为什么不是在加载的时候,就直接注入呢?因为加载的顺序不同,很可能Bean_A依赖的Bean_B还没有加载好,也就无法从容器中获取

所以,Spring将其分为了2个步骤:

  1. 加载所有的Bean配置成BeanDefinition到容器中,如果Bean有依赖关系,则使用占位符暂时代替。
  2. 然后,在调用getBean时,进行真正的依赖注入,即如果碰到了属性是ref的(占位符),那么就从容器里获取这个Bean,然后注入到实例中——称之为依赖注入。可以看到,依赖注入实际上,只需要“低级容器”就可以实现了

这就是IoC。

所以ApplicationContext refresh方法里面的操作不只是IoC,是高级容器的所有功能(包括IoC),IoC的功能在低级容器里就可以实现。

总结

小结一下:IoC在Spring里,只需要低级容器就可以实现,共需要2个步骤:

  1. 加载配置文件,解析成BeanDefiniton放在Map里。
  2. 调用getBean的时候,从BeanDefinition所属的Map里,拿出Class对象进行实例化,同时,如果有依赖关系,将递归调用getBean方法——完成依赖注入。

至于高级容器 ApplicationContext,它包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,它不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等

可以预见,随着 Spring 的不断发展,高级容器的功能会越来越多。

诚然,了解 IoC 的过程,实际上为了了解 Spring 初始化时,各个接口的回调时机。例如 InitializingBean,BeanFactoryAware,ApplicationListener 等等接口。