[关闭]
@Purpose 2017-06-11T04:18:16.000000Z 字数 3868 阅读 1792

Spring入门之旅: 控制反转(IoC)与依赖注入(DI)

Spring


前言

Spring框架核心是IoC容器,所以学习Spring框架就要先理解控制反转的思想以及依赖注入的实现方法。一开始我觉得这些名词挺高大上,然而在拜读了肖汉松老师的博客:
“控制反转(IoC)与依赖注入(DI)”之后,发现这些但其实是不难理解的,下面我们通过探讨一下问题来认识和学习IoC和DI之间的关系以及在Spring中的应用。


什么是控制反转

首先,我们来讨论一下耦合这个词。这个词语相信各位都不陌生,下面这幅图就非常形象地描述了在软件开发中耦合发生的情况
此处输入图片的描述
在上图,每一个齿轮就代表我们开发中的一个对象,在图中我们可以看到,每个齿轮之间都耦合起来了,彼此依赖,协同工作。但是这样做的问题也非常明显:一旦某个齿轮坏了不能运转,就会导致整个系统的崩溃,这就是强耦合系统的致命的问题。
然而多耦合的情况我们是无法避免的,这是协同工作的基础,而且随着工程应用的规模变大,依赖关系也会变得越来越复杂,很容易就牵一发而动全身。

为了解决这个问题,大师Michael Mattson提出了IoC理论,用来实现对象之间的解耦

控制反转(Inversion of Control)是一种面向对象编程中的一种设计思想,它的基本思想就是:借助于“第三方”实现具有依赖关系的对象之间的解耦。
此处输入图片的描述
由于引进了中间位置的“第三方”,也就是IoC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心。

我们再来看看,控制反转(IoC)到底为什么要起这么个名字?我们来对比一下:

  1. 软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
  2. 软件系统在引入IOC容器之后,这种情形就完全改变了,如图2所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
    通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

其实与其说控制反转是一种编程思想,不如说它是一种管理模式。生活中控制反转,依赖倒置的思想应用其实更为广泛。

海尔公司作为一个电器制商需要把自己的商品分销到全国各地,但是发现,不同的分销渠道有不同的玩法,于是派出了各种销售代表玩不同的玩法,随着渠道越来越多,发现,每增加一个渠道就要新增一批人和一个新的流程,严重耦合并依赖各渠道商的玩法。实在受不了了,于是制定业务标准,开发分销信息化系统,只有符合这个标准的渠道商才能成为海尔的分销商。让各个渠道商反过来依赖自己标准。反转了控制,倒置了依赖。

我们把海尔和分销商当作软件对象,分销信息化系统当作IOC容器,可以发现,在没有之前分销信息化系统,分销商就像图1中的齿轮一样,增加一个齿轮就要增加多种依赖在其他齿轮上,势必导致系统越来越复杂。开发分销系统之后,所有分销商只依赖分销系统,就像图2显示那样,可以很方便的增加和删除齿轮上去。

什么是依赖注入

依赖注入就是将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。

什么是依赖

在Class A中,有Class B的一个实例,就叫做Class A 对 Class B 有一个依赖。比如下面的类 Human 中,有一个Man的对象,那么就类 Human 对类 Man有一个依赖。

  1. public class Human {
  2. ...
  3. Man man;
  4. ...
  5. public Human() {
  6. man = new Man();
  7. }
  8. }

我们不难看出以上代码存在的问题:

  1. 如果现在要改变 man 生成方式,如需要用new Man(String name)初始化 father,需要修改 Human 代码;
  2. 如果想测试不同 Man 对象对 Human 的影响很困难,因为 man 的初始化被写死在了 Human 的构造函数中;
  3. 如果new Man()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象 Mock 掉这个过程也很困难。

依赖注入

现在我们改进一下,把两个类进行解耦

  1. public class Human {
  2. ...
  3. Man man;
  4. ...
  5. public Human(Man man) {
  6. this.man = man;
  7. }
  8. }

在上面代码中,类Man 的实例对象 man 是在Human类外构建好的,也就是在调用Human 的构造方法之前,就已经在外部实例化好了Man 对象,而无需Human在自己的内部自行构建。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。
对比这两段代码,我们就能体会到依赖注入的好处:

  1. 解耦,将依赖之间解耦。
  2. 因为已经解耦,所以方便做单元测试,尤其是 Mock 测试。

控制反转和依赖注入的关系

上面已经分别解释了控制反转和依赖注入的概念。有些人会把控制反转和依赖注入等同,但实际上它们有着本质上的不同。

他们之间的关系有点像类和实例对象之间的关系,IoC框架使用依赖注入作为实现控制反转的方式,但是控制反转还有其他的实现方式,例如说ServiceLocator,所以不能将控制反转和依赖注入等同。

Spring中的依赖注入

现在,我们结合Spring中的IoC容器,简单描述一下过程。

  1. class MovieLister...
  2. private MovieFinder finder;
  3. public void setFinder(MovieFinder finder) {
  4. this.finder = finder;
  5. }
  6. class ColonMovieFinder...
  7. public void setFilename(String filename) {
  8. this.filename = filename;
  9. }

上面的代码中定义了两个类,我们可以看出,他们都用了依赖注入的方式,通过外部传入依赖,而不是自己创建依赖。那么,问题来了,谁把依赖传给他们的呢?也就是说,谁负责创建finder,并把finder传给MovieLister?答案是: Spring的IoC容器。

Spring中使用IoC容器是要用xml进行配置的,当然,也可以用代码注解来进行配置,下面是spring.xml的内容

  1. <beans>
  2. <bean id="MovieLister" class="spring.MovieLister">
  3. <property name="finder">
  4. <ref local="MovieFinder"/>
  5. </property>
  6. </bean>
  7. <bean id="MovieFinder" class="spring.ColonMovieFinder">
  8. <property name="filename">
  9. <value>movies1.txt</value>
  10. </property>
  11. </bean>
  12. </beans>

在Spring中,每个bean代表一个对象的实例,默认是单例模式,即在程序的生命周期内,所有的对象都只有一个实例,进行重复使用。通过配置bean,IoC容器在启动的时候会根据配置生成bean实例。具体的配置语法参考Spring文档。这里只要知道IoC容器会根据配置创建MovieFinder,在运行的时候把MovieFinder赋值给MovieListerfinder属性,完成依赖注入的过程。

测试代码

  1. public void testWithSpring() throws Exception {
  2. ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");//1
  3. MovieLister lister = (MovieLister) ctx.getBean("MovieLister");//2
  4. Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
  5. assertEquals("Once Upon a Time in the West", movies[0].getTitle());
  6. }
  1. 根据配置生成ApplicationContext,即IoC容器。
  2. 从容器中获取MovieLister的实例。

总结

  1. 控制反转是一种在软件工程中解耦合的思想,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。
  2. 依赖注入是一种设计模式,可以作为控制反转的一种实现方式。依赖注入就是将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。
  3. 通过IoC框架,类A依赖类B的强耦合关系可以在运行时通过容器建立,也就是说把创建B实例的工作移交给容器,类A只管使用就可以。

原文链接: 肖汉松: 控制反转(IoC)与依赖注入(DI)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注