不懂控制反转么?大白话讲清楚spring中这些晦涩的概念
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了不懂控制反转么?大白话讲清楚spring中这些晦涩的概念相关的知识,希望对你有一定的参考价值。
参考技术A前面介绍了spring环境的搭建,在搭建spring环境的时候分为java环境和javaWeb环境,在javaWeb环境下通常会结合springMVC使用,在java项目中只需要把spring的包导入工程中,一般初学者会把所有的包全部导入,然后就可以通过获得applicationContext,把类的实例化交给spring管理,然后从spring容器中获得类的实例。
spring中有控制反转(Ioc)和依赖注入(DI)两个概念,Ioc和DI是spring的核心概念,同时也是一种新的编程思想。
控制反转(Ioc)
控制反转,顾名思义就是控制权的改变,在没有接触spring之前,我们要使用一个类的实例,必须使用new的方式生成一个对象,这个过程的主动权掌握在程序员亦自己写的程序中,但是使用了spring之后,实例的创建不再由程序员手动实现,而是由spring容器来完成,实现了控制的反转,即主动权交给了spring的IOC容器。
依赖注入(DI)
一个类包括属性和方法,在创建了实例对象之后,或者在创建实例对象的同时,需要初始化成员变量(属性),在spring之前成员变量的初始化可以通过构造方法或setXXX方法;在spring出现之后,由于实例的创建交给了spring的IOC容器,那么成员变量的初始化也依赖于IOC容器,由容器去注入成员变量的值。
上面介绍了IOC和DI两个概念,可以得出IOC和DI其实是在做一件事,就是spring的IOC容器创建实例对象,一切对象的实例化都交给了spring容器,在程序中不必手动使用new的方式实例化变量。
spring提供两种不同的方式来配置spring,一种是配置文件(XML),另一种是基于注解(Annotation)。下面针对IOC和DI使用两种方式一一做介绍
在介绍之前必须了解如何在java项目中获得spring容器,也即ApplicationContext。
获取ApplicationContext的方式有三种,这里使用的ClassPathXmlApplicationContext,ClassPathApplicationContext是ApplicationContext的子类,这里的cpac就是spring的IOC容器,通过它的getBean()方法可以获得容器中已经初始化的实例。
配置文件(XML)
假如我们有下面的学生类(student)
此类有三个属性id、name、age,下面通过配置文件的方式,在spring的配置文件中配置,
在spring的配置文件中配置 标签,id属性在配置文件中必须唯一,class属性指定类的权限类名,下面有 标签,指定Student的三个属性,且指定了属性值,测试结果如下,
这里可以看到输出了配置的值,这种方式是使用属性注入的方式,要求必须有setXXX方法,还有另外一种方式,通过构造方法的方式注入,
既然是通过构造方法的方式注入,那么在Student类中必须要有这个构造方法,在上面的Student类中已经有了有参的构造方法,我们知道在自定义了有参的构造方法之后,系统不会再默认的提供无参构造,我们还必须添加无参构造,下面看测试结果,
由于我们只注入了id、name两个属性,这里可以打印出,age由于未赋值且其类型为String,则默认为null。
上面是通过配置文件的方式,配置了根据属性、构造方法注入的方式,这个例子是比较简单的,下面看一个复杂的例子,在程序中经常会采用分层、面向接口编程的思绪,
DAO接口
service接口
DAO实现类
service实现类
在service的实现类中有一个DAO层的接口对象,我们下面看这种是如何根据属性注入的,
首先,配置DAO层的bean,这里的class属性必须配置其实现类,然后配置service的实现类,在ServiceImpl中有一个dbDAO类型的属性,使用ref属性引用上面的bean。测试结果如下,
ref属性可以引用spring配置文件中的bean,使用id的值。
通过上面的介绍,可以知道使用配置文件这种方式可以很方便的配置,但是当需要配置的类很多,且依赖很多的情况下,这种方式会很繁琐,那么另外一种方式便派上了用场。
注解(Annotation)
spring提供@Component、@Repository、@Controller、@Service四种注解,其实,只使用@Component一个便可以做到,@Repository、@Controller、@Service只是针对不同的层设置的,可以更加明显,@Repository对应DAO层,@Controller对应控制层,@Service对应服务层,使用这三个注解可以明显的分层,使系统容易理解,也可以只使用@Component。
使用了注解需要开启组件自动扫描机制,在spring的配置文件中开启组件自动扫描机制,需要context命名空间的支持,下面是一个配置文件的例子,
在spring配置文件的头部添加context命名空间的支持,然后使用 标签开启组件扫描,base-package属性指定扫描的基包,此包及此包下的子包都会进行扫描。开启了组件扫描之后,还需要自动注入,网上有说需要: ,其实前面的组件扫描已经包含了此标签的作用,为此不需要配置此标签了,自动注入默认根据类型(byType)进行注入,还有根据名字注入(byName)即bean中id的值,还有根据构造方法注入(constructor)。自动注入需要@Aotuwired注解,此注解可以放在属性上,也可以放在setXXX方法上,放在属性上则可以省略setXXX方法,下面是具体的例子
在类上使用@Component注解,且指定了实例的名为su,如果不指定则默认为类名称首字母小写,即studentAnnotation。
下面是一个自动扫描和自动注入的例子,
类上使用了@Service注解,属性上使用了@Autowired注解,这时setDbDAO()方法可以不要。@Autowired注解就是为了省略setXXX方法的,@Autowired注解默认使用的按照类型注入,如果存在多个相同类型的实例,这里自动注入会失败,为了可以正确注入,引入另外一个注解@Qualifier(value=""),此注解的value属性可以指定一个bean的id值,做到自动注入。
@Qualifier可以用在属性或者setXXX方法上。
综上,通过配置文件和注解两种方式介绍了依赖注入。
透透彻彻IoC(你没有理由不懂!)
http://www.myexception.cn/open-source/418322.html
引述:IoC(控制反转:Inverse of Control)是Spring容器的内核,AOP、声明式事务等功能在此基础上开花结果。但是IoC这个重要的概念却比较晦涩隐讳,不容易让人望文生义,这不能不说是一大遗憾。不过IoC确实包括很多内涵,它涉及代码解耦、设计模式、代码优化等问题的考量,我们打算通过一个小例子来说明这个概念。
通过实例理解IoC的概念
贺岁大片在中国已经形成了一个传统,每到年底总有多部贺岁大片纷至沓来让人应接不暇。在所有贺岁大片中,张之亮的《墨攻》算是比较出彩的一部。该片讲述了战国时期墨家人革离帮助梁国反抗赵国侵略的个人英雄主义故事,恢宏壮阔、浑雄凝重的历史场面相当震撼。其中有一个场景:当刘德华所饰演的墨者革离到达梁国都城下,城上梁国守军问到:“来者何人?”刘德华回答:“墨者革离!”我们不妨通过一个Java类为这个“城门叩问”的场景进行编剧,并借此理解IoC的概念:
代码清单3-1 MoAttack:通过演员安排剧本
public class MoAttack { public void cityGateAsk(){ //①演员直接侵入剧本 LiuDeHua ldh = new LiuDeHua(); ldh.responseAsk("墨者革离!"); } }
我们会发现以上剧本在①处,作为具体角色饰演者的刘德华直接侵入到剧本中,使剧本和演员直接耦合在一起(图3-1)。
一个明智的编剧在剧情创作时应围绕故事的角色进行,而不应考虑角色的具体饰演者,这样才可能在剧本投拍时自由地遴选任何适合的演员,而非绑定在刘德华一人身上。通过以上的分析,我们知道需要为该剧本主人公革离定义一个接口:
代码清单3-2 MoAttack:引入剧本角色
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:通过构造函数注入革离扮演者
public class MoAttack { private GeLi geli; //①注入革离的具体扮演者 public MoAttack(GeLi geli){ this.geli = geli; } public void cityGateAsk(){ geli.responseAsk("墨者革离!"); } }
MoAttack的构造函数不关心具体是谁扮演革离这个角色,只要在①处传入的扮演者按剧本要求完成相应的表演即可。角色的具体扮演者由导演来安排,如代码清单3-4所示:
代码清单3-4 Director:通过构造函数注入革离扮演者
public class Director { public void direct(){ //①指定角色的扮演者 GeLi geli = new LiuDeHua(); //②注入具体扮演者到剧本中 MoAttack moAttack = new MoAttack(geli); moAttack.cityGateAsk(); } }
在①处,导演安排刘德华饰演革离的角色,并在②处,将刘德华“注入”到墨攻的剧本中,然后开始“城门叩问”剧情的演出工作。
属性注入
有时,导演会发现,虽然革离是影片《墨攻》的第一主角,但并非每个场景都需要革离的出现,在这种情况下通过构造函数注入相当于每时每刻都在革离的饰演者在场,可见并不妥当,这时可以考虑使用属性注入。属性注入可以有选择地通过Setter方法完成调用类所需依赖的注入,更加灵活方便:
代码清单3-5 MoAttack:通过Setter方法注入革离扮演者
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方法注入革离扮演者
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接口:
public interface ActorArrangable { void injectGeli(GeLi geli); }
然后,MoAttack实现ActorArrangable接口提供具体的实现:
代码清单3-7 MoAttack:通过接口方法注入革离扮演者
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:通过接口方法注入革离扮演者
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配置文件的对以上实例进行配置的配置文件片断:
<?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语言本身的类反射功能。
这些文章摘自于我的《Spring 3.x企业应用开发实战》的第3章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。
以上是关于不懂控制反转么?大白话讲清楚spring中这些晦涩的概念的主要内容,如果未能解决你的问题,请参考以下文章