简介
控制反转(Inversion of Control, 缩写为IoC), 是面向对象编程中的一种设计原则, 可以用来减低计算机代码之间的耦合度;其中最常见的方式叫做依赖注入(Dependency Injection, 简称DI), 还有一种方式叫 “赖查找” (Dependency Lookup); 通过控制反转, 对象在被创建的时候, 由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它; 也可以说, 依赖被注入到对象中
理论背景
在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑;如下图展示了软件系统中耦合的对象;如同一个齿轮组一样,如果有一个齿轮出了问题,那么整个系统就会出现问题
耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间;如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一;为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996 年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中
什么是 IOC
IOC 是 Inversion of Control 的缩写,多数书籍翻译成 “控制反转”
1996 年, Michael Mattson 在一篇有关探讨面向对象框架的文章中, 首先提出了IOC 这个概念; 对于面向对象设计及编程的基本思想, 前面我们已经讲了很多了, 不再赘述, 简单来说就是把复杂系统分解成相互合作的对象, 这些对象类通过封装以后, 内部实现对外部是透明的, 从而降低了解决问题的复杂度, 而且可以灵活地被重用和扩展; IOC理论提出的观点大体是这样的: 借助于 “第三方” 实现具有依赖关系的对象之间的解耦; 如下图展示了 IOC 解耦过程
由于引进了中间位置的"第三方", 也就是IOC容器, 使得 A、B、C、D 这4个对象没有了耦合关系, 齿轮之间的传动全部依靠 “第三方” 了, 全部对象的控制权全部上缴给 “第三方” IOC 容器, 所以, IOC容器成了整个系统的关键核心, 它起到了一种类似"粘合剂"的作用, 把系统中的所有对象粘合在一起发挥作用, 如果没有这个"粘合剂", 对象与对象之间会彼此失去联系, 这就是有人把 IOC 容器比喻成 “粘合剂” 的由来
如果把上图中间的IOC容器拿掉,然后再来看看这套系统
现在看到的画面, 就是我们要实现整个系统所需要完成的全部内容; 这时候, A、B、C、D 这 4 个对象之间已经没有了耦合关系, 彼此毫无联系, 这样的话, 当你在实现A的时候, 根本无须再去考虑 B、C 和 D 了, 对象之间的依赖关系已经降低到了最低程度; 所以, 如果真能实现 IOC 容器, 对于系统开发而言, 这将是一件多么美好的事情, 参与开发的每一成员只要实现自己的类就可以了, 跟别人没有任何关系!
我们再来看看, 控制反转(IOC)到底为什么要起这么个名字?我们来对比一下, 软件系统在没有引入 IOC 容器之前, 如图1所示, 对象 A 依赖于对象 B, 那么对象A在初始化或者运行到某一点的时候, 自己必须主动去创建对象B或者使用已经创建的对象 B; 无论是创建还是使用对象 B, 控制权都在自己手上;
软件系统在引入 IOC 容器之后, 这种情形就完全改变了, 由于 IOC 容器的加入, 对象A与对象B之间失去了直接联系, 所以, 当对象 A 运行到需要对象 B 的时候, IOC 容器会主动创建一个对象 B 注入到对象 A 需要的地方; 通过前后的对比, 我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动行为变为了被动行为, 控制权颠倒过来了, 这就是"控制反转"这个名称的由来。
IOC 也叫依赖注入(DI)
2004 年, Martin Fowler 探讨了同一个问题, 既然 IOC 是控制反转, 那么到底是"哪些方面的控制被反转了呢?“, 经过详细地分析和论证后, 他得出了答案: “获得依赖对象的过程被反转了”; 控制被反转之后, 获得依赖对象的过程由自身管理变为了由IOC容器主动注入; 于是, 他给"控制反转"取了一个更合适的名字叫做"依赖注入(Dependency Injection)”; 他的这个答案, 实际上给出了实现IOC的方法: 注入; 所谓依赖注入, 就是由IOC容器在运行期间, 动态地将某种依赖关系注入到对象之中; 所以, 依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情, 就是指通过引入 IOC 容器, 利用依赖关系注入的方式, 实现对象之间的解耦
Bean 的 Scope
scope 共有 5 种模式, 如果没有配置 scope 的话, 默认是 singleton(单例模式)
singleton: 单例模式, 默认, 只会创建一个实例
prototype: 多例模式, 每次请求都会创建一个新的实例
request: 每一次 HTTP 请求都会产生一个新的 bean, 同时该bean仅在当前 HTTP request 内有效
session: 每一次 HTTP 请求都会产生一个新的 bean, 同时该bean仅在当前 HTTP session 内有效
global session: 类似于标准的 HTTP Session 作用域, 不过仅在基于 portlet 的 web 应用中有意义
什么时候需要使用单例或多例呢,简单的区别方式就是 这个类中的属性如果会变的话,就要使用多例,如果属性不会改变,就可以使用单例
比如在 UserAction(Struts2) 类中 User 对象是会改变的,所以要使用多例,否则在调用 add() 的时候,最后一次 add 操作会覆盖之前的 user 对象
注入配置方式
- 通过 getter 和 setter 方式注入
2. 在类中添加构造函数,然后通过构造函数方式注入
构造函数添加 IUserService