当一个组件需要外部资源时,最直接也最明智的方法是执行查找,这种行为是主动查找。但这种查找存在一个缺点---组件需要知道如何获得资源。一个好的获取资源的解决方案是应用IoC(Inversion of control,控制反转)。它的思想是反转资源获取的方向。传统的资源查找方式是要求组件向容器发起请求来查找资源,作为回应,容器适时的返回资源。而应用了IoC之后,则是容器主动的将资源推送到它所管理的组件中,组件所要做的只是选择一种合适的方式接受资源。
IoC是一种通用的设计原则,而DI(Dependency Injection,依赖注入)则是具体的设计模式,它体现了IoC的设计原则。DI是IoC典型的实现,IoC和DI容易被混淆。IoC和DI的关系就好比Java中“接口”和“接口实现类”的关系一样。
IoC的理念就是"让别人为你服务",也就是让IoC Service Provider来为你服务.
通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC的场景中,二者之间通过IoC Service Provider 来打交道,所有的被注入对象和依赖对象现在统一由IoC Service Provider统一管理。被注入对象需要什么直接和IoC
Service Provider招呼一声,后者会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider就是通常的IoC容器充当的角色。
IoC就这么简单,原来是需要什么自己去拿,现在需要什么让别人送过来。
在DI模式下,容器全权负责组建的装配,容器以一些预先定义好的方式(例如setter方法,或构造函数,接口注入)将匹配的资源注入到每个组件中。
首先举个例子来说明:
当你来到酒吧,想要喝啤酒的时候,通常会直接召唤服务生,让它为你送来一杯清凉解渴的啤酒。同样的,作为被注入对象,想要IoC Service Provider为你服务,并将所需要的被依赖对象送过来,也需要通过某种方式通知对方。
- 如果你是酒吧常客,或许刚坐好,服务生已经将你最长喝的啤酒放到了你面前;
- 如果你是初次光临,你坐下之后还要召唤服务生
- 还有一种可能,你根本不知道哪个牌子是哪个牌子,这时,你只能打手势或干脆画出商标图告诉服务生你到底想要什么。
【1】构造方法注入
被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,马上可以使用。好比你刚进入酒吧的门,服务生已经将你喜欢的啤酒放到了桌子上。缺点是如果组件有很多的依赖,则构造函数的参数列表将变得冗长,降低代码的可读性。
被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象。构造方法注入方式比较直观,对象被构造完成后,即进入就绪状态,马上可以使用。好比你刚进入酒吧的门,服务生已经将你喜欢的啤酒放到了桌子上。缺点是如果组件有很多的依赖,则构造函数的参数列表将变得冗长,降低代码的可读性。
【2】setter方法注入
通过setter方法,可以更改相应的对象属性,所以当前对象只要为其依赖对象所对应的属性添加setter方法,就可以通过setter方法将相应的依赖对象设置到被注入对象中。setter注入会存在一些问题:(1)容易出现忘记调用setter方法注入组件所需要的依赖,将会导致NullPointerException。(2)代码存在安全问题,第一次注入后,不能阻止再次调用setter,但是由于setter注入简单所以非常流行,相对于构造方法来说更宽松一些,可以在对象构造完成之后再注入。
【3】接口注入
被注入对象如果想要IoC Service Provider为其注入依赖对象,就必须实现某个接口。这个接口提供一个方法,用来为其注入依赖对象。IoC Service Provider 最终通过这些接口来了解应该为被注入对象注入什么依赖对象。接口注入比较死板和繁琐。如果需要注入依赖对象,被注入对象就必须声明和实现另外的接口,而接口又特定于容器,一旦脱离了容器,组件不能重用。这是一种侵入式的注入。这就好像你同样在酒吧点啤酒,为了让服务生理解你的意思,你就必须戴上一顶啤酒式的帽子
IoC容器的特点
【1】无需主动new对象;而是描述对象应该如何被创建即可
IoC容器帮你创建,即被动实例化;
【2】不需要主动装配对象之间的依赖关系,而是描述需要哪个服务(组件),
IoC容器会帮你装配(即负责将它们关联在一起),被动接受装配;
【3】主动变被动,好莱坞法则:别打电话给我们,我们会打给你;
【4】迪米特法则(最少知识原则):不知道依赖的具体实现,只知道需要提供某类服务的对象(面向抽象编程),松散耦合,一个对象应当对其他对象有尽可能少的了解,不和陌生人(实现)说话
【5】IoC是一种让服务消费者不直接依赖于服务提供者的组件设计方式,是一种减少类与类之间依赖的设计原则。
理解IoC容器问题关键:控制的哪些方面被反转了?
1、谁控制谁?为什么叫反转? ------ IoC容器控制,而以前是应用程序控制,所以叫反转
2、控制什么? ------ 控制应用程序所需要的资源(对象、文件……)
3、为什么控制? ------ 解耦组件之间的关系
4、控制的哪些方面被反转了? ------ 程序的控制权发生了反转:从应用程序转移到了IoC容器。
什么是DI
DI:依赖注入(Dependency Injection) :用一个单独的对象(装配器)来装配对象之间的依赖关系 。
理解DI问题关键
谁依赖于谁? ------- 应用程序依赖于IoC容器
为什么需要依赖? ------- 应用程序依赖于IoC容器装配类之间的关系
依赖什么东西? ------- 依赖了IoC容器的装配功能
谁注入于谁? ------- IoC容器注入应用程序
注入什么东西? ------- 注入应用程序需要的资源(类之间的关系)
更能描述容器其特点的名字——“依赖注入”(Dependency Injection)
IoC容器应该具有依赖注入功能,因此也可以叫DI容器
使用DI限制:组件和装配器(IoC容器)之间不会有依赖关系,因此组件无法从装配器那里获得更多服务,只能获得配置信息中所提供的那些。
使用IoC/DI容器开发需要改变的思路
1、应用程序不主动创建对象,但要描述创建它们的方式。
2、在应用程序代码中不直接进行服务的装配,但要配置文件中描述哪一个组件需要哪一项服务。容器负责将这些装配在一起。
其原理是基于OO设计原则的The Hollywood Principle:Don‘t call us, we’ll call you(别找我,我会来找你的)。也就是说,所有的组件都是被动的(Passive),所有的组件初始化和装配都由容器负责。组件处在一个容器当中,由容器负责管理。
IoC容器功能:实例化、初始化组件、装配组件依赖关系、负责组件生命周期管理。
本质:
IoC:控制权的转移,由应用程序转移到框架;
IoC/DI容器:由应用程序主动实例化对象变被动等待对象(被动实例化);
DI: 由专门的装配器装配组件之间的关系;
IoC/DI容器:由应用程序主动装配对象的依赖变应用程序被动接受依赖