Spring揭秘-IOC介绍

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/qq_36000403/article/details/83155133
出自【zzf__的博客】

1.IOC理念:让别人为你服务

IOC的全称是Inversion of Control即控制反转 还有一个别名叫做依赖注入

1.1构造依赖对象的传统做法:

public FXNewsProvider(){
	newListener = new DowJonesNewsListener();
	newPersistener = new DowJonesNewsPersister();
}

2.2 IoC的引出
传统做法是通过new构造对象 或者是通过Service-Locator(可以解决直接的依赖耦合),它们都有一个共同点,就是我们都是主动地去获取依赖的对象,其实没有必要这样,我们最终需要做的,其实就是直接调用依赖对象所提供的某项服务而已,只要用到这个依赖对象的时候,它能准备就绪,我们完全可以不管这个对象是自己找来的还是别人送来的。如果有人能够在我们需要时将某个依赖对象送过来,为什么还要大费周折地自己去折腾?IOC就为我们做了这样的事,他的反转,就反转在让你原来的事必躬亲,转变为现在的享受服务。IOC的理念就是让别人为你服务!!
如下图所示:

所有的被注入对象和依赖对象都由IoC Service Provider统一管理 被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的 IoC Service Provider在这里就是通常的IoC容器所充当到的角色

从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里


2.加深理解IoC

如果还是有点不理解IoC的设计理念,我今天和大家分享网上的一些技术大牛们对Spring框架的IOC的理解。

以下内容来自:http://luanxiyuan.iteye.com/blog/2279954
一、分享Iteye的开涛对Ioc的精彩讲解
首先要分享的是Iteye的开涛这位技术牛人对Spring框架的IOC的理解,写得非常通俗易懂, 以下内容全部来自原文,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846
 
2.1、IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么:
    传统JavaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪些方面反转了
    有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

    用图例说明一下,传统程序如下图,都是主动去创建相关对象然后再组合起来:

在这里插入图片描述

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如下图所示:
在这里插入图片描述

2.2 IoC能做什么

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

2.3 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.4 最后总结一下:
控制反转IoC(Inversion of Control)是说将创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系

IOC:控制反转:将对象的创建权,由Spring管理.
DI:依赖注入:在Spring创建对象的过程中,把对象依赖的属性注入到对象中.被注入对象依赖IoC容器配置依赖对象


3.被注入对象通知IoC Service Provider 的三种方式

想让IoC Service Provider为被注入对象提供服务,并将所需要的被依赖对象送过来,也需要通过某种方式通知对方。要其中提到了有三种依赖注入方式即构造方法注入,setter方法注入,以及接口注入

三种方式的优缺点:

3.1 接口注入:

  • 不作过多介绍,采用这种用法很少很少;
    从注入方式的使用上来说,接口注入是现在不甚提倡的一种方式,处于”退役状态”。因为它强制被注入对象实现不必要的接口,带有侵入性。而构造方法注入和setter方法注入则不需要如此

3.2 构造方法注入:

public FXNewsProvider(IFXNewsListener newsListner, IFXNewsPersister newsPersister){
	this.newsListner =  newsListner;
	this.newsPersister  =  newsPersister;
}
  • 构造方法注入:
    被注入对象的构造乃至其整个生命周期,应该都由IoC Service
    Provider来管理的,这种注入方式的优点就是,对象在构造完成之后,即已进入就绪状态,可以马上使用。缺点就是,①当依赖对象比较多的时候,构造方法的参数列表会比较长。②而通过反射构造对象的时候,对相同类型的参数的处理会比较困难,维护和使用上也比较麻烦。③而且在JAVA中,构造方法无法被继承,无法设置默认值。④对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。

3.3 setter方法注入:

public class FXNewsProvider{
	private IFXNewsListener newsListener;
	private IFXNewsPersister newPersistener;

	public IFXNewsListener  getNewsListener(){
			return newsListener;
	}
	public void setNewsListener(IFXNewsListener  newsListener){
			this.newsListener = newsListener;
	}
	public IFXNewsListener  getNewPersistener(){
			return newPersistener;
	}
	public void getNewsListener(IFXNewsPersister newPersistener){
			this.newPersistener  = newPersistener;
	}

}
  • 因为方法可以命名,所以setter方法注入在描述性上要比构造方法注入好些。另外,setter方法可以被继承,允许设置默认值,而且有良好的IDE支持。缺点当然就是对象无法在构造完成后马上进入就绪状态

4.IOC模式带给我们的好处

不只是一个方向上的转变,这种模式不会对业务对象构成很强的侵入性,使用IOC后,对象具有更好的可重用性,和可扩展性 和可测试性。

总结:IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。为什么说能够解耦呢,因为从另一个角度看,DI即依赖注入,带来了松耦合,我们知道,如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

扩展说明:紧密耦合的代码难以测试,难以复用,难以理解,另一方面,一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了,为了完成有实际意义的功能,不同的类必须以适当的方式进行交互。总而言之,耦合是必须的,但应当被谨慎地管理

5.掌管大局的IoC Service Provider

虽然业务对象可以通过IoC方式声明相应的依赖,但是最终仍然需要通过某种角色或者服务将这些相互依赖的对象绑定到一起,而IoC Service Provider就对应IoC场景中的这一角色。

IoC Service Provider在这里是一个抽象出来的概念,它可以指代任何将IoC场景中的业务对象绑定到一起的实现方式,它可以是一段代码,也可以是一组相关的类,甚至可以是比较通用的IoC框架或者IoC容器实现。
比如:

IFXNewsListener  newListener  =  new DowJonesNewsListener();	//①
IFXNewsPersister  newsPersister  =  new DowJonesNewsPersister();//②	
FXNewsProvider  newsProvider  =  new FXNewsProvider(newListener  , newsPersister );//③	

①和②就是下面将要说的职责之一:业务对象的构建管理
③ 就是业务对象间的依赖绑定

5.1 职责

  • 业务对象的构建管理:
    在IoC场景中,业务对象无需关心所依赖的对象如何构建如何获取,但这部分工作始终需要有人来做。所以, Ioc
    Service Provider需要将对象的构建逻辑从客户端对象那里剥离出来,以免这部分逻辑污染业务对象的实现
  • 业务对象间的依赖绑定:
    对于Ioc Service Provider来说,这个职责是最艰巨也是最重要的,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullpointerException)。Ioc Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

5.1.1由上面的职责,我们可以得知IoC容器在我们的具体使用场景中到底帮助了我们做了些什么
以下例子来自https://www.zhihu.com/question/23277575/answer/169698662
假设有以下四个类,它们的关系如下图所示。
当我们需要创建一辆汽车时,我们需要编写如下代码
在这里插入图片描述

Tire tire = new Tire();//轮胎
Bottom bottom = new Bottom(tire);//底盘
Framework framework = new Framework(bottom);//车身
Car car = new Car(framework);//汽车

当有了IoC容器之后,对车类进行初始化的那段代码发生的地方,转移到了IoC容器里。
在这里插入图片描述

这有什么好处呢?
IoC 容器的第一个好处是:这个容器可以自动对你的代码进行初始化,你只需要维护一个容器(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。
IoC 容器的第二个好处是:我们在创建实例的时候不需要了解其中的细节。在上面的例子中,我们自己手动创建一个车instance时候,是从底层往上层new的:

在这里插入图片描述

这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。

而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):
在这里插入图片描述
这里IoC Container可以直接隐藏具体的创建实例的细节,在我们来看它就像一个工厂:
在这里插入图片描述

我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。

实际项目中,有的Service Class可能是十年前写的,有几百个类作为它的底层。假设我们新写的一个API需要实例化这个Service,我们总不可能回头去搞清楚这几百个类的构造函数吧?IoC Container的这个特性就很完美的解决了这类问题——因为这个架构要求你在写class的时候需要写相应的Config文件,所以你要初始化很久以前的Service类的时候,前人都已经写好了Config文件,你直接在需要用的地方注入这个Service就可以了。这大大增加了项目的可维护性且降低了开发难度。

5.2 关系管理方式

对于IoC Service Provider 来说,它也同样需要知道自己所管理和掌握的被注入对象和依赖对象之间的对应关系。有以下几种方式

5.2.1 直接编码方式:

在容器启动之前,我们就可以通过程序编码的方式将被注入对象和依赖对象注册到容器中,并明确它们相互之间的依赖关系

IoContainer container  =  …;//获取容器
Container.register(FXNewsProvider.class,new  FXNewsProvider());//往容器中注册对象
Container.register(IFXNewsListener.class, new DowJonesNewsListener());//往容器中注册对象
…
FXNewsProvider newsProvider  =  (FXNewsProvider) container.get(FXNewsProvider.class);
//从容器中获取对象
newProvider.getAndPersistNews();//调用获取到的对象的方法

5.2.2 配置文件方式:

配置文件类别有 普通文本文件,properties文件,XML文件等,最为常见的还是通过XML文件来管理和保存依赖注入的信息。

5.2.3 元数据方式:略

6.好了,以上IoC的一些介绍内容就是我经过查找资料以及我的个人见解得出的总结,欢迎各位进行评论,一起学习

猜你喜欢

转载自blog.csdn.net/qq_36000403/article/details/83155133
今日推荐