序言
记得刚毕业那会儿,出来招工作被问到Spring的核心时,都觉得简单的一笔,直接说不就是IoC(控制反转)和DI(依赖注入)么,然后省略一万字对两个名词的解释。最近空来整理了一下Spring中IoC的相关概念,即是复习,也是希望分享出来能帮助到大家更快理解IoC。
其实IoC包括依赖查找( DL )和依赖注入(DI);只不过DL因为有侵入性 (它需要用户自己去是使用 API 进行查找资源和组装对象),已经被抛弃。所以现在提到IoC,更多的想到的就是依赖注入(DI)了。
依赖注入(DI)包括Set注入和构造器注入!其实还有一个通过实现接口的方式实现依赖注入,不过不常用,就不说了。
如图所示:
但其实 IOC 和DI 相当于一回事,只不过是看待问题的角度不同而已:
IOC: Spring 反向控制应用程序需要的资源。
DI: 应用程序依赖Spring为其提供资源。
IOC 是站在Spring 的角度,而DI 是站在应用程序的角度。
如下图所示:
接下来详细介绍一下IoC,从其初始化到实现过程,细细理解!
IoC粗理解
IoC亦称为“依赖倒置原理”(Dependency Inversion Principle),几乎所有框架都使用了倒置注入(Martin Fowler)技巧,是IoC原理的一项应用。SmaIITaIk、C++、Java和.NET面向对象语言的程序员已使用了这些原理。但是Spring是Java语言实现中最著名的一个。同时,控制反转即是Spring框架的核心,也是Spring框架要解决的核心问题。
IoC细理解
由于很多对象的依赖关系和维护并不需要和系统运行状态有很强的关联性,所以可以把在面向对象编程中需要执行的诸如新建对象、为对象引用赋值等操作交由容器统一完成;这样一来,这些散落在不同代码中的功能相同的部分就集中成为容器的一部分,也就是面向对象系统的基础设施的一部分。同时,这些对象之间的相互依赖关系也是比较稳定的,一般不会随着应用的运行状态的改变而改变。
此时,IoC 容器控制了对象,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象这是正转,因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;这就解释了控制反转。
基于以上特性,这些对象使用IoC容器来管理,简直就是天作之合。虽然这些特性存在于应用系统中,但是应用系统并不承担管理这些对象的责任,而是通过依赖反转把责任交给了容器(也可以说是平台)。
有了以上这些基础知识储备,Spring IoC容器的原理也就不难理解了。
Spring中IoC的应用
在Spring中,Spring IoC提供了一个基本的 JavaBean 容器,通过IoC模式管理依赖关系,并通过依赖注入和AOP切面增强了为JavaBea月这样的 POJO 对象赋予事务管理、生命周期管理等基本功能。
IoC容器
容器的两种表现形式
Spring 作者 Rod Johnson设计了两个接口用以表示容器:BeanFactory和 ApplicationContext
- BeanFactory 粗暴简单,可以理解为就是个 HashMap ,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
- ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
故我们可以认为直接的BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。所以亦可简单的把Spring IoC通过BeanFactory的实现当做低级容器;把ApplicationContext的实现当做高级容器。
此文我们主要讲解Spring 低级容器(BeanFactory)的 IoC;因为高级容器 ApplicationContext,它包含了低级容器的功能,当它执行 refresh 模板方法的时候,将刷新整个容器的 Bean。同时其作为高级容器,它包含了太多的功能,不仅仅是 IoC。它支持不同信息源头,支持 BeanFactory 工具类、支持层级容器、支持访问文件资源、支持事件发布通知、支持接口回调等等。
BeanFactory的IoC实现过程:
IoC 在 Spring 里,只需要低级容器(BeanFactory)就可以实现,两个步骤:
- 1、加载配置文件,解析成 BeanDefinition 放在 Map 里。
- 2、调用 getBean 的时候,从 BeanDefinition 所属的 Map 里,拿出 Class 对象进行实例化,同时,如果有依赖关系,将递归调用 getBean 方法 —— 完成依赖注入。getBean的流程如下所示:
IoC容器初始化过程
值得注意的是,在这个过程中,一般不包含Bean侬赖注入的实现。
在Spring IoC的设计中,Bean定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。
但有一个例外值得注意,在使用loc容器时有一个预实例化的配置,通过这个预实例化的配置(具体来说,可以通过为Bean定义信息中的lazyinit属性),用户可以对容器初始化过程作一个微小的控制,从而改变这个被设置了]azyinit属性的Bean的依赖注入过程。
举例来说,如果我们对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IoC容器初始化时就预先完成了,而不需要等到整个初始化完成以后,第一次使用getBean时才会触发。
BeanDefinition的定位
对loC容器来说,它为管理POJO之间的依赖关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。我们可以使用各种形式的Bean定义信息,其中比较熟悉和常用的是使用XML的文件格式。
在Bean定义方面,Spring为用户提供了很大的灵活性。在初始化IoC容器的过程中,首先需要定位到这些有效的Bean定义信息,这里Spring使用Resource接口来统一这些Bean定义信息,而这个定位由ResourceLoader来完成。
- 如果使用上下文,ApplicationContext本身就为客户提供了定位的功能。因为上下文本身就是DefaultResourceLoader的子类。
- 如果使用基本的BeanFactory作为loC容器,客户需要做的额外工作就是为BeanFactory指定相应的Resource来完成Bean信息的定位。
BeanDefinition的载入
信息的载入过程。对IoC容器来说,这个载入过程,相当于把定义的BeanDefinition在IoC容器中转化成一个spring内部表示的数据结构的过程。IoC容器对Bean的管理和依赖注入功能的实现,都是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高IoC容器的性能和容量,完全可以自己做一些扩展。
IoC容器的依赖注入
IoC容器的初始化过程完成的主要工作是在IoC容器中建立BeanDefinition数据映射。但在此过程中并没有IoC容器对Bean依赖关系进行注入,那么IoC容器是怎样对Bean的依赖关系进行注入的呢?
假设当前IoC容器已经载入了用户定义的Bean信息,开始分析依赖注入的原理:
首先,依赖注入的过程是用户第一次向容器索要Bean时触发的,当然也有例外,也就是我们可以在BeanDefinition信息中通过控制lazy-init属性来让容器完成对Bean的预实例化。这个预实例化实际上也是一个完成依赖注入的过程,但它是在初始化的过程中完成的。
所以,当用户向IoC容器索要Bean时,如果读者还有印象,那么一定还记得在基本的IoC容器接口BeanFactory中,有一个getBean的接口定义,这个接口的实现就是触发依赖注入发生的地方(也就是依赖注入的入口);而依赖注入的发生是在容器中的BeanDefinition数据已经建立好的前提下才能完成的。
IoC小结
尽管可以用最简单的方式来描述IoC容器,将它视为一个hashMap,但只能说这个hashMap是容器的最基本的数据结构,而不是IoC容器的全部。
打个比方来讲,使用IoC后相当于IoC就是一个饮品店;以前的我们需要自己new对象,也就是需要自己买橙子,买榨汁机来榨果汁喝;而是用IoC后,我们只需要把需求(想喝橙汁)告诉它,然后由它给我们提供橙汁就可以了。这样子想,是不是IoC就感觉简单多了呢?
Spring IoC容器作为一个产品,其价值体现在一系列相关的产品特性上,这些产品特性以依赖反转模式的实现为核心,为用户更好地使用依赖反转提供便利,从而实现了一个完整的IoC容器产品。
参考文章:
1、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》
2、
3、