详细解读Spring的IOC和DI?
怎样理解Spring的IOC和DI呢?
我们先从一些其它的基本概念开始说起。
先说依赖。依赖是每个系统,每个程序员都要面对的问题。小到对象与对象之间的依赖,模块与模块之间的依赖,大到系统与系统之间的依赖。完善每个部分,并把他们有效的整合到一起,这就是程序员的工作。
例如我们经常这样处理:
假如Class A依赖于Class B,我们会在Class A中使用new关键字:
B b= new B();
或者Class B还实现了接口Interface C,我们也可以写成:
C c = new B();
注意这里使用的是new B()而不是new C()。因为C是一个接口,所以它是不能被实例化的。
这种情况下,我们就说Class A与Class B是强耦合的。也许我们的系统曾经或者现在仍然,是这样一个又一个类,耦合依赖组合而成的。这时候,如果我们要修改其中的一个类,往往牵一发而动全身,影响范围广,改动量大。
那么如何改进这种情况呢?根本思路就是解耦合。IOC就是解耦合的一种设计思想,而DI可以理解为IOC的一种实现方式。
再回到之前描述的情况。我们想象一种场景,就是Class B在系统的升级过程中完全失去了价值,并需要完全替代为Class D。这时候,如果我们采用的是B b = new B()的形式,那么就需要把所有的B b = new B()都改成D d = new D(),然后再把原来所有对b的引用改为对d的引用。
如果之前使用的是Interface C的话,情况会稍微好一些。我需要做的是,把C c = new B(),改成C c = new D()。当然,前提是D也实现了Interface C。
那还有没有更好的方式,能够让我在B变成D时,A中的代码完全不需要修改,即A和B完全解耦合呢?
答案之一就是IOC,或者,我们称之为IOC容器。
IOC的底层实现使用了反射,设计模式为工厂模式。说到这儿,有些许领悟,又有些许迷茫。反射的效率不是比直接创建低10倍左右的效率吗?我们说那是以前,现在也就是1到2倍的差距。我们认为,如果能完全实现A和B的解耦合,那么,这点效率的牺牲是完全值得的。
好,我们说回到IOC容器。IOC容器带给我们的便利是,当A需要使用C的一个实现类时,我并不会直接使用new B()或new D(),而是把我的需求告诉IOC,IOC会根据情况,自动将一个实现了C接口的类的实例创建,并注入给我。
注意这里的表述,有三个关键点。
1. B和D都是C接口的实现类。这是IOC的一个基本要求。
2. C接口的实例是由IOC容器给出的,而不是由A创建的。这就是控制反转,即实例创建的控制权由依赖类转换到了IOC容器。
3. 在表述中,我们使用了注入。这里的注入,便是DI,即依赖注入。回忆前文中说过,IOC是一种设计思想,DI则是IOC的一种实现方式。
引用之前读过的大神文章中的一个例子。IOC设计模式,好比是一个电脑主机。主机上有一个USB接口。我不管你的介质是什么,是U盘还是移动硬盘,你只要采用了USB接口的标准,就可以连接电脑主机。IOC也是如此,IOC不管你C接口的实现类是B还是D,也不管你具体是怎么实现的。你只要实现了C接口,IOC就可以把你的实例注入进来,供A使用。
说到这儿,也许又有了一些疑问。IOC是怎么知道,A要使用的是接口C的哪个实现类呢?如果接口C没有实现类会怎么样呢?
要解决这个疑问,就要回到工厂模式。我们知道,工厂模式的思路是把实例创建和创始化的工作交给一个工厂类,这个实例怎么创建,怎么初始化,是由工厂类决定的。我们说Spring的IOC是工厂模式的一种升级模式。
升级之处在于,IOC把工厂类怎么创建和初始化实例,从写在代码中,改成了写在配置文件中,便形成了我们在spring.xml文件中写的各种关于Bean的配置。Bean的实质,其实就是告诉IOC容器,我要使用哪个实现类,我的初始化参数是什么。
这个时候,你是否想起你还使用过无配置模式,即使用注解?注解的形式,是IOC对工厂模式的另一种升级。注解的作用也是为了告诉容器实现类的相关信息。
然后,我们又回忆起在Spring使用时,曾经出现过的一些错误。比如说会报说找不到某个接口的实现类,或者是某个接口的实现类多于一个,并且没有提供选择策略。这些错误,都是我们在使用配置文件或使用注解时,没有注明某个接口有哪些实现类或者是给某个接口注明了多于一个实现类而造成的。
好,到此,IOC和DI我们基本上解释清楚了。再回到开始描述的情况。当我们使用了IOC时,如果又遇到了要把B改成D的情况。这时候,我们不需要修改任何实现代码。只需要在配置文件中的相关配置由B改成D即可。或者,我们使用了注解形式,那么,我们要做的就是把B类上的@Component注解去掉,并在D上加上@Component注解。如果,你使用的是@Component注解的话。
最后,我们来简单表述一下IOC或者DI的优劣势。
劣势:思路转换需要一定的学习成本;效率上有一定的牺牲。
优势:解耦合。没错,IOC就是要完成解耦合这样一个单纯的目的。至于解耦合的好处有哪些?就由你自己领悟或者,我们以后再说。