2.2 依赖注入DI与控制反转IOC
本节主要介绍依赖注入与控制反转的基本概念,理解什么是依赖注入、什么是控制反转。在此基础上通过示例动手操作加深理解。
2.2.1 什么是依赖注入
从汉字的字面意思理解,可以把“依赖注入”分为两个词,一个是“依赖”,一个是“注入”。那什么是依赖、什么是注入呢?依赖是依靠别人或事物而不能自立或自给,通俗的理解就是,不是自身的,但没有就活不下去,比如人没有了空气、水、阳光,那就活不下去,所以人依赖空气、水、阳光。注入是之前内部没有通过外部灌入的。
上面是从字面意思理解了一下依赖注入,接着从编程的角度分析一下。依赖注入是组件之间依赖关系由容器在运行期决定的,即由容器动态地将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了解耦,提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无须任何代码就可以指定目标需要的资源,完成自身的业务逻辑,不需要关心具体的资源来自何处、由谁实现。
● 依赖注入是组件之间依赖关系由容器在运行期决定的,即由容器动态地将某个依赖关系注入到组件之中。这句话总结依赖注入的核心原理,比如分别把人(Person)和空气(Air)都当作一个组件(类),人依赖空气,那么这个依赖关系不是人和空气两个组件关联的,需要一个容器决定,而且不是一开始就决定的,是运行期决定的。
● 依赖注入的目的并非为软件系统带来更多功能,而是为了解耦,提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。这句话是使用依赖注入的目的。使用依赖注入可以进一步解耦,也是软件设计中高内聚低耦合的体现。
● 通过依赖注入机制,我们只需要通过简单的配置,而无须任何代码就可以指定目标需要的资源,完成自身的业务逻辑,不需要关心具体的资源来自何处、由谁实现。这句话是实现依赖注入的方法,通过简单配置就能实现依赖注入。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,接着使用人和空气来深入分析一下:
● 谁依赖谁:人依赖空气。
● 为什么需要依赖:人需要空气,没有空气,人就不存在,就是一个空对象。
● 谁注入谁:人通过容器注入空气。
● 注入了什么:注入了人所需要的空气。
2.2.2 什么是控制反转
与依赖注入一样,我们还是先从字面意思理解一下控制反转。它也可以分为两个词,一个是“控制”,一个是“反转”。那什么是控制,什么又是反转呢?
● 控制:为掌握住对象不使任意活动或超出范围;或使其按控制者的意愿活动。
● 反转:向相反的方向转动。
上面是从中文字面对控制反转的理解,下面从编程的角度分析一下。IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。理解IOC的关键是:“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”。我们接着使用人和空气来深入分析一下:
● 谁控制谁,控制什么:以往我们是通过new关键字来创建对象的,比如人(Person)依赖空气(Air),在Person中如果要使用Air对象,就需要通过New关键字来主动创建,在IOC中有一个专门的容器来创建这些对象,即由IOC容器来控制对象的创建。
谁控制谁?当然是IOC容器控制对象了。
控制什么?主要控制了外部资源获取(不只是对象,还包括文件等)。
● 为何是反转,哪些方面反转了:有反转就有正转,正转就是我们通过New主动获取依赖对象;反转则是由容器来帮忙创建及注入依赖对象。
为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动地接受依赖对象,所以是反转。
哪些方面反转了?依赖对象的获取被反转了。Air对象通过容器注入给Person对象。
2.2.3 依赖注入的优缺点
可能有人会纳闷为什么只介绍使用依赖注入的优缺点,那控制反转就没有优缺点吗?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”。相对IOC而言,“依赖注入”明确描述了“被注入对象依赖IOC容器配置依赖对象”。
俗话说,每个硬币都有两面。同样,IOC也是有优点和缺点的。
优点,也是使用依赖注入的目的:实现组件之间的解耦,提高程序的灵活性和可维护性,提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。
缺点也是存在的。一是它创建对象的方式变复杂了。二是因为使用反射来创建对象,所以在效率上会有些损耗。但相对于程序的灵活性和可维护性来说,这点损耗是微不足道的。三是使用XML进行配置时太复杂,一旦类有改变,XML就需要改变。有了注解之后,与使用XML进行配置相比简单很多。
2.2.4 IOC实例
光说不练假把式,特别是IT技术,经常会出现看能看懂但写不出来的尴尬局面。上面分析了一下依赖注入与控制反转,本节将通过示例来加深理解。这里还是使用人与空气的例子。
人依赖空气,在传统的方式创建两个类:一个是Person类,一个是CleanAir类。
上面两个类实现了依赖的关系,还有就是注入。在了解注入之前,我们还有一个问题要思考。有这样一句话:世界上唯一不变的就是变化。之前干净的空气不复存在,而Person依赖的不再是CleanAir,而是比CleanAir更有内涵的DirtyAir。如果还是按照上面的方式来,那就需要在增加一个DirtyAir类的同时修改Person。这种强依赖有很大的弊端,一个地方变化引起其他地方变化,而且改变的只是Air,但Person也要改变。怎么样才能尽量减少修改的地方呢?于是面向接口的编程出现了。下面先定义一个接口IAir,类CleanAir实现接口IAir,在Person中不再直接依赖CleanAir,而是依赖接口IAir,这样即使是DirtyAir也只需要给Person修改不同的Air就行了。这个注入的过程,利用Spring框架只需要改一下配置即可实现。
上面定义IAir接口算是对依赖关系的优化,降低了人与空气的耦合度,但是并没有使用New关键字创建对象,只是定义了依赖关系。下面用Spring实现注入。
(1)创建一个Maven Project,archetype选择quickstart,如图2-2所示。
图2-2
(2)创建之后,既然要使用Spring框架来实现注入,那肯定要在项目中引入Spring框架,配置pom.xml,添加依赖。
① 在properties节点配置要引入Spring的版本号,这里用的是5.0.0.RELEASE。
<spring.version>5.0.0.RELEASE</spring.version>
② 引入。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency>
(3)实现依赖注入。
前面创建的IAir接口、Person类、CleanAir类、DirtyAir类实现了依赖关系,但是怎么让Spring框架识别到呢?不可能把各个类创建好就结束了,还需要进行配置才能让Spring知道哪些是组件。这里对Person类、CleanAir类、DirtyAir类在上面定义的基础上进行修改。
在上面的代码中使用了@Component将类注解成组件,使用@Autowired将IAir类型对象注入Person中。CleanAir类、DirtyAir类都实现了IAir接口,怎么让Person具体注入哪个对象呢?使用@Qualifier关键字来进行区分,这里使用的是qualifier=dirtyair。同时这些组件定义之后还要告诉Spring框架组件位置在哪,所以在scr/main/resources下新建了ApplicationContext.xml进行配置。
(4)测试。
在main中获取到应用上下文,通过getBean方法获取Person对象,然后调用Breath()方法。不了解ClassPathXmlApplicationContext、getBean这些方法也没关系,在后面的章节会有详细介绍。
输出结果:
DirtyAir
如果想使用CleanAir对象,只需要把Person类中@Qualifier注解value的值改为CleanAir对应的beanId:cleanair。
(5)小结
参考上面的示例思考2.2.1和2.2.2节中的概念,就会发现依赖注入、控制反转其实也不难。理解了依赖注入、控制反转对后面Spring框架的学习会有更大的帮助。