理解依赖注入

来源:互联网 发布:windows10引导ubuntu 编辑:程序博客网 时间:2024/05/29 04:58

虽然Spring并不是依赖注入的首创者,但RodJohnson是第一个高度重视以配置文件来管理Java实例的协作关系的人,他给这种方式起了一个名字,控制翻转(Inversion of Control, IoC)。在后来的日子里,Martine Fowler 为这种方式起了另一个名称:依赖注入(Dependency Injection)。

因此,不管是依赖注入,还是控制反转,其含义完全相同。当某个Java对象(调用者)需要调用另外一个Java对象(被依赖对象)的方法时,在传统模式下通常有两种做法。

原始做法:调用者主动创建被依赖对象,然后再调用被依赖对象的方法。

简单工厂模式:调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。

对于第一种方式,由于调用者需要通过形如“new被依赖对象构造器(); ” 的代码创建对象,因此必然导致调用者与被依赖对象实现类的硬编码耦合,非常不利于项目升级的维护。

对于简单工厂的方式:大致需要把握三点:①调用者面向被依赖对象的接口编程:②将被依赖对象的创建交给工厂完成;③调用者通过工厂来获得被依赖组件。通过这三点改造,可以保证调用者只需与被依赖对象的接口耦合,这就避免了类层次的硬编码耦合,这种方式唯一的缺点是,调用组件需要主动通过工厂去获取被依赖对象,这就会带来调用组件与被依赖对象工厂的耦合。

使用Spring框架之后,调用者无需主动获取被依赖对象,调用者只要被动接受Spring容器为调用者的成员变量赋值即可(只要配置一个<property...>子元素,Spring就会执行对应的setter方法为调用者的成员变量赋值)。由此可见,使用Spring框架之后,调用者获取被依赖对象的方式由原来的主动获取,变成了被动接受——于是Rod Johnson将这种方式称为控制反转。

从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此Martine Fowler将这种方式称为依赖注入。

正因为Spring将依赖对象注入给了调用者,所以调用者无须主动获取被依赖对象,只要被动等待Spring容器注入即可,由此可见,控制反转和依赖注入其实是同一个行为的两种表达,只是描述的角度不同而已。

为了更好地理解依赖注入,可以参考人类社会的发展来看以下问题在各种社会形态里如何解决:一个人(Java实例,调用者)需要一把斧头(Java实例 被依赖对象)。

在原始社会里,几乎没有社会分工。需要斧头的人(调用者)只能自己去磨一把斧头(被依赖对象)。对应的情形为:Java程序里的调用者自己创建被依赖对象,通常采用new 关键字调用构造器创建一个被依赖对象,这是Java初学者经常感到事情。

进入工业社会,工厂出现了,斧头不在有普通人完成,而在工厂里被生产出来,此时需要斧头的人(调用者)找到工厂,购买斧头,无需关心斧头的制造过程。对应简单工厂设计模式,调用者只需要定位工厂,无须理会被依赖对象的具体实现过程。

进入 “共产主义” 社会,需要斧头的人甚至不需要定位工厂,“坐等” 社会提供即可。调用者无须关心被依赖对象的实现,无须理会工厂,等待Spring依赖注入。

在第一种情况下,Java实例的调用者创建被调用的Java实例,调用者直接使用 new 关键字创建被依赖对象,程序高度耦合,效率低下。真实应用极少使用这种方式。

这种模式是Java初学者最喜欢使用的方式,这种模式有如下两个坏处。

1.可扩展性差。由于“人”组件与“斧头”组件的实现类高度耦合,当程序视图扩展斧头组件时,“人”组件的代码也要随之改变。

2.各组件职责不清。对于“人”组件而言,它只需要调用“斧头”组件的方法即可,并不关心“斧头”组件的创建过程。但在这种模式下,“人”组却需要主动创建“斧头”组件,因此职责混乱

第二种情况下,调用者无须关心被依赖对象的具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用者的代码面向接口编程,可以让调用者和被依赖对象的实现类解耦,这也是工厂模式被大量使用的原因。但调用者依然需要主动定位工厂,调用者与工厂耦合在一起。

第三种情况是最理想的情况:程序完全无须理会被依赖对象的具体实现,也无须主动定位工厂,这是一种优秀的解耦方式,实例之间的依赖关系由IoC容器负责管理。

由此可见,使用Spring框架之后的两个主要改变是:

->程序无须使用new 调用构造器去创建对象。所有的Java对象都可以交给Spring容器去创建。

->当调用者需要调用被依赖对象的方法时,调用者无须主动获取被依赖对象,只要等待Spring容器注入即可。


依赖注入通常有如下两种。

->设值注入:IoC容器使用成员变量的setter方法来注入被依赖对象。

->构造注入:IoC容器使用构造器来注入被依赖对象。