深入理解Spring IoC的原理(转发)
前言
本文转发自好好学java,作者:莫那·鲁道(点击蓝色字体即可跳转),本文有所修删改。
原作者的这篇文章对Spring IoC的实现原理讲的挺详细的。当然,也正因为如此,所以理解起来有些难度,估计本文不止需要二刷,三刷四刷都是免不了的!
广义的IOC
- IoC(Inversion of Control): 控制反转。
两种实现方法: 依赖查找(DL)和依赖注入(DI)。
IOC和DI、DL的关系(这个DL,Avalon和EJB就是使用这种方式实现的IoC): - DL已经被抛弃,因为它需要用户自己去使用API进行查找资源和组装对象。即有侵入性。
- DI是Spring使用的方式,容器负责组件的装配。
注意:Java使用DI(依赖注入)方式实现IoC的不止Spring,包括Google和Guice,还有一个冷门的PicoContainer(极度轻量,但只提供IoC)。
Spring的IoC
Spring的IoC设计支持以下功能:
- 依赖注入
- 依赖检查
- 自动装配
- 支持集合
- 指定初始化方法和销毁方法
- 支持回调某些方法(但是需要实现Spring接口,略有侵入)
其中,最重要的就是依赖注入,从XML的配置上说,即ref标签。对应Spring RuntimeBeanReference对象。
对于IoC来说,最重要的就是容器。容器管理着Bean的生命周期,控制着Bean的依赖注入。
那么,Spring是如何设计容器的呢?
Spring作者Rod Johnson设计了两个接口用以表示容器。
- BeanFactory
- ApplicationContext
BeanFactory可以理解为就是个HashMap,Key是BeanName,Value是Bean实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为“低级容器”。
ApplicationContext可以称之为“高级容器”。因为他比BeanFactory多了更多的功能。他继承了多个接口,因此具备了更多的功能。
例如资源的获取,支持多种消息(例如JSP tag的支持),对BeanFactory多了工具级别的支持等等。所以你看它的名字,已经不是BeanFactory之类的工厂了,而是“应用上下文”,代表着整个大容器的所有功能。
该接口定义了一个refresh方法,此方法是所有阅读Spring源码的人最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的bean。
为了更直观的展示“低级容器”和“高级容器”的关系,我这里通过常用的ClassPathXmlApplicationContext类,来展示整个容器的层级UML关系。
上图详解:
- 最上面的BeanFactory: 就是我们前面所说的“低级容器”,它由org.springframework.beans.factory.BeanFactory接口定义,是基础类型的IoC容器。
- 下面3个绿色的:HierarchicalBeanFactory、ListableBeanFactory、MessageSource:都是功能扩展接口。
- 下面的隶属ApplicationContext粉红色的“高级容器”,依赖着“低级容器”,这里说的是依赖,不是继承哦。它依赖着“低级容器”的getBean()功能。而高级容器有更多的功能:支持不同的信息源头,可以访问文件资源,支持应用事件(Observer模式)。
- 通常用户看到的就是“高级容器”。但BeanFactory也非常够用啦!
- 左边灰色区域的是“低级容器”,只负责加载Bean,获取Bean。容器其他的高级功能是没有的。例如上图画的**refresh刷新Bean工厂所有配置、生命周期事件回调**等。
解释了低级容器和高级容器,我们可以看看一个IoC启动过程是什么样子的。即:ClassPathXmlApplicationContext这个类在启动时,都做了啥。
下图是ClassPathXmlApplicationContext的构造过程,实际就是Spring IoC的初始化过程。
上图具体过程详解:
- 用户构造ClassPathXmlApplicationContext(简称CPAC)
- CPAC首先访问了“抽象高级容器”的final的refresh方法,这个方法是模板方法,所以要回调子类(低级容器)的refreshBeanFactory方法,这个方法的作用是使用低级容器加载所有BeanDefinition和Properties到容器中。
- 低级容器加载成功后,高级容器开始处理一些回调,例如Bean后置处理器。回调setBeanFactory方法,或者注册监听器等,发布事件、实例化单例Bean等等功能。
简单说就是:
- 低级容器加载配置文件(从XML、数据库、Applet),并解析成BeanDefinition到低级容器中。
- 加载完成后,高级容器启动高级功能,例如接口回调,监听器,自动实例化单例,发布事件等功能。
注: 一定要把“低级容器”和“高级容器”的区别弄清楚,不能一叶障目不见泰山。
当我们创建好容器,就会使用getBean方法获取Bean。
getBean()的流程如下所示:
从图中可以看出,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个步骤:
- 加载所有的Bean配置成BeanDefinition到容器中,如果Bean有依赖关系,则使用占位符暂时代替。
- 然后,在调用getBean时,进行真正的依赖注入,即如果碰到了属性是ref的(占位符),那么就从容器里获取这个Bean,然后注入到实例中——称之为依赖注入。可以看到,依赖注入实际上,只需要“低级容器”就可以实现了。
这就是IoC。
所以ApplicationContext refresh方法里面的操作不只是IoC,是高级容器的所有功能(包括IoC),IoC的功能在低级容器里就可以实现。
总结
小结一下:IoC在Spring里,只需要低级容器就可以实现,共需要2个步骤:
- 加载配置文件,解析成BeanDefiniton放在Map里。
- 调用getBean的时候,从BeanDefinition所属的Map里,拿出Class对象进行实例化,同时,如果有依赖关系,将递归调用getBean方法——完成依赖注入。
至于高级容器 ApplicationContext,它包含了低级容器的功能,当他执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,包含了太多的功能。一句话,它不仅仅是 IoC。他支持不同信息源头,支持 BeanFactory 工具类,支持层级容器,支持访问文件资源,支持事件发布通知,支持接口回调等等。
可以预见,随着 Spring 的不断发展,高级容器的功能会越来越多。
诚然,了解 IoC 的过程,实际上为了了解 Spring 初始化时,各个接口的回调时机。例如 InitializingBean,BeanFactoryAware,ApplicationListener 等等接口。