耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中,很多的J2EE项目均采用了IOC框架产品Spring。
面向对象的核心思想简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,如下图:
由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了, 全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合 在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
在没有IOC容器之前,一个对象需要自己决定何时、如何创建自己依赖的其它对象(因此需要知道要依赖引入对应的对象类),有IOC容器后,由它负责所有的对象的创建,并将每个对象的依赖的其它对象关系注入,解决掉对象何时、如何创建、如何注入其依赖对象的问题。即对象原来主动的控制自己的依赖对象变成了被动的由IOC容器动态的注入,因此IOC被叫做控制反转。而IOC容器解决问题的本质是对象间的依赖关系、为对象动态注入依赖的其它对象,从这个角度讲又是依赖注入(DI)。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象。这种编程方式可以让对象在生成时才决定到底是哪一种对象。反射的应用是很广泛的,很多的成熟的框架,比如象Java中的Hibernate、Spring框架,.Net中 NHibernate、Spring.Net框架都是把“反射”做为最基本的技术手段。
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看 作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来 看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护 性。
使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的缺点,做到心中有数,杜绝滥用框架。
第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框 架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同 样的知识体系。
第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
第三、具体到IOC框架产品(比如:Spring)来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深 入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,象WEB2.0网站就是这种情况。
所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。
Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
所谓的控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器帮忙来实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体的表现就是我们的配置文件。那么就会有以下2个问题:
1.对象和对象的关系怎么表示?
可以用xml,properties等文件表示
2.描述对象关系的文件放在哪里?
可能是classpath,fileSystem,或者是URL网络资源,ServletContext等
有了配置文件,还需要对配置文件进行解析,解析过程中可能又会遇到以下几个问题
1.不同的配置文件对对象的描述不一样,如标准的,自定义声明式的,如何统一?
使用Resource接口来统一
2.在内部需要有一个统一的关于对象的定义
统一使用BeanDefinition
3.如何对不同的配置文件进行解析?
使用BeanDefinitionReader和BeanFactory
4.需要对不同的配置文件语法,采用不同解析方式?
使用ApplicationContext
以上5个全是接口,都有各式各样的实现,正是这5个接口定义了spring ioc容器的基本代码组件结构,而其组件各种实现的组合关系组成了一个运行时的具体容器。
(1)Resource
是对资源的抽象,每一个接口实现类都代表了一种资源类型,如ClasspathResource、URLResource、FileSystemResource等。每一个资源类型都封装了对某一种特定资源的访问策略。它是spring资源访问策略的一个基础实现,应用在很多场景。
(2)BeanDefinition
又来抽象和描述一个具体bean对象。是描述一个bean对象的基本数据结构
(3)BeanDefinitionReader
将外部资源对象描述的bean定义,统一转化为统一个内部数据结构BeanDefinition。对应不同的描述需要有不同的Reader。如XMLBeanDefinitionReader用来读取xml描述配置的bean对象
(4)BeanFactory
用来定义一个很纯粹的bean容器,它是一个bean容器的必备结构。同时和外部应用环境等隔离。BeanDefinition是它的基本数据结构。他维护一个BeanDefinitions Map,并可以根据BeanDefinition的描述进行bean的创建和管理。
(5)ApplicationContext
从名字上来看叫做应用上下文,是和应用环境息息相关的。这个就是我们平时开发中经常直接使用的一个类,应用上下文,或者也叫做spring容器。其实它的基本实现是会持有一个BeanFactory对象,并基于此提供一些包装和功能扩展。为什么要这么做呢?因为BeanFactory实现了一个容器基本结构和功能,但是与外部环境隔离。那么读取配置文件,并将配置文件解析成BeanDefinition,然后注册到BeanFactory的这一个过程的封装自然就需要ApplicationContext。ApplicationContext和应用环境息息相关,常见的实现类有ClassPathXMLApplicationContext、FileSystemXMLApplicationContext、WebApplicationContext等。ClassPath、xml、FileSystem等词都代表了应用和环境相关的一些意思。
以上5个组件基本代表了ioc容器的一个最基本组成,而组件的组合是放在ApplicationContext的实现这一层来完成的
以ClassPathXMLApplicationContext为例,展示5个组件的组合关系