反射是实现控制反转 依赖注入和依赖注入的根本基础,对吗

博客分类:
控制反转(Inversion of Control,IoC)
对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。所以,控制反转是,关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。在没有控制反转时,对象A如果需要调用对象B的方法,那么对象A要自己创建对象B,有了IoC后,容器负责创建B并注入A。
Question:
对于依赖倒置原则(Dependency Inversion Principle,DIP),依赖注入(Dependency Injection,DI)和控制反转(Inversion of Control,IoC)容器,你是怎么理解的?
Answer:
依赖倒置原则(Dependency Inversion Principle, DIP)。这个设计准则某种程度上和依赖注入模式有些关联。DIP的出发点是:在应用开发中,高层模块不应当直接依赖低层模块。DIP并不意味着依赖注入。这个准则并没有讲到高层模块如何知道调用哪个低层模块。不过这一点通过实现工厂模式接口可以间接获知,或者通过类似Spring框架、Pico容器、Guice或者Apache HiveMind之类的loC容器实现依赖注入从而得知高层模块调用的具体哪个低层模块。
DIP意味着:
高层模块不应当依赖低层模块,它们都应当依赖抽象。
抽象不应该依赖具体实现。具体实现应该依赖抽象。
应用这个准则后,高层模块并不直接同低层模块交互,而是通过一个抽象层来跟低层模块进行交互。这使得需求变更后增加的成本更加灵 活可控。这里有些实现DIP的示例代码片段。
首先定义抽象层:
package principle_dip2;
public interface AnimalHandler {
&&& public abstract void handle( );
}
package principle_dip2;
public interface AnimalHelper {
&&& public abstract void help( );
}
接着是依赖于抽象类而非具体实现的高层代码。
package principle_dip2;
public class CircusService {
&&& AnimalH
&&& public void setHandler(AnimalHandler handler) {
&&&&&&& this.handler =
&&& }
&&& public void showStarts( ) {
&&&&&&& //code omitted for brevity
&&&&&&& handler.handle( );
&&& }
}
package principle_dip2;
public class TigerHandler implements AnimalHandler{
&&& AnimalH
&&& public void setHelper(AnimalHelper helper) {
&&&&&&& this.helper =
&&& }
&&& public void handle( ){
&&&&&&& //...
&&&&&&& helper.help( );
&&&&&&& //...
&&& }
}
package principle_dip2;
public class TigerHelper implements AnimalHelper{
&&& public void help( ){
&&&&&&& //......
&&& }
}
依赖注入模式(Dependency Injection):在运行时将类的依赖注入到代码中。通过将依赖定义为接口,并将实现这个接口的实体类注入到主类的构造器中来实现这个模式。这允许程序员在不同的实现之间转换而不用去修改主类。依赖注入模式可以通过单一责任原则(Single Responsibility Principle)SRP来使得代码高内聚(high cohesion),因为所依赖的通常都是完成独立的功能的对象,例如,(通过DAO进行)数据存取或(通过Service和Delegate类实现)业务服务。
控制反转容器(Inversion of Control Container,IoC),是一个支持依赖注入的容器。这种方式下,可以采用一个中心容器,例如Spring框架,Guice或者HiveMind,来定义哪个依赖应该使用哪个实体类。Ioc的松耦合性可以带来更多的灵活性,并且在程序运行时更容易去切换到正确的依赖对象上。控制反转模式的基本概念是,不去实际生成对象,而是去定义如何生成对象。不用直接在代码中将模块和服务硬编码在一起,而是在配置文件中描述哪个模块需要哪个服务。容器(例如Spring框架这个IoC容器)会负责将这两者绑定起来。应用IoC的时候,某对象所需的依赖会在创建的时候通过外部实体传入,这些外部实体用来协调系统中的不同对象。也就是说,依赖是被注入到对象中去的。因此,IoC就是关于一个对象如何获得其协作对象的引用的一种责任反转机制。
DI和IoC的真正强大之处在于,在运行时而非编译时绑定类间关系。例如,在Seam框架中,你可以对一个接口进行两种实现:真正的实现和模拟(mock)的实现,而在运行时根据某个属性、另一个文件存在与否或者某个优先值去决定真正调用哪一个实现。这尤其当你希望程序在不同场景下表现不同的行为时,这是非常好用的。DI和IoC的另外一个好处是,使得代码更容易进行单元测试。当然也有其他一些好处,例如,不用使用工厂或者单例模式就可以实现松耦合,其实现方法一致因此适合缺乏经验的程序员,等等。当然,享受这些好处是要付出代价的,例如系统复杂性会随之增加,另外在使用时也需要更加小心,不能因为这个技术受欢迎就滥用,而是在能够真正体现其优势的地方才去使用。
注意:上下文依赖注入(Contexts and Dependency Injection)是用来描述标准依赖注入的一个尝试。CDI是Java EE 6 stack的一部分,也就是说任何一个运行在Java EE 6兼容容器之上的应用都可以轻松使用CDI。Weld就是CDI的一个可参考的实现。
浏览: 289684 次
来自: 上海
如果把m换成具体的数字,比如4或者5,会让读者更明白
不错,加油,多写点文章
你好,我用sencha cmd打包完本地工程后,把app.js ...
lwpan 写道inverse = &true&qu ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'博客访问: 793990
博文数量: 248
博客积分: 10010
博客等级: 上将
技术积分: 2415
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
昨天经过一朋友的SPACE,看到有关于控制反转的讨论,一时技痒,写下一段留言,完后由于比较长的时间没接触这几个单词,因此又去查了些资料,重新整理了一下,跟大家一起讨论。
&& 整理之前,首先要说说“依赖”,什么是依赖,依赖就是关联,UML中定义的“关联”是最泛泛的一种关系,表现为两个类图之间有根线就有关联,我个人理解成,在C/C++中,A include了另一个头文件B,JAVA/.Net中A using了另一个package或者unit B,则两者就有了关联,A依赖于B,因为假设没有依赖关系,A为啥要include B?肯定了发生了某种调用(B.Call())或者引用(如B做为某个类变量或者参数变量)。所以偶把这个理解成依赖。
&& 网上讨论的假设B发生变化那A也发生变化成为依赖,偶觉得可能会有一定的误导,因为假设变化的是B内部,接口不变,那A为什么要变?或者B增加一个接口A不调用,A也不用变,但是A依然依赖于B。
&& 这是关于依赖,接下来是关于标题三个名词,在这里不想一个个去解释,因为那可能有种就事论事的感觉,想跟大家讨论一些比较本质的东西。
&& 在面对一个复杂事务的时候,我们的处理办法是什么?毫无疑问是分解,想想那些日理万机的领导,凡事不论巨细都要过问的话那是不可能,因为个人的精力有限,而复杂度是随着规模的增大而呈数量级的变化的,所以领导怎么处理?分解,分成市场总监,财务总监,技术总监,行政总监等一个个角色,每个角色负责一块,到时候跟领导汇报各自的问题就OK了,领导来做决策。
&&&&& 以软件来类比的话,这里的总监就是一个个模块,汇报就是OO中的接口(Interface),领导就是框架,把全部的模块串起来完成一个既定的大目标(如实现某个方案)。那工程师或者财务人员呢?毫无疑问就是一个个具体的CLASS了,是实现具体某个功能的执行体,如一项编程工作或者整理出某个报表。
&&& 由此可见,每天在我们周围发生的这些事情,这些公司内部的一些行为都可以映射成整个软件构架。那么从公司的这种组织行为我们可以得到什么样的思考?首先有了分解我们可以降低一些的复杂度,但接下来呢,软件最复杂的在于什么?在于变化。公司也一样,外界要面临重重生存压力,内部可能会有一些公司层面的或者个人层面的问题,甚至人员跳槽也会带来一定的风险,那公司怎么处理? 隔离。把容易变化的跟不容易变化的相对稳定的隔离开来,这样就能做到控制影响到最小。那么隔离通过什么来实现?抽象。为什么?因为抽象是相对稳定的。
&&& 这里举个比较好笑的例子:一个即将做领导的儿子问曾经做领导的父亲怎么才能平步青云,父亲说你不能说假话,因为老百姓会不答应,也不能说实话,因为领导会对你有意见,儿子思索良久问:那我该说什么? 父亲意味深长的说:空话
&& 笑话不但好笑,还能反映一定的道理,为啥空话这么有用,因为他是抽象的,而抽象不涉及到一些具体的数据或者事务,因此他是稳定的,是不容易变化的,同时基本上也是正确的。所以我们经常在公开场合听到类似的话“我们要团结同志,努力进取,提高工作效率,降低工作成本。。。”你能说这是错的吗,当然不能,所以既然是非常稳定的对的,所以这种依赖就很可靠啦。
&&&公司也一样,假设领导依赖于工程师的能力,成天问这个问那个,那如果有一天工程师跳槽了怎么办?领导处理的都是一些非常重要宏观的事务,不可能因为某个小小的工程师而产生什么大的影响,因此他不会直接依赖工程师,而是依赖于一个叫技术总监这样的一个抽象。如果工程师离职,那换个工程师继续替代他以前做的事情就行了,而且都实现的是技术总监规定好的接口,这样对领导就没什么影响了,也只有这样变化就被隔离开来了。
&& 再反过来想想软件,其实上面描述的都是一些对软件而言非常有意义的做法,OO中非常重要的一点就是模块之间依赖于抽象的接口,而不是具体的实现,为啥,因为抽象是相对稳定的。一个IO他必然就有READ/WRITE这两个抽象,至于具体是磁盘还是键盘,那是下面的实现不同了,通过这种构架,能保持软件的弹性与可维护性。
&& 由公司的行为还有一点容易受到启发的就是公司的组织构架,公司的层次可以映射成软件的分层,领导是框架层,下面通过一个个接口去管控一个个CLASS。我们设计软件的时候毫无疑问也应该这样。设计好框架设计接口,设计好接口再去调用一个个的API或者CLASS去实现某一个具体的实现比如数据库的读写或者SOCKET数据的收发。每个地方有自己相对对立的职责,各尽其职。如果上来就是一个个API,那相当于一大群工程师既做商务谈判,又去编码,那就杂乱无章啦。这样的结果感觉都是一样:混乱。
&& 最后言归正传,谈谈上面的三个名词,依赖倒置DIP(Dependency Inversion Principle)在马丁大叔的杰作《敏捷开发:原则,实践与模式》中描述的比较清楚,高层模块不应该依赖于底层模块,而应该两者都依赖于一个抽象,底层模块实现抽象。相信这点已经在上面讨论的比较清楚了,就好比尽管领导归根结底要依赖工程师来做事,但他不会直接依赖你,而是依赖于一个总监的抽象。这就是倒置,哪里倒了?这种依赖关系倒了,引入了一个新的中间层,一个抽象。以前传统的过程设计中是从上到下的一条依赖线,现在是平的一条领导到总监,然后是一条从下往上的工程师到总监的关联线。
& 控制反转(Inversion of Control)其实有点类似,主要是OO中提出了框架的概念,什么是框架,按王氏兄弟的《道法自然》中的描述,主要是一个动态的环境体,可以处理一些比较复杂的事务或者逻辑,跟类库静态的行为不大一样,类库都是一个个等待别人调用的服务。 那么控制体现在什么地方?传统的做法,我们在自己的程序中调用一个个类库去完成一个个功能,类库是我们的执行体,但是逻辑算法等是自己实现的,这就是自己的控制,但框架不一样,逻辑算法都是它来实现,我们只要提供它几个需要的接口或者执行体就可。这就是反转,控制在框架这边了,主要是简化了一定的工作量,对于一些常见的业务场景不需要自己去重复实现罢了。那么控制反转主要应用的是模板方法等模式,个人觉得观察者模式是一个比较典型的控制发转的实例,因为论询observer是subject这边实现的一个逻辑,而observer 只要实现notify这样一个接口即可。所以其实也没什么。
&& 依赖注入(Dependency Injection)其实相比以上两者应该不是一个层次的概念,主要是表现为利用构造函数或者接口来完成具体的工作类的绑定而已,这么想好了,比如要完成一个开发工作,可以让总监自己去指定工程师甲来完成这个工作,但为了更大的弹性,可以让总监提供这么个接口
AssignJob(Engineer engineer) {engineer.do();}
这样总监可以默认让甲去完成这个工作,但如果某种原因例如甲请假,那么就可以通过这个接口去让乙去做这件事情,这样在发生变化的时候就有弹性了。顺便提一下,软件多数通过CONFIG来达到更大的灵活性,由于.NET/JAVA等引入了反射的概念,可以在RUNTIME期间动态的去创建类,这个功能就很强大了,参考如上的业务需求,我们就可以把类名放在CONFIG中,通过反射去加载不同的类从而完成不同的实现。这也是很多框架(如structs)的最基本的做法
&&& 以上是一些个人看法,感觉一些OO的名词听起来玄乎,其实也就那么回事,只要掌握好OO的几个基本原则,其实基本上都可以推导出来。呵呵。个人愚见,希望跟大家交流
&& 附上王氏兄弟的更精彩精辟的讲解:
阅读(1546) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。  控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给外部容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是组件对象的控制权转移了,从程序代码本身转移到了外部容器。
  IoC(Inversion of Control)是近年来兴起的一种思想,不仅仅是编程思想。主要是协调各组件间相互的依赖关系,同时大大提高了组件的可移植性,组件的重用机会也变得更多。在传统的实现中,由程序内部代码来控制程序之间的关系。我们经常使用new关键字来实现两对象组件间关系的组合,这种实现的方式会造成组件之间耦合(一个好的设计,不但要实现代码重用,还要将组件间关系解耦)。IoC很好的解决了该问题,它将实现组件间关系从程序内部提到外部容器来管理。也就是说由容器在运行期将组件间的某种依赖关系动态的注入到组件中。控制程序间关系的实现交给了外部的容器来完成。即常说的好莱坞原则“Don't
call us, we'll call you”(你呆着别动,到时我会找你)。
  我们知道,如果Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
IoC实现方法
  实现控制反转主要有两种方式:依赖注入和依赖查找。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取响应名称的对象,获得依赖对象的时间也可以在代码中自由控制。IoC的实现与语言无关。用各种语言,如C++, Java, C#等都可以。这里主要以Java为例。
  依赖注入有如下实现方式:
基于接口。实现特定接口以供外部容器注入所依赖类型的对象。接口中定义要注入依赖对象的方法。基于setter方法。实现特定属性的public set方法,来让外部容器调用,以传入所依赖类型的对象。如Spring Framework,WebWork/XWork。基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。如PicoContainer,HiveMind。基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。
  依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。这主要是通过JNDI或ServiceManager等获得依赖对象,这类似于Martin Fowler说的ServiceLocator模式。 如EJB,Avalon(Apache的一个复杂使用不多的项目),就是用依赖查找。
图1 控制反转的实现方式
  依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。
  下面分别演示3中主要的注入机制。待注入的业务对象如下。
package com.zj.ioc.
public class Content {
public void BusniessContent(){
System.out.println(&do business&);
public void AnotherBusniessContent(){
System.out.println(&do another business&);
}  MyBusniess类展示了一个业务组件,它的实现需要对象Content的注入。下面代码分别演示构造子注入(ConstructorInjection),设值注入(SetterInjection)和接口注入(InterfaceInjection)三种方式。
  1、构造子注入
package com.zj.ioc.di.
import com.zj.ioc.di.C
public class MyBusiness {
private Content myC
public MyBusiness(Content content) {
myContent =
public void doBusiness(){
myContent.BusniessContent();
public void doAnotherBusiness(){
myContent.AnotherBusniessContent();
}  2、设值注入
package com.zj.ioc.di.
import com.zj.ioc.di.C
public class MyBusiness {
private Content myC
public void setContent(Content content) {
myContent =
public void doBusiness(){
myContent.BusniessContent();
public void doAnotherBusiness(){
myContent.AnotherBusniessContent();
}  3、接口注入
  定义注入接口InContent,里面有注入Content对象的方法。
package com.zj.ioc.di.
import com.zj.ioc.di.C
public interface InContent {
void createContent(Content content);
}  业务类需要实现这个接口来进行注入。
package com.zj.ioc.di.
import com.zj.ioc.di.C
public class MyBusiness implements InContent{
private Content myC
public void createContent(Content content) {
myContent =
public void doBusniess(){
myContent.BusniessContent();
public void doAnotherBusniess(){
myContent.AnotherBusniessContent();
  下面代码展示了基于JNDI实现的依赖查找机制。
public class MyBusniessObject{
&span style=&font-family:&;&&
&/span&private DataS
private MyCollaborator myC
&span style=&font-family:&;&&
&/span&public MyBusnissObject(){
&span style=&font-family:&;&&
&/span&Context ctx =
&span style=&font-family:&;&&
&/span&try{
ctx = new InitialContext();
ds = (DataSource) ctx.lookup(“java:comp/env/dataSourceName”);
myCollaborator =
(MyCollaborator) ctx.lookup(“java:comp/env/myCollaboratorName”);
……  依赖查找的主要问题是,这段代码必须依赖于JNDI环境,所以它不能在应用服务器之外运行,并且如果要用别的方式取代JNDI来查找资源和协作对象,就必须把JNDI代码抽出来重构到一个策略方法中去。
Spring 中的控制反转(IoC)
  (1)IoC = Inversion of Control(由容器控制程序之间的关系)
  IoC,用白话来讲,就是由容器来控制程序中的各个类之间的关系,而非传统实现中,直接在代码中由程序代码直接操控。比如在一个类(A)中访问另外一个类中(B)的方法时,我们需要先去new 一个B的对象,然后调用所需的方法。他们的关系很显然在程序代码中控制,同时它们之间的耦合度也比较大,不利于代码的重用。而我们现在把这种控制程序之间的关系交给Ioc容器,让它去帮你实例化你所需要的对象,而你直接在程序中调用就可以了。这也就是所谓&控制反转&的概念的由来:控制权由应用程序的代码中转到了外部容器,控制权的转移,是所谓反转的由来。
  可能你不知道Ioc容器到底,或确切的指的是什么?其实这里的容器就相当于一个工厂一样。你需要什么,直接来拿,直接用就可以了,而不需要去了解、去关心你所用的东西是如何制成的,在程序中体现为实现的细节,这里就用到了工厂模式,其实Spring容器就是工厂模式和单例模式所实现的。
  对于初学者,我想简单的先说明几点,不要把applicationContext.xml或带有bean的配置文件理解为容器,它们只是描述了要用到的类(bean)之间的依赖关系。Spring中的容器很抽象,不像Tomcat, Weblogic, WebSphere等那样的应用服务器容器是可见的。Spring的Ioc容器给人的感觉好像就是那些配置文件(applicationContext.xml),我刚开始学时,也以为就是那些带bean的配置文件,虽然它对你学习Spring没什么影响,但如果想更深沉的了解就会迷茫的,我们在这里要正确理解Spring的Ioc容器,以后对我们学习会有很大的帮助的。其实它的容器是有一些类和接口来充当的,你可能又会很迷茫。这就是它与别的框架的不同之处,这一点也正在体现了它的无侵入性的一点,不像EJB需要专门的容器来运行,侵入性很大的重量级的框架。Spring只是一种轻量级的无侵入性的框架。说白了Spring的Ioc容器就是可以实例化BeanFactory或ApplicationContext(扩展了BeanFactory)的类。
  可能你对Ioc容器还是不太理解,慢慢来,刚刚接触的人都会很迷茫。我现在通过讲解一个例子来说明它的工作原理,你可能会恍然大悟,原来如此简单。
  首先打开你的IDE编译器,我使用NetBeans 8.0,它很好地集成了Spring的开发。
  我举了一个大家比较熟悉的例子,用户登录验证。如果用户名为admin,密码为1234,就会在控制台输出”恭喜你,登录成功!”,反之输出“对不起,登录失败!”,并在日志文件中记录登录信息。这里我用到的是log4j(日志记录器)。我严格按分层思想和Spring中提倡的按接口编程的思想来演练这个简单的例子。可能你会想这么简单的为什么要那么麻烦呢?怎么不用一个类就解决了,我们时刻要记住,我们的代码要易维护,可重用,易扩展,低耦合,高内聚。如果你了解这些原则,你自然会明白这样麻烦的好处了。
  为演示Spring的无侵入性特征,我们创建一个普通的Java项目,而不是Java Web项目,然后添加Spring Framework 4.0.1的类库。先整体的讲解一下工程中的组织结构,如下图。除了源码文件,还需要创建一个applicationContext.xml配置文件,用于指定bean之间的依赖关系。
图2 SpringChap2项目结构
  首先我们看一下UserLogin接口和它的实现部分UserLoginImpl:
package com.chap2;
public interface UserLogin {
public boolean login(String userName, String password);
}package com.chap2;
public class UserLoginImpl implements UserLogin{
public boolean login(String userName, String password){
if(userName.equals(&admin&) && password.equals(&1234&)){
}  上面的接口只是简单的定义了一个方法,用于验证用户身份是否合法。在实现中只是简单的数据比较,实际当中应该从数据库中去取数据进行验证的,我们只要能说明问题就可以了。
  然后在看一下业务逻辑层的UserService这里应该再对该类进行提取接口的,我只是写了一个类,如果你有兴趣的话,你可以在加上接口像上面的UserLogin一样,这样做是为了降低代码的耦合度,提高封装性。
package com.chap2.
import com.chap2.UserL
public class UserService {
private String userN
private UserLogin userL
* @param userName the userName to set
public void setUserName(String userName) {
this.userName = userN
* @param password the password to set
public void setPassword(String password) {
this.password =
* @param userLogin the userLogin to set
public void setUserLogin(UserLogin userLogin) {
this.userLogin = userL
public String validateUser(){
if(userLogin.login(userName, password)){
return &恭喜你,登录成功!&;
return &对不起,登录失败!&;
}  在这个业务方法里其实是对userLogin中的方法的再次封装,这里面可能隐藏的用到了正面封装的模式(Facade)或叫做门面模式。它的好处是为了不让客户直接访问UserLogin中的方法,在实际的开发中UserLogin的方法应该放在DAO层中,这里的DAO是数据访问层的意思,其实就是对数据库执行的CRUD(增,删,改,查)操作。大家不要因为我多讲了一些就迷。我们应该养成好的编程习惯,也就是分工明细。层与层之间只能通过接口访问。
  这里的前台客户端的调用,我没用到html, jsp之类的页面,只是用了一个main()方法简单的模拟一下。同样可以起到上面讲的效果的。
package com.chap2.
import com.chap2.service.UserS
import mons.logging.L
import mons.logging.LogF
import org.springframework.beans.factory.BeanF
import org.springframework.context.ApplicationC
import org.springframework.context.support.ClassPathXmlApplicationC
public class Client {
public static void main(String[] args) {
Log log = LogFactory.getLog(Client.class);
//初始化,并加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext(&applicationContext.xml&);
BeanFactory beanFactory = (BeanFactory)
//得到实例化的UserService
UserService userService = (UserService) beanFactory.getBean(&userService&);
//输出并记录登录信息
(userService.validateUser());
}  最后项目生成一个可执行的可执行的Java JAR程序包,注意要修改JAR包的manifest.mf文件,添加&Main-Class: com.chap2.util.Client&行,以指定有main方法的入口类。
  生成的JAR包结构如下:
图3 生成的可执行程序
  在dist目录下面生成了包SpringChap2.jar,以及它依赖的各个库。可以直接运行这个Java程序。运行结果:
六月 20, :15 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@6433a2: startup date [Fri Jun 20 14:33:15 CST 2014]; root of context hierarchy
六月 20, :15 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
六月 20, :17 下午 com.chap2.util.Client main
INFO: 恭喜你,登录成功!  你可能会想登录用户的信息在哪?所有的配置信息和实例化的工作都交给了Spring的Ioc容器,看看配置文件的配置。
&?xml version='1.0' encoding='UTF-8' ?&
&!-- was: &?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&
xmlns:aop=&http://www.springframework.org/schema/aop&
xmlns:tx=&http://www.springframework.org/schema/tx&
xsi:schemaLocation=&http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd&&
&!-- bean id=&propertyConfigurer&
class=&org.springframework.beans.factory.config.PropertyPlaceholderConfigurer&
p:location=&/WEB-INF/jdbc.properties& /&
&bean id=&dataSource&
class=&org.springframework.jdbc.datasource.DriverManagerDataSource&
p:driverClassName=&${jdbc.driverClassName}&
p:url=&${jdbc.url}&
p:username=&${jdbc.username}&
p:password=&${jdbc.password}& / --&
&!-- ADD PERSISTENCE SUPPORT HERE (jpa, hibernate, etc) --&
&bean id=&userLogin& class=&com.chap2.UserLoginImpl& /&
&bean id=&userService& class=&com.chap2.service.UserService&&
&property name=&userName&&
&value&admin&/value&
&/property&
&property name=&password&&
&value&1234&/value&
&/property&
&property name=&userLogin&&
&ref bean=&userLogin&&&/ref&
&/property&
  上面的配置文件只是描述了我们所用到类的调用关系,和数据的赋值&初始值&。我们在UserSerivce类中要用到userLogin中的方法,我们只需在这里简单的设置它的一个属性(property)就可以了。以前的编程要在代码中实现,通过先new 一个UserLogin的实现对象,然后在调用其中的方法。我们这里只需在配置文件中简单的配置一下就可以了,在程序用到这个方法时,容器会自动先实例化UserLoginImpl,然后把它交给UserService使用。在UserService中不用担心实例化,以及管理它的生命周期了。全部让Spring的Ioc容器管理就行了。这样做减少了它们之间的依赖性,也就是降低它们的耦合度。
  整个程序的工作流程是这样的。在main()方法中通过ApplicationContext来加载配置文件,然后把它转换为BeanFactory。这就是我们要讲的Spring中控制反转容器。它把所有的类都初始化,并放在这个容器中。在我们需要的时候只需像 UserService userService = (UserService) beanFactory.getBean(&userService&);通过配置文件中bean的id或name调用就可以了。不必在像以前的编程那样UserService userSerivce=new
UserService();这样做还有一个好处就是让容器来管理它的生命周期,我们只需用就可以了,用完了在交给容器管理。而不用担心什么时候销毁它。
  可以看出,上面这个程序可以直接运行,无需任何容器如Tomcat, Jetty, JBoss。当然这不是Web项目,没有用到JSP。如果是Spring Web项目,用到了JSP,那只要一个支持Servlet的容器即可,但无需EJB容器,这体现出Spring的轻量级、非侵入式的框架设计思想。Spring框架本身是不依赖于应用服务器容器的。
  (2)IOC 是一种使应用程序逻辑外在化的设计模式
  因为提供服务的组件是被注入而不是被写入到客户机代码中。将 IOC 与接口编程应用结合从而产生出 Spring 框架的架构,这种架构能够减少客户机对特定实现逻辑的依赖。
  (3)IoC的设计目标
  不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器(在 Spring 框架中是 IOC 容器) 负责将这些联系在一起。
  (4)IoC在应用开发中的体现
  IoC的抽象概念是“依赖关系的转移”,在实际应用中的下面的各个规则其实都是IoC在应用开发中的体现。
  “高层模块组件不应该依赖低层模块组件,而是模块组件都必须依赖于抽象”是 IoC的一种表现
  “实现必须依赖抽象,而不是抽象依赖实现”也是IoC的一种表现
  “应用程序不应依赖于容器,而是容器服务于应用程序”也是IoC的一种表现。
  接下来我们讲在讲述它的另外的一个名字:依赖注入(DI)。
Spring 中的依赖注入(DI)
  (1)DI = Dependency Injection
  正在业界为IoC争吵不休时《Inversion of Control Containers and the Dependency Injection pattern》为IoC正名,至此,IoC又获得了一个新的名字:依赖注入(Dependency Injection)。
  Dependency Injection模式是依赖注射的意思,也就是将依赖先剥离,然后在适当时候再注射进入。
  (2)何谓依赖注入
  相对IoC 而言,“依赖注入”的确更加准确地描述了这种古老而又时兴的设计理念。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
  讲的通俗点,就是在运行期,由Spring根据配置文件,将其他对象的引用通过组件的提供的setter方法或者构造方法等进行设定。
  在上面的UserService中已经体现了这种方式,当UserService需要的UserLogin的时候,容器会给它注入。这就体现了需要用的时候,有容器给你注入。你不必主动的如创建了。也不用如何管理它了。是不是和以前的编程方式有些改变。可能你会想到,这不就是工厂模式的衍生吗?不错它就是利用工厂模式的原理实现的,但它远比工厂模式简单。通过上面的部分代码我们就可以看出它就是工厂模式的衍生,BeanFactory就充分说明了这一点。
在Spring中为什么要提供“依赖注入”设计理念
  (1)目的
  依赖注入的目标并非为软件系统带来更多的功能,而是为了提升组件重用的概率,并为系统搭建一个灵活、可扩展的平台。
  (2)原因---更简洁的编程实现
  很多初学者常常陷入&依赖注入,何用之有?&的疑惑。想来前面和下面的例子可以帮助大家简单的理解其中的含义。
  回顾上面创建SpringChap2的例子中,UserService类在运行前,其userName, password节点为空。运行后由容器将字符串&admin&和&1234&注入。此时UserService即与内存中的&admin&和&1234&字符串对象建立了依赖关系。也许区区一个字符串我们无法感受出依赖关系的存在。
  如果把这里的userName/password属性换成一个数据源(DataSource),可能更有感觉:
&bean id=&dataSource& class=&org.springframework.indi.JndiObjectFactoryBean&&
&property name=&jndiName&&
&value& java:/comp/env/jdbc/testDB&/value&
&/property&
&bean id=&dataBean& class=&examples.DAOBean&&
&property name=&dataSource&&
&ref bean=&dataSource&/&
&/property&
&/beans&  其中DAOBean(假设DAOBean是一个运行在Java EE容器中的组件---如Weblogic或者Tomcat等)中的dataSource将由容器在运行期动态注入,而DataSource的具体配置和初始化工作也将由容器在运行期完成。
  对比传统的实现方式(如通过编码初始化DataSource实例),我们可以看到,基于依赖注入的系统实现相当灵活简洁。
  (3)产生的效果
  通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定DAOBean中所需的DataSource实例。DAOBean只需利用容器注入的DataSource实例,完成自身的业务逻辑,而不用关心具体的资源来自何处、由谁实现。
  首先,提高了组件的可移植性和可重用度
  假设我们的部署环境发生了变化,系统需要脱离应用服务器独立运行,这样,由于失去了容器的支持,原本通过JNDI获取DataSource的方式不再有效(因为,现在则需要改变为由某个组件直接提供DataSource)。
  我们需要如何修改以适应新的系统环境?很简单,我们只需要修改dataSource的配置:
&bean id=&dataSource& class=& mons.dbcp.BasicDataSource & destroy-method=&close&&
&property name=&driverClassName&&
&value&com.microsoft.jdbc.sqlserver.SQLServerDriver&/value&
&/property&
&property name=&url&&
&value&jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=WebStudyDB&/value&
&/property&
&property name=&username&&
&value&sa&/value&
&/property&
&property name=&password&&
&value&1234&/value&
&/property&
&bean id=&dataBean& class=&examples.DAOBean&&
&property name=&dataSource&&
&ref bean=&dataSource&/&
&/property&
&/beans&  这里我们的DataSource改为由Apache DBCP组件提供。没有编写任何代码我们即实现了DataSource的切换。
  其次,依赖注入机制减轻了组件之间的依赖关系
  回想传统编码模式中,如果要进行同样的修改,我们需要付出多大的努力。因此,依赖注入机制减轻了组件之间的依赖关系,同时也大大提高了组件的可移植性,这意味着,组件得到重用的机会将会更多。
深入了解依赖注入
  Spring所倡导的开发方式---由容器帮助我们管理对象的生命周期和关系。
  我们只需要将对象在Spring中进行登记。Spring所倡导的开发方式就是如此,所有的类都会在Spring容器中登记,告诉Spring你是个什么东西,你需要什么东西,然后Spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。
  所有的类的创建、销毁都由Spring来控制。所有的类的创建、销毁都由Spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被Spring控制,所以这叫控制反转。
  (1)IoC的实现前提---借助于依赖注入
  IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。
  比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了Spring我们就只需要告诉Spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A并不需要知道。
  在系统运行时,Spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由Spring注入到A中的,依赖注入的名字就这么来的。
  (2)如何实现依赖注入----通过reflection来实现DI
  那么DI是如何实现的呢?Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,Spring就是通过反射来实现注入的。
  利用下面的代码可以从配置文件中获得某个组件对象,并且动态地给该组件的message属性赋值。
Properties pro = new Properties();
pro.load(new FileInputStream(&config.properties&));
String actionImplName = (String)pro.get(actionBeanName);
String actionMessageProperty = (String)pro.get(actionMessagePropertyName);
Object obj = Class.forName(actionImplName).newInstance();
//BeanUtils是Apache Commons BeanUtils提供的辅助类
BeanUtils.setProperty(obj,&message&, actionMessageProperty);
return (Action)
Spring IoC与工厂模式的对比
  在以往的开发中, 通常利用工厂模式(Factory)来解决此类问题----使外部调用类不需关心具体实现类,这样非常适合在同一个事物类型具有多种不同实现的情况下使用。其实不管是工厂模式还是依赖注入,调用类与实现类不可能没有任何依赖,工厂模式中工厂类通常根据参数来判断该实例化哪个实现类,Spring IoC将需要实例的类在配置文件文件中配置。
  使用Spring IoC能得到工厂模式同样的效果,而且编码更加简洁。
  当我们在应用系统中的组件设计完全是基于接口定义时,一个关键问题便产生了-----我们的程序如何去加载接口的各个实现类。在传统的解决方案种往往基于Factory模式来实现。
// 代表某种产品类的接口,也就是我们所要创建的对象所应该具有的功能要求
interface Product {
public void execute();
// 不同的产品类(也就是我们所要创建的各个对象)
class ConcreteProductA implements Product {
public void execute() {
class ConcreteProductB implements Product {
public void execute() {
// 工厂类,利用它来创建出不同类型的产品---客户所需要的对象
class Factory {
public static Product CreateProduct(Object param) {
return ConstructObjects(param);
private static Product ConstructObjects(Object param) {
//根据不同的产品类型的需求来创建不同的产品对象
// 调用类,也就是请求者类
public class UserClient {
public UserClient() {
Product product1 = Factory.CreateProduct(paramA);
//实例化ConcreteProductA
Product product2 = Factory.CreateProduct(paramB);
//实例化ConcreteProductB
}  通过工厂模式,最终达到在ConstructObjects方法中设定实例化实现类的逻辑,这样对于调用类来说,不直接实例化实现类(工厂模式中工厂类通常根据参数来判断该实例化哪个实现类),纵然实现类发生变化,而调用代码仍然可以不作修改,给维护与扩展带来便利----系统中的其他组件需要获取这个接口的实现,而无需事先获知其具体的实现。
  但采用工厂模式来实现时,将会有如下三个主要的缺点:
  (1)除非重新编译,否则无法对实现类进行替换
  必须重新编译工厂类使得原本可以达成的易用性大大降低。在过去,Spring诞生之前,许多项目中,我们通过引入可配置化工厂类的形式,为这种基于接口的设计提供足够的支持。这解决了实例化的问题,但是它为我们的项目开发带来了额外的负担,同时,它也没有真正帮我们解决其余两个问题。
  (2)无法透明的为不同组件提供多个实现
  这是我们在应用工厂模式时一个比较头疼的问题,因为Factory类要求每个组件都必须遵从Factory类中定义的方法和结构特征。
  当然我们可以在代码的实现的形式上为Factory类中的ConstructObjects方法增加一个参数,通过该参数达到对接口实现的不同版本进行索引。这种实现方式的问题在于我们必须担负很大的维护工作量,每个组件都必须使用一个不同的关键字。从而使得它必须以一种与众不同的方式与其他组件的实例相区分。
  (3)无法简单的进行切换实例产生的模型----单例或者原形
  上面的代码是实现了返回多个实例的方式,如果我们需要保持了一个Singleton的实例,此时我们必须需要重新修改并编译Factory类。
  存在这个问题的核心是在于组件必须主动寻找接口的实现类,因此这个问题并不能通过传统的工厂模式加以解决。下面用Spring IoC来实现。
  SpringConfig.xml:
&bean id=&productA& class=&ConcreteProductA& /&
&bean id=&productB& class=&ConcreteProductB& /&  InitSpring.java:
public class InitSpring
AbstractApplicationContext wac =
private static InitSpring instance = new InitSpring();
private InitSpring()
public static void Init(AbstractApplicationContext wac)
instance.wac =
public static Object getInstance(String objName)
return instance.wac.getBean(objName);
public static Object getInstance(Class objClass)
return getInstance(objClass.getName());
}  Client.java(调用类):
public class Client
public Client()
Product product = (Product)InitSpring.getObject(&productA&);
//实例化ConcreteProductA
Product product = (Product)InitSpring.getObject(&productB&);
//实例化ConcreteProductB
}  对比调用代码,其中同样也没有硬编码实现类,但比较工厂模式,少了Factory类而且采用配置文件来决定各个产品的实现类,使用Spring IOC能得到工厂模式同样的效果,而且编码更加简洁、灵活方便。
  Spring对于基于接口设计的应用造成了极大的冲击效应。因为Spring接过了将所有组件进行串联组装的重任,我们无需再纠缠于遍布各处的工厂类设计。
参考文献:
本文已收录于以下专栏:
相关文章推荐
原来校招时候的时候刷过一道题——写段代码实现控制反转,现在终于有了些思路~自己留念,同时分享给大家。
轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)
关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享.
依赖注入和控制反转两个概念让很多...
两个主角“依赖注入”和“控制反转”:
1、二都说的都是同一件事,只是叫法不同。是一个重要的面向对象编程的法则,也是一种设计模式;
2、英文原称:依赖注入,Dependency Inject...
像前面博客中提到struts框架,Hibernate框架似的,spring同样也是一个开源的框架。使用框架的的优势在于分层结构,每层有相应的框架,减少开发工作量,减少组件之间的耦合。struts框架应...
一、控制反转:
从简单的代码示例入手:
/// 邮件服务类
public class EmailService
其实依赖性注入(Dependency Injection)和控制反转(Inversion of Control)二者是同一个概念。具体含义是:当某个角色(可能是一个c#实例,调用者)需要另一个...
学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家...
在之前的 Spring源码剖析——核心IOC容器原理这篇文章中,已经详细介绍了在Spring当中我们配置的Bean是怎样被Spring解析和管理的,我们配置的那些Bean经过 载入 、解析 和...
一个小例子
我们先写一个不使用控制反转的小例子:#includestruct A{
virtual void func(){}
一.什么是依赖注入和控制反转?控制反转:即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转...
他的最新文章
讲师:钟钦成
讲师:宋宝华
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)}

我要回帖

更多关于 依赖注入 反射 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信