学习过Spring框架的人必定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来讲,总以为IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和你们分享网上的一些技术大牛们对Spring框架的IOC的理解以及谈谈我对Spring Ioc的理解。html
1、分享Iteye的开涛对Ioc的精彩讲解
首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得很是通俗易懂,如下内容所有来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846java
1.一、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为什么是反转(有反转就应该有正转了),哪些方面反转了”,那咱们来深刻分析一下:spring
●谁控制谁,控制什么:传统Java SE程序设计,咱们直接在对象内部经过new进行建立对象,是程序主动去建立依赖对象;而IoC是有专门一个容器来建立这些对象,即由Ioc容器来控制对 象的建立;谁控制谁?固然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不仅是对象包括好比文件等)。数据库
●为什么是反转,哪些方面反转了:有反转就有正转,传统应用程序是由咱们本身在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙建立及注入依赖对象;为什么是反转?由于由容器帮咱们查找及注入依赖对象,对象只是被动的接受依赖对象,因此是反转;哪些方面反转了?依赖对象的获取被反转了。编程
用图例说明一下,传统程序设计如图2-1,都是主动去建立相关对象而后再组合起来:设计模式
图1-1 传统应用程序示意图框架
当有了IoC/DI的容器后,在客户端类中再也不主动去建立这些对象了,如图2-2所示:函数
图1-2有IoC/DI容器后程序结构示意图post
1.二、IoC能作什么
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导咱们如何设计出松耦合、更优良的程序。传统应用程序都是由咱们在类内部主动建立依赖对象,从而致使类与类之间高耦合,难于测试;有了IoC容器后,把建立和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,因此对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得很是灵活。学习
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序本来是老大,要获取什么资源都是主动出击,可是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来建立并注入它所须要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找咱们,咱们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
1.三、IoC和DI
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并不是为软件系统带来更多功能,而是为了提高组件重用的频率,并为系统搭建一个灵活、可扩展的平台。经过依赖注入机制,咱们只须要经过简单的配置,而无需任何代码就可指定目标须要的资源,完成自身的业务逻辑,而不须要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为何须要依赖,谁注入谁,注入了什么”,那咱们来深刻分析一下:
●谁依赖于谁:固然是应用程序依赖于IoC容器;
●为何须要依赖:应用程序须要IoC容器来提供对象须要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所须要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不一样角度描述,因为控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),因此2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
看过不少对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感受,读完以后依然是一头雾水,感受就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每个字,读完以后给人一种豁然开朗的感受。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。
2、分享Bromon的blog上对IoC与DI浅显易懂的讲解
2.一、IoC(控制反转)
首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来讲,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,咱们是如何找女友的?常见的状况是,咱们处处去看哪里有长得漂亮身材又好的mm,而后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,而后嘿嘿……这个过程是复杂深奥的,咱们必须本身设计和面对每一个环节。传统的程序开发也是如此,在一个对象中,若是要使用另外的对象,就必须获得它(本身new一个,或者从JNDI中查询一个),使用完以后还要将对象销毁(好比Connection等),对象始终会和其余的接口或类藕合起来。
那么IoC是如何作的呢?有点像经过婚介找女友,在我和女友之间引入了一个第三者:婚姻介绍所。婚介管理了不少男男女女的资料,我能够向婚介提出一个列表,告诉它我想找个什么样的女友,好比长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,而后婚介就会按照咱们的要求,提供一个mm,咱们只须要去和她谈恋爱、结婚就好了。简单明了,若是婚介给咱们的人选不符合要求,咱们就会抛出异常。整个过程再也不由我本身控制,而是有婚介这样一个相似容器的机构来控制。Spring所倡导的开发方式就是如此,全部的类都会在spring容器中登记,告诉spring你是个什么东西,你须要什么东西,而后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其余须要你的东西。全部的类的建立、销毁都由 spring来控制,也就是说控制对象生存周期的再也不是引用它的对象,而是spring。对于某个具体的对象而言,之前是它控制其余对象,如今是全部对象都被spring控制,因此这叫控制反转。
2.二、DI(依赖注入)
IoC的一个重点是在系统运行中,动态的向某个对象提供它所须要的其余对象。这一点是经过DI(Dependency Injection,依赖注入)来实现的。好比对象A须要操做数据库,之前咱们老是要在A中本身编写代码来得到一个Connection对象,有了 spring咱们就只须要告诉spring,A中须要一个Connection,至于这个Connection怎么构造,什么时候构造,A不须要知道。在系统运行时,spring会在适当的时候制造一个Connection,而后像打针同样,注射到A当中,这样就完成了对各个对象之间关系的控制。A须要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3以后一个重要特征是反射(reflection),它容许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是经过反射来实现注入的。
理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工做只是在spring的框架中堆积木而已。
3、我对IoC(控制反转)和DI(依赖注入)的理解
在平时的java应用开发中,咱们要实现某一个功能或者说是完成某个业务逻辑时至少须要两个或以上的对象来协做完成,在没有使用Spring的时候,每一个对象在须要使用他的合做对象时,本身均要使用像new object() 这样的语法来将合做对象建立出来,这个合做对象是由本身主动建立出来的,建立合做对象的主动权在本身手上,本身须要哪一个合做对象,就主动去建立,建立合做对象的主动权和建立时机是由本身把控的,而这样就会使得对象间的耦合度高了,A对象须要使用合做对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,而且是紧密耦合在一块儿,而使用了Spring以后就不同了,建立合做对象B的工做是由Spring来作的,Spring建立好B对象,而后存储到一个容器里面,当A对象须要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,而后交给A对象使用,至于Spring是如何建立那个对象,以及何时建立好对象的,A对象不须要关心这些细节问题(你是何时生的,怎么生出来的我可不关心,能帮我干活就行),A获得Spring给咱们的对象以后,两我的一块儿协做完成要完成的工做便可。
因此控制反转IoC(Inversion of Control)是说建立对象的控制权进行转移,之前建立对象的主动权和建立时机是由本身把控的,而如今这种权力转移到第三方,好比转移交给了IoC容器,它就是一个专门用来建立对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,经过IoC容器来创建它们之间的关系。
这是我对Spring的IoC(控制反转)的理解。DI(依赖注入)其实就是IOC的另一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:得到依赖对象的方式反转了。
4、小结
对于Spring Ioc这个核心概念,我相信每个学习Spring的人都会有本身的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。若是有理解不到位或者理解错的地方,欢迎广大园友指正!
---------------------------------------------------------------------------------------------------------------------
引述: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语言自己的类反射功能。