引述:IoC(控制反转:Inverse of Control)是Spring容器的内核,AOP、声明式事务等功能在此基础上开花结果。但是IoC这个重要的概念却比较晦涩隐讳,不容易让人望文生义,这不能不说是一大遗憾。不过IoC确实包括很多内涵,它涉及代码解耦、设计模式、代码优化等问题的考量,我们打算通过一个小例子来说明这个概念。

通过实例理解IoC的概念

贺岁大片在中国已经形成了一个传统,每到年底总有多部贺岁大片纷至沓来让人应接不暇。在所有贺岁大片中,张之亮的《墨攻》算是比较出彩的一部。该片讲述了战国时期墨家人革离帮助梁国反抗赵国侵略的个人英雄主义故事,恢宏壮阔、浑雄凝重的历史场面相当震撼。其中有一个场景:当刘德华所饰演的墨者革离到达梁国都城下,城上梁国守军问到:“来者何人?”刘德华回答:“墨者革离!”我们不妨通过一个Java类为这个“城门叩问”的场景进行编剧,并借此理解IoC的概念:
代码清单3-1  MoAttack:通过演员安排剧本

  1. public class MoAttack {
  2. public void cityGateAsk(){
  3. //①演员直接侵入剧本
  4. LiuDeHua ldh = new LiuDeHua();
  5. ldh.responseAsk("墨者革离!");
  6. }
  7. }
public class MoAttack {
public void cityGateAsk(){
//①演员直接侵入剧本
LiuDeHua ldh = new LiuDeHua();
ldh.responseAsk("墨者革离!");
}
}

我们会发现以上剧本在①处,作为具体角色饰演者的刘德华直接侵入到剧本中,使剧本和演员直接耦合在一起(图3-1)。

   一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地遴选任何适合的演员,而非绑定在刘德华一人身上。通过以上的分析,我们知道需要为该剧本主人公革离定义一个接口:
代码清单3-2  MoAttack:引入剧本角色

  1. public class MoAttack {
  2. public void cityGateAsk()
  3. {
  4. //①引入革离角色接口
  5. GeLi geli = new LiuDeHua();
  6. //②通过接口开展剧情
  7. geli.responseAsk("墨者革离!");
  8. }
  9. }
public class MoAttack {
public void cityGateAsk()
{
//①引入革离角色接口
GeLi geli = new LiuDeHua(); //②通过接口开展剧情
geli.responseAsk("墨者革离!");
}
}

在①处引入了剧本的角色——革离,剧本的情节通过角色展开,在拍摄时角色由演员饰演,如②处所示。因此墨攻、革离、刘德华三者的类图关系如图 3 2所示:

   可是,从图3 2中,我们可以看出MoAttack同时依赖于GeLi接口和LiuDeHua类,并没有达到我们所期望的剧本仅依赖于角色的目的。但是角色最终必须通过具体的演员才能完成拍摄,如何让LiuDeHua和剧本无关而又能完成GeLi的具体动作呢?当然是在影片投拍时,导演将LiuDeHua安排在GeLi的角色上,导演将剧本、角色、饰演者装配起来(图3-3)。

通过引入导演,使剧本和具体饰演者解耦了。对应到软件中,导演像是一个装配器,安排演员表演具体的角色。
   现在我们可以反过来讲解IoC的概念了。IoC(Inverse of Control)的字面意思是控制反转,它包括两个内容:

  • 其一是控制
  • 其二是反转

那到底是什么东西的“控制”被“反转”了呢?对应到前面的例子,“控制”是指选择GeLi角色扮演者的控制权;“反转”是指这种控制权从《墨攻》剧本中移除,转交到导演的手中。对于软件来说,即是某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。
   因为IoC确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人物Martin Fowler提出了DI(依赖注入:Dependency Injection)的概念用以代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。“依赖注入”这个名词显然比“控制反转”直接明了、易于理解。

IoC的类型

从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。下面我们继续使用以上的例子说明这三种注入方法的区别。

构造函数注入

在构造函数注入中,我们通过调用类的构造函数,将接口实现类通过构造函数变量传入,如代码清单3-3所示:
代码清单3-3  MoAttack:通过构造函数注入革离扮演者

  1. public class MoAttack {
  2. private GeLi geli;
  3. //①注入革离的具体扮演者
  4. public MoAttack(GeLi geli){
  5. this.geli = geli;
  6. }
  7. public void cityGateAsk(){
  8. geli.responseAsk("墨者革离!");
  9. }
  10. }
public class MoAttack {
private GeLi geli;
//①注入革离的具体扮演者
public MoAttack(GeLi geli){
this.geli = geli;
}
public void cityGateAsk(){
geli.responseAsk("墨者革离!");
}
}

MoAttack的构造函数不关心具体是谁扮演革离这个角色,只要在①处传入的扮演者按剧本要求完成相应的表演即可。角色的具体扮演者由导演来安排,如代码清单3-4所示:
代码清单3-4  Director:通过构造函数注入革离扮演者

  1. public class Director {
  2. public void direct(){
  3. //①指定角色的扮演者
  4. GeLi geli = new LiuDeHua();
  5. //②注入具体扮演者到剧本中
  6. MoAttack moAttack = new MoAttack(geli);
  7. moAttack.cityGateAsk();
  8. }
  9. }
public class Director {
public void direct(){
//①指定角色的扮演者
GeLi geli = new LiuDeHua(); //②注入具体扮演者到剧本中
MoAttack moAttack = new MoAttack(geli);
moAttack.cityGateAsk();
}
}

在①处,导演安排刘德华饰演革离的角色,并在②处,将刘德华“注入”到墨攻的剧本中,然后开始“城门叩问”剧情的演出工作。

属性注入

有时,导演会发现,虽然革离是影片《墨攻》的第一主角,但并非每个场景都需要革离的出现,在这种情况下通过构造函数注入相当于每时每刻都在革离的饰演者在场,可见并不妥当,这时可以考虑使用属性注入。属性注入可以有选择地通过Setter方法完成调用类所需依赖的注入,更加灵活方便:
代码清单3-5  MoAttack:通过Setter方法注入革离扮演者

  1. public class MoAttack {
  2. private GeLi geli;
  3. //①属性注入方法
  4. public void setGeli(GeLi geli) {
  5. this.geli = geli;
  6. }
  7. public void cityGateAsk() {
  8. geli.responseAsk("墨者革离");
  9. }
  10. }
public class MoAttack {
private GeLi geli;
//①属性注入方法
public void setGeli(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者革离");
}
}

MoAttack在①处为geli属性提供一个Setter方法,以便让导演在需要时注入geli的具体扮演者。
代码清单3-6  Director:通过Setter方法注入革离扮演者

  1. public class Director {
  2. public void direct(){
  3. GeLi geli = new LiuDeHua();
  4. MoAttack moAttack = new MoAttack();
  5. //①调用属性Setter方法注入
  6. moAttack.setGeli(geli);
  7. moAttack.cityGateAsk();
  8. }
  9. }
public class Director {
public void direct(){
GeLi geli = new LiuDeHua();
MoAttack moAttack = new MoAttack(); //①调用属性Setter方法注入
moAttack.setGeli(geli);
moAttack.cityGateAsk();
}
}

和通过构造函数注入革离扮演者不同,在实例化MoAttack剧本时,并未指定任何扮演者,而是在实例化MoAttack后,在需要革离出场时,才调用其setGeli()方法注入扮演者。按照类似的方式,我们还可以分别为剧本中其他诸如梁王、巷淹中等角色提供注入的Setter方法,这样,导演就可以根据所拍剧段的不同,注入相应的角色了。

接口注入

将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。为了采取接口注入的方式,必须先声明一个ActorArrangable接口:

  1. public interface ActorArrangable {
  2. void injectGeli(GeLi geli);
  3. }
public interface ActorArrangable {
void injectGeli(GeLi geli);
}

然后,MoAttack实现ActorArrangable接口提供具体的实现:
代码清单3-7  MoAttack:通过接口方法注入革离扮演者

  1. public class MoAttack implements ActorArrangable {
  2. private GeLi geli;
  3. //①实现接口方法
  4. public void injectGeli (GeLi geli) {
  5. this.geli = geli;
  6. }
  7. public void cityGateAsk() {
  8. geli.responseAsk("墨者革离");
  9. }
  10. }
public class MoAttack implements ActorArrangable {
private GeLi geli;
//①实现接口方法
public void injectGeli (GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者革离");
}
}

Director通过ActorArrangable的injectGeli()方法完成扮演者的注入工作。
代码清单3-8  Director:通过接口方法注入革离扮演者

  1. public class Director {
  2. public void direct(){
  3. GeLi geli = new LiuDeHua();
  4. MoAttack moAttack = new MoAttack();
  5. moAttack. injectGeli (geli);
  6. moAttack.cityGateAsk();
  7. }
  8. }
public class Director {
public void direct(){
GeLi geli = new LiuDeHua();
MoAttack moAttack = new MoAttack();
moAttack. injectGeli (geli);
moAttack.cityGateAsk();
}
}

由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,因此我们不提倡采用这种方式。

通过容器完成依赖关系的注入

虽然MoAttack和LiuDeHua实现了解耦,MoAttack无须关注角色实现类的实例化工作,但这些工作在代码中依然存在,只是转移到Director类中而已。假设某一制片人想改变这一局面,在选择某个剧本后,希望通过一个“海选”或者第三中介机构来选择导演、演员,让他们各司其职,那剧本、导演、演员就都实现解耦了。
   所谓媒体“海选”和第三方中介机构在程序领域即是一个第三方的容器,它帮助完成类的初始化与装配工作,让开发者从这些底层实现类的实例化、依赖关系装配等工作中脱离出来,专注于更有意义的业务逻辑开发工作。这无疑是一件令人向往的事情,Spring就是这样的一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。下面是Spring配置文件的对以上实例进行配置的配置文件片断:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
  7. <!--①实现类实例化-->
  8. <bean id="geli" class="LiuDeHua"/>
  9. <bean id="moAttack" class="com.baobaotao.ioc.MoAttack"
  10. p:geli-ref="geli"/><!--②通过geli-ref建立依赖关系-->
  11. </beans>
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--①实现类实例化-->
<bean id="geli" class="LiuDeHua"/>
<bean id="moAttack" class="com.baobaotao.ioc.MoAttack"
p:geli-ref="geli"/><!--②通过geli-ref建立依赖关系-->
</beans>

通过new XmlBeanFactory(“beans.xml”)等方式即可启动容器。在容器启动时,Spring根据配置文件的描述信息,自动实例化Bean并完成依赖关系的装配,从容器中即可返回准备就绪的Bean实例,后续可直接使用之。
   Spring为什么会有这种“神奇”的力量,仅凭一个简单的配置文件,就能魔法般地实例化并装配好程序所用的Bean呢?这种“神奇”的力量归功于Java语言本身的类反射功能。

转.....IOC 和DI的更多相关文章

  1. 深入理解DIP、IoC、DI以及IoC容器

    摘要 面向对象设计(OOD)有助于我们开发出高性能.易扩展以及易复用的程序.其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC.DI以及Ioc容器等概念.通过本文我们将一起学 ...

  2. 初识Spring框架实现IOC和DI(依赖注入)

    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的, IoC是 ...

  3. 对IOC和DI以及AOP的理解

    为了理解Spring的IoC与DI从网上查了很多资料,作为初学者,下面的描述应该是最详细,最易理解的方式了. 首先想说说IoC(Inversion of Control,控制倒转).这是spring的 ...

  4. Spring4学习笔记1-HelloWorld与IOC和DI概述

    1.安装环境 1.1安装eclipse,jdk 1.1安装Spring tool suite(非必要项) 2.spring HelloWorld 2.1 需要的jar包(spring官网下载:http ...

  5. IoC和DI的理解

    1 概述 当我们想闭上眼睛想如何让我们的软件更加可用可维护时,我们总能想到一个词:松耦合.在这篇文章中,主要讲述了模块间存在的依赖关系,但这种依赖关系违背了依赖倒置原则.在这之后,我们将讨论一种解除软 ...

  6. Spring控制反转与依赖注入(IOC、DI)

    IOC: 反转控制   Inverse Of Control DI:依赖注入 Dependency Injection 目的:完成程序的解耦合 解释:在应用系统的开发过程中,有spring负责对象的创 ...

  7. [原创]java WEB学习笔记97:Spring学习---Spring 中的 Bean 配置:IOC 和 DI

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  8. 对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解

    1.概述 所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模 ...

  9. 深入理解DIP、IoC、DI以及IoC容器(转)

    深入理解DIP.IoC.DI以及IoC容器 摘要 面向对象设计(OOD)有助于我们开发出高性能.易扩展以及易复用的程序.其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC.D ...

  10. 【转】深入理解DIP、IoC、DI以及IoC容器

    原文链接:http://www.cnblogs.com/liuhaorain/p/3747470.html 前言 对于大部分小菜来说,当听到大牛们高谈DIP.IoC.DI以及IoC容器等名词时,有没有 ...

随机推荐

  1. ios项目开发(天气预报项目):通过经纬度获取当前城市名称

     1 . 在项目里加入� CoreLocation.framework 2 .在 .h 文件输入例如以下: 1.#import <CoreLocation/CLLocation.h> ...

  2. 给定表达式[x/2] + y + x * y, 其中x,y都是正整数。

    改进了一下,不过还是要十多秒吧. package com.boco.study; import java.math.BigDecimal; import java.util.Calendar; imp ...

  3. careercup-中等难题

    17.1 编写一个函数,不用临时变量,直接交换两函数. 解法: 方法一:这个是经典面试题,也相当直接.我们将用a0表示a的初值,b0表示b的初始值,用diff表示a0-b0的值. 让我们将a>b ...

  4. PAT 1012

    1012. The Best Rank (25) To evaluate the performance of our first year CS majored students, we consi ...

  5. JavaScript标准Selection操作

    简介 术语 属性 方法 document.activeElement document.designMode = 'on'; 简介 selection是对当前激活选中区(即高亮文本)进行操作. 在非I ...

  6. 聊一聊ES5数组(Array)新增的那些方法

    一.前言 ES5中新增的一些处理数组(Array)的方法, 对于用JavaScript处理数据非常有用.我总结了一下,给这些方法分了类,大体如下: 2个索引方法:indexOf() 和 lastInd ...

  7. 绿荫工作室爱选修app内测

    下载地址:http://greendasungta.com/greencms/ixuanxiu.apk 开发目的是用于方便在校生的选课操作以及对选修课的交流.

  8. Android_baseComponentExample

    xml布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns ...

  9. Convert Date between LocalDateTime

    http://blog.progs.be/542/date-to-java-time Java8 has new date and time classes to “replace” the old ...

  10. mvn打包

    mvn install -DskipTests mvn install -Dmaven.test.skip=true