15.2 依赖注入

15.2 依赖注入

在过去数年间,依赖注入技术作为代码可测试性的一个解决方案已经被广泛应用。实际上,Spring、谷歌Guice等伟大框架都采用了依赖注入技术。那么,什么是依赖注入技术?

什么是依赖注入

很多人在使用中并不区分依赖注入和控制反转IoC),尽管Martin Fowler在其文章中已分析了二者的不同。
http://martinfowler.com/articles/injection.html

实例

简单来说,依赖注入的情况如下。
有两个组件ABA依赖于B。假定A是一个类,且A有一个方法importantMethod使用到了B,如下:

1
2
3
4
5
6
7
8
public class A {
public void importantMethod() {
B b = ... // get an instance of B
b.usefulMethod();
...
}
...
}

要使用**B类,A类必须先获得组件B的实例的引用**。若B是一个具体类,则可通过new关键字直接创建组件B实例。但是,如果B是接口,且有多个实现,则问题就变得复杂了。我们固然可以任意选择接口B的一个实现类,但这也意味着A的可重用性大大降低了,因为无法采用B的其他实现。
依赖注入是这样处理此类情景的:接管对象的创建工作,并将该对象的引用注入需要该对象的组件
以上述例子为例,
依赖注入框架会分别创建对象A和对象B,将对象B注入到对象A

依赖注入方式

**为了能让框架进行依赖注入,程序员需要编写特定的set方法或者构造方法**。

setter方法注入

例如,为了能将B注入到A中,类A会被修改成如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class A {
private B b;
public void importantMethod() {
// no need to worry about creating B anymore
// B b = ... // get an instance of B
b.usefulMethod();
...
}
//提供setter方法
public void setB(B b) {
this.b = b;
}
}

修改后的类A新增了一个setter方法,setter方法将会被框架调用,用以注入一个B的实例。由于对象依赖由依赖注入,类AimportantMethod方法不再需要在调用BusefulMethod方法前去创建一个B的实例。

构造器注入

当然,也可以采用构造器方式注入,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class A {
private B b;
//提供构造器
public A(B b) {
this.b = b;
}
public void importantMethod() {
// no need to worry about creating B anymore
// B b = ... // get an instance of B
b.usefulMethod();
...
}
}

本例中,**Spring会先创建B的实例,再创建实例A,然后把B注入到实例A
注意
Spring管理的对象称为beans
通过提供一个
控制反转容器**(或者依赖注入容器),Spring为我们提供一种可以“聪明”地管理Java对象依赖关系的方法。其优雅之处在于,程序员无须了解Spring框架的存在,更不需要引入任何Spring类型。

版本支持

  • 从1.0版本开始,**Spring就同时支持setter构造器方式的依赖注入**。
  • 从2.5版本开始,通过Autowired注解,Spring支持基于field方式的依赖注入,但缺点是程序必须引入org.springframework.beans.factory.annotation.Autowired,这对Spring产生了依赖,这样,程序无法直接迁移到另一个依赖注入容器内。

使用Spring,程序几乎将所有重要对象的创建工作移交给Spring,并配置如何注入依赖

Spring支持XML和注解两种配置方式。此外,还需要创建一个ApplicationContext对象,ApplicationContext对象代表一个Spring控制反转容器,

ApplicationContext接口

org.springframework.context.ApplicationContext接口有多个实现,包括ClassPathXmlApplicationContextFileSystemXmlApplicationContext。这两个实现都需要至少一个包含beans信息的XML文件ClassPathXmlApplicationContext尝试在类加载路径中加载配置文件,而FileSystemXmlApplicationContext则从文件系统中加载。
下面为从类路径中加载config1.xmlconfig2.xmlApplicationContext创建的一个代码示例:

1
2
3
ApplicationContext context =
new ClassPathXmlApplicationContext(
new String[] {"config1.xml", "config2.xml"});

可以通过调用ApplicationContextgetBean方法获得对象:

1
Product product = context.getBean("product", Product.class);

getBean方法会查询idproduct且类型为Productbean对象

理想情况下,我们仅需在测试代码中创建一个ApplicationContext,应用程序本身无须处理。对于Spring MVC应用,可以通过一个Spring Servlet来处理ApplicationContext,而无须直接处理。