ITEEDU

Spring Gossip: Dependency Injection

IoC模式基本上是一个高层的概念,在 Martin Fowler 的 Inversion of Control Containers and the Dependency Injection pattern 中谈到,实现IoC有两种方式:Dependency Injection与Service Locator,Spring 所采用的是Dependency Injection 来实现 IoC,中文翻译为依赖注入,依赖注入的意义是:「保留抽象接口,让组件依赖于抽象接口,当组件要与其它实际的对象发生依赖关系时,藉过抽象接口来注入依 赖的实际对象。」

看看下面这个程序:

public class BusinessObject {
	private FloppyWriter writer = new FloppyWriter();
	....
	public void save() {
		...
		writer.saveToFloppy();
	}
}

BusinessObject 依赖于实际的 FloppyWriter,为了让 BusinessObject 获得重用性,不让 BusinessObject 直接依赖于实际的 FloppyWriter,而是依赖于抽象的接口:

public interface IDeviceWriter {
	public void saveToDevice();
}
public class BusinessObject {
	private IDeviceWriter writer;
	public void setDeviceWriter(IDeviceWriter writer) {
		this.writer = writer;
	}
	public void save() {
		....
		writer.saveToDevice();
	}
}
public class FloppyWriter implement IDeviceWriter {
	public void saveToDevice() {
		....
		// 实际储存至Floppy的程序代码
	}
}
public class UsbDiskWriter implement IDeviceWriter {
	public void saveToDevice() {
		....
		// 实际储存至UsbDisk的程序代码
	}
}



如果今天BusinessObject想要与UseDiskWriter对象发生依赖关系,可以这么建立:

businessObject.setDeviceWriter(new UsbDiskWriter()); 

由于BusinessObject依赖于抽象接口,在需要建立依赖关系时,可以透过抽象接口注入依赖的实际对象。

依赖注入在Martin Fowler的文章中谈到了三种实现方式:Interface injection、Setter injection 与 Constructor injection。并分别称其为Type 1 IoC、Type 2 IoC 与 Type 3 IoC。

上面的BusinessObject所实现的是Type 2 IoC,透过Setter注入依赖关系,而Type 3 IoC,则在是建构式上注入依赖关系,例如:

public class BusinessObject {
	private IDeviceWriter writer;
	public BusinessObject(IDeviceWriter writer) {
		this.writer = writer;
	}
	public void save() {
		....
		writer.saveToDevice();
	}
}

Spring 鼓励的是 Setter injection,但也允许您使用 Constructor injection,使用 Setter 或 Constructor 来注入依赖关系视您的需求而定,使用 Constructor 的好处之一是,您可以在建构对象的同时一并完成依赖关系的建立,然而如果要建立的对象关系很多,则会在建构式上留下一长串的参数,这时使用 Setter 会是个不错的选择,另一方面,Setter 可以有明确的名称可以了解注入的对象会是什么,像是setXXX()这样的名称会比记忆Constructor上某个参数位置代表某个对象来得好。

Type 1 IoC是Interface injection,使用Type 1 IoC时会要求实作接口,这个接口是为容器所用的,容器知道接口上所规定的方法,它可以呼叫实作接口的对象来完成依赖关系的注入,例如:

public interface IDependencyInjection {
	public void createDependency(Map dependObjects);
}
public class BusinessObject implement IDependencyInjection {
	private Map dependObjects;
	public void createDependency(Map dependObjects) {
		this.dependObject = dependObjects;
		// 在这边实现与BusinessObject的依赖关系
		......
	}
	public void save() {
		....
		writer.saveToDevice();
	}
}

如果要完成依赖关系注入的对象,必须实现IDependencyInjection接口,并交由容器管理,容器会呼叫被管理对象的createDependency()方法来完成依赖关系的建立。

在上面的例子中,Type 1 IoC要求BusinessObject实现特定的接口,这就使得BusinessObject依赖于容器,如果日后BusinessObject要脱离 目前这个容器,就必须修改程序,想想在更复杂的依赖关系中产生更多复杂的接口,组件与容器(框架)的依赖会更加复杂,最后使得组件无法从容器中脱离。

所以Type 1 IoC具有强的侵入性,使用它来实现依赖注入会使得组件相依于容器(框架),降低组件的重用性。

Spring的核心是个IoC容器,您可以用Setter或Constructor的方式来实现您的业务对象,至于对象与对象之间的关系建立,则透过组态 设定,让Spring在执行时期根据组态档的设定来为您建立对象之间的依赖关系,您不必特地撰写一些Helper来自行建立这些对象之间的依赖关系,这不 仅减少了大量的程序撰写,也降低了对象之间的耦合程度。