依赖注入
依赖注入,DI,英文名字是Dependency Injction,也叫控制反转,ICO,这两个名词是一个概念。 依赖注入的目的:就是为了将各层对象以松耦合的方式组织在一起,降低程序各层对象之间的耦合性,符合面向对象程序开发的原则, 使得系统维护时变得容易。 举个例子,假如程序集A中有个类A,程序集B中一个接口IB和两个实现类B1,B2。现在要在类A中的某个方法里调用接口IB的对象,A就是调用者,IB就是被调用者。 那么传统的做法是使用new来创建IB对象。但这样就会造成类A强依赖于接口IB的实现。 采用依赖注入的设计模式,就是使用IOC容器来实现接口IB对象的创建。然后将创建的接口IB对象注入到调用者A中。因为这个过程中创建被调用者IB的工作,不再由调用者A来完成,而是又IOC容器来完成,所以叫做控制反转。 常用个IOC框架由Castle Windsor、微软企业库中的Unity、Spring.NET、StructureMap、Ninject等等。 常用的注入方式有构造器注入、属性(Setter)注入和接口注入 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象; 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性; 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。 IOC容器主要是使用反射技术 ============================================================================================================================================================================================================================================================================================================================================================================================================================================ 举个例子,组件A中有类ClassA,组件B中有接口IB和其对应的实现类B1和B2。 那么,现在ClassA需要利用IB接口来做一些事情,例如: publicclass ClassA { publicvoid DoSomething() { IB b = ??? b.DoWork(); } } 现在的问题来了,IB b = ??? 中这三个???要写什么代码?是要写成 IB b = new B1(),还是要写成IB b = new B2() ? 不管是哪一种,都会让ClassA强依赖于IB的实现。 在上面这种方案中,ClassA通过new一个B1或B2来实现对IB的依赖的获取,换句话说,ClassA在主动获取依赖。 这样的设计会让ClassA很难扩展,那我们要改良设计:使用依赖注入。上面说到了,问题出在new这里,也就是依赖是Class去主动获取的,那我们就要解决这个问题:不要去主动获取对IB的依赖(通过new),而让这个依赖从ClassA的外面“注入”进来。注入有多种方式,比较常用的一种是通过构造函数注入,那么,我们要把ClassA改成: publicclass ClassA { private IB b; public ClassA(IB b) { this.b = b; } public DoSomething() { this.b.DoWork(); } } 调用时:B1 b=new B1(); ClassA a=new ClassA(b); a.DoSomething(); 可以看到,通过把IB这个依赖从构造函数中“注”进来后,ClassA就不依赖IB的实现了。还可以发现,这个重构过程中,我们是把"ClassA主动获取对IB的依赖”变成“把对IB的依赖从外部注入到ClassA中”,依赖的方向反转了,所以,依赖注入又称“控制反转”。 上面是依赖注入的所有内容。依赖注入不涉及任何反射、IoC框架(如Unity, Autofac)等内容。要注意这点。 ============================================================================================================================================================================================================================================================================================================================================================================================================================================ IOC的基本概念是:不创建对象,但是描述创建它们的方式。在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务。容器负责将这些联系在一起。 依赖:一个类(A)对另一个类(B)的依赖,也就是一个类需要调用到另一个类 注入:把另一个类(B)注入到类(A)中,以供A使用,不再像以前那样在A类中 new出B类,解了耦合。 ============================================================================================================================================================================================================================================================================================================================================================================================================================================ 所谓依赖,举个例子说明,一个类Person,另一个类Car,如果Person的某个方法比如说drive,需要引用Car,则称Person类依赖于 Car类,延伸到对象,这种依赖关系依然成立,比如说Person类的对象boy依赖于Car类的对象toyota。再讲讲这个drive方法的实现,假定代码如下: Public Person{ ... public void drive(){ Car toyota=new Car("TOYOTA"); toyota.挂档; toyota.踩油门; toyota.打方向; } } 这其中的依赖关系,就导致了对象boy需要负责对象toyota的创建,甚至是整个生命周期的管理,而这样显然会带来耦合度高,不易维护等缺点,比如说要让这个男孩驾驶一辆Audi,则还需要修改类Person的代码。 因此在java的设计理论中就提出了一条非常著名的原则,依赖倒转原则(Dependence Inversion),其核心思想就是要将这种具体类之间的依赖,尽量转换成抽象依赖,也就是说类Person应该依赖于抽象类ICar,而不是具体的类 Car,这里java就大力推荐了抽象和接口的使用,至于抽象和接口之间的区别,任何一本JAVA书籍都有介绍,这里就不再说了。 这个依赖倒转原则在设计模式也体现得非常多,比如说工厂模式和构建模式,个人认为控制反转IoC,其实也可以认为是实现这个原则的一种设计模式。控制反转,其中的控制这个词一直不太理解是什么意思,不过控制反转的另外一种说法也就是依赖注入(dependence injection),个人觉得更易于理解。还是以上文的boy与toyota为例,其核心就是要将boy依赖的对象toyota注入到boy中去,而无需boy自己去引用toyota,这个注入的过程,通常是由一个控制程序来完成的,无需对象去关心,举例如下: Public Person{ private ICar car; public Person(ICar onecar){ car=onecar; } public void drive(){ car.挂档; car.踩油门; car.打方向; } } 这个时候,进行注入并且调用的过程,就很简单了,如下: Toyota toyota=new Toyota(); Person boy=new Person(toyota); boy.drive(); 注:这里我们假定,Toyota类是ICar接口类的一个具体实现。 这个例子就演示一个最简单的注入方式的例子,也就是构造子方式注入,通过将依赖对象注入到对象的构造子中来实现。另外还有一种常用的注入方式,就是属性方式注入,意思就是通过将依赖对象注入到对象的属性中来实现,还是以boy和toyota的例子说明,如下: Public Person{ private ICar car; public Person(){ } public void drive(){ car.挂档; car.踩油门; car.打方向; } public ICar getCar(){ return this.car; } public void setCar(ICar onecar){ car=onecar; } } 这个时候,进行注入并且调用的过程,就变成如下所示: Toyota toyota=new Toyota(); Person boy=new Person(); boy.setCar(toyota); boy.drive(); 至此依赖注入的概念应该比较清楚了,再来看看在Spring中如何实现IoC的,看看Spring如何作为一个成熟的IoC容器,Spring中其实主要通过两个概念来实现IoC,首先通过XML配置文件,将对象和依赖对象都配置到某个XML文件中,当然该XML文件需要符合Spring指定的规范,然后通过架构中的BeanFactroy类,来自动实现上文所述注入过程,还是以boy与toyota为例,如下: 首先,Person类还是一样的, 然后xml配置文件增加点东西-(假定为bean.xml): <?xml version="1.0" encoding="GBK"?> <!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="oneCar" class="Toyota"> <!-- Toyota类是ICar的一个实现--> </bean> <bean id="onePerson" class="Person"> <!--本例以属性方式注入为例 --> <property name="car"> <ref bean="oneCar"></ref> </property> </bean> </beans> 最后,调用的过程,就变成如下: BeanFactory factory=new XmlBeanFactory("bean.xml"); Person boy=(Person )factory.getBean("onePerson"); boy.drive(); --不知是否讲清楚了,呵呵