1、看墨攻理解 IoCSpring底层核心 摘自: http:/ 这篇文章写的很有意思,把晦涩难懂的术语用拍电影来表达,效果很不错,特此转载。 概述 IoC(反向控制: Inverse of Control)是 Spring容器的底层核心功能, AOP 功能、声明事务等功能在此基础上生根开花。但是 IoC 这个重要的概念却比较晦涩隐讳,拐弯抹角,不容易让人望文生义,不能不说是一大遗憾。不过 IoC 确实包括很多内涵,它涉及到代码解耦,设计模式优化等问题的考量。 2006 年多部贺岁大片以让人应接不暇的频率纷至沓来,其中张之亮的墨攻算是比较出彩的一部,讲述了战国时期墨家人革离帮助梁国反抗赵国侵略的个
2、人英雄主义故事,恢宏壮阔,浑雄凝重的历史场面相当震撼。其中有一 个场景:当刘德华所饰的墨者革离到达梁国都城下,城上梁国守军问: “来者何人? ”,刘德华回答: “墨者革离! ”,我们不妨用 Java对这段 “城门问对 ”的场景进行编剧并借由这个例子来理解 IoC 的内涵。 剧本和饰演者耦合 MoAttack代表墨攻的剧本, cityGetAsk()代表 “城门问对 ”这段剧情, LiuDeHua是具体饰演者刘天王: 代码清单 1 MoAttack:通过演员安排剧本 public class MoAttack public void cityGateAsk() LiuDeHua ldh =new
3、 LiuDeHua(); 演员直接侵入剧本 ldh.responseAsk(“墨者革离! “); 我们会发现以上剧本在 处,作为具体饰演者的刘德华直接侵入到剧本中,使剧本和演员直接耦合在一起: 图 1 剧本和演员直接耦合 一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地遴选任何适合的演员,而非绑定在刘德华一人身上。通过以上的分析,我们知道需要为该剧本主人公革离定义一个接口,以角色进行剧情安排,饰演者实现角色的接口: 代码清单 2 MoAttack:引入剧本角色 publicclass MoAttack. publicvoid cityGa
4、teAsk() . GeLi geli =new LiuDeHua(); 引入革离角色接口 geli.responseAsk(“墨者革离! “); 通过接口开展剧情 在 处引入了剧本的角色 革离,剧本的情节通过角色展开,在拍摄时角色的事迹由演员表现,如 处所示。因此墨攻、革离、刘德华三者的类图关系如图 2所示 : 我们希望剧本和演员无关,可是,在图 2 中,我们看到 MoAttack 同时依赖于 GeLi 接口和 LiuDeHua类,并没有达到我们所期望的剧本仅依赖于角色的目的。可是角色最终又必须通过具体的演员才能完成拍摄,如何将让 LiuDeHua 和剧本无关而又能完成 GeLi 的具体动作
5、呢?当然是在影片投拍时,导演将 LiuDeHua 安排 在 GeLi 的角色上,通过导演之手将剧本、角色、饰演者装配起来。 图 3 剧本和饰演者解耦了 通过引入导演,剧本和具体的饰演者解耦了,对应到软件中,导演象是一个装配器,将具体的饰演者赋给了剧本的角色。 现在我们可以反过来讲解 IoC的概念了。 IoC( Inverse of Control)的字面意思是控制反转,它包括两个层面的内容:其一是 “控制 ”,其二是 “反转 ”,到底是什么东西的控制被反转了呢?对应到前面的例子, “控制 ”是指 GeLi 角色扮演者的选择控制权, “反转 ”是指这种选择控制权从墨攻剧本中移除,转交到导演的手中
6、。对于程序来说,即是某一接口具体实现类的选择控制权从客户类中移除,转交给第三方来确定,客户类不知道是哪个具体的实现类,它通过接口方法对实现类进行调用。 因为 IoC 确实不够开门见山,因此业界曾进行了广泛的讨论,最终软件界的泰斗级人 物Martin Fowler提出了 DI(依赖注入: Dependency Injection)的概念,即将客户类对接口实现类的依赖关系由第三方(容器或协作类)注入,以移除客户类对具体接口实现类的依赖。“依赖注入 ”的概念显然比 “控制反转 ”直接达意,易于理解。 IoC 的三种类型 从注入方法上看,主要可以划分为三种的注入类型,分别是构造函数注入、属性注入和接口
7、注入, Spring支持构造函数注入和属性注入。下面我们继续使用以上的例子说明这三种注入方法的区别。 构造函数注入 我们通过客户类的构造函数, 将接口实现类通过接口变量传入,如代码清单 3 3所示: 代码清单 3 MoAttack:通过构造函数注入革离扮演者 publicclass MoAttack private GeLi geli; public MoAttack(GeLi geli) 注入革离的具体扮演者 this.geli = geli; publicvoid cityGateAsk() geli.responseAsk(“墨者革离! ”); MoAttack的构造函数不关心具体是谁扮
8、演革离这个角色,只要在 处传入的扮演者按剧本要求完成角色功能即可。 角色的具体扮演者由导演来安排,如代码清单 3 4所示: 代码清单 4 Director:通过构造函数注入革离扮演者 publicclass Director publicvoid direct() GeLi geli =new LiuDeHua(); 指定角色的扮演者 MoAttack moAttack =new MoAttack(geli); 注入具体扮演者到剧本中 moAttack.cityGateAsk(); 在 处,导演安排刘德华饰演革离的角色,并在 处,将刘德华 “注入 ”到墨攻的剧本中,然后开始 “城门问答 ”剧段
9、的演出工作。 属性注入 有时,导演会发现,虽然革离是影片墨攻的第一主人公,但并非每场戏都需要革离的出现,通过构造函数方式注入显得很不妥当,在这种情况下, 可以使用属性注入进行改造。属性注入通过 setter方法完成客户类所需依赖的注入,更灵活,更方便。 代码清单 5 MoAttack:通过 setter方法注入革离扮演者 publicclass MoAttack private GeLi geli; publicvoid setGeli(GeLi geli) 属性注入方法 this.geli = geli; publicvoid cityGateAsk() geli.responseAsk(“
10、墨者革离 “); MoAttack在 处为 geli属性提供一个 setter方法,以便让导演在拍需要革离的戏时才将注入 geli的具体扮演者,而不需要刘德华从头到尾跟着墨攻剧组跑。 代码清单 6 Director:通过 setter方法注入革离扮演者 publicclass Director publicvoid direct() GeLi geli =new LiuDeHua(); MoAttack moAttack =new MoAttack(); moAttack.setGeli(geli); 调用属性 setter方法注入 moAttack.cityGateAsk(); 和通过构造函
11、数注入革离扮演者不同,在实例化 MoAttack时,并未指定任何扮演者,而是在实例化 MoAttack 后,调用其 setGeli()方法注入扮演者。按照类似的方式,我们还可以为剧本中其他如巷淹中,梁王等角色分别提供注入的 setter方法,导演即可以根据所拍剧段的不同,注入所需要的角色了。 接口注入 将客户类所有注入的方法抽取到一个接口中,客户类通过实现这一接口提供注入的方法。为了采取接口注入的方式,需要声明一个额外的接口: publicinterface ActorArrangable void injectGeli(GeLi geli); 然后, MoAttack实现这个接口并实现接口中
12、的方法: 代码清单 7 MoAttack:通过接口方法注入革离扮演者 publicclass MoAttack implements ActorArrangable private GeLi geli; publicvoid injectGeli (GeLi geli) 实现接口方法 this.geli = geli; publicvoid cityGateAsk() geli.responseAsk(“墨者革离 “); Director通过 ActorArrangable的 injectGeli()方法完成扮演者的注入工作。 代码清单 8 Director: 通过接口方法注入革离扮演者 pu
13、blicclass Director publicvoid direct() GeLi geli =new LiuDeHua(); MoAttack moAttack =new MoAttack(); moAttack. injectGeli (geli); moAttack.cityGateAsk(); 由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性 注入并无本质区别,因此我们不提倡这种方式。 通过容器完成依赖关系的建立 虽然 MoAttack和 LiuDeHua实现了解耦,无需关注实现类的实例化工作,但这些工作在代码中依然存在,只是转移到 Director中而已,
14、导致导演的权力非常大,潜规则不断滋生。假设某一制片人想改变这一局面,在相中某个剧本后,通过一个 “海选 ”或者第三公正中介来选择导演、演员,让他们各司其职,那剧本、导演、演员就都实现解耦了。 所谓媒体 “海选 ”和中介机构在程序领域即是一个第三方容器,它帮助我们完成类的初始化和装配工作,让我们从这些 底层的实现类实例化,依赖关系的装配中脱离出来,专注于更有意思的业务代码的编写工作,那确实是挺惬意的事情。 Spring就是这样一个容器,它通过配置文件描述类之间的依赖关系,下面是 Spring配置文件的对以上实例进行配置的样式代码: 通过 new XmlBeanFactory(“beans.xml”)等方式即可启动容器,在容器启动时, Spring 根据配置文件的描述信息,通过 Java的反射机制自动实例化 Bean并完成依赖关系的建立,从容器中即可返回准备就绪的 Bean实例,以待后续的使用。 小结 随着 Spring 的广泛应用, IoC 的概念被越来越多的提及,可是很多说者因为 IoC 本身的晦涩往往并不清楚其中的意义,我们通过时下叫座的贺岁大片墨攻来解释 IoC 的概念,相信对加深 IoC概念的理解有所补益。