依赖注入
依赖注入是让对象只通过构造参数、工厂方法的参数或者配置的属性来定义他们的依赖的过程。这些依赖也是其他对象所需要协同工作的对象, 容器
会在创建Bean的时候注入这些依赖。整个过程完全反转了由Bean自己控制实例化或者依赖引用,所以这个过程也称之为“控制反转”
当使用了依赖注入的特性以后,会让开发者更容易管理和解耦对象之间的依赖,使代码变得更加简单。对象之间不再关注依赖,也不需要知道依赖类
的位置。如此一来,开发的类更易于测试 尤其是当开发者的依赖是接口或者抽象类的情况时,开发者可以轻易地在单元测试中mock对象。
依赖注入主要使用两种方式,一种是基于构造函数的注入,另一种的基于Setter方法的依赖注入。
基于构造函数的依赖注入
基于构造函数的依赖注入是由IoC容器来调用类的构造函数,构造函数的参数代表这个Bean所依赖的对象。构造函数的依赖注入与调用带参数的静态
工厂方法基本一样。 调用具有特定参数的静态工厂方法来构造bean几乎是等效的,本讨论同样处理构造函数和静态工厂方法的参数。下面的例子展示了一
个通过构造函数来实现依赖注入的类。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
请注意,这个类没有什么特别之处。 它是一个POJO,它不依赖于容器特定的接口,基类或注解。
解析构造器参数
构造函数的参数解析是通过参数的类型来匹配的。如果在Bean的构造函数参数不存在歧义,那么构造器参数的顺序也就是就是这些参数实例化以及装
载的顺序。参考如下代码:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设ThingTwo和ThingThree类与继承无关,也没有什么歧义。下面的配置完全可以工作正常。开发者无需再到元素中指定构造函数参数的index或type
<beans>
<bean id="thingOne" class="x.y.ThingOne">
<constructor-arg ref="thingTwo"/>
<constructor-arg ref="thingThree"/>
</bean>
<bean id="thingTwo" class="x.y.ThingTwo"/>
<bean id="thingThree" class="x.y.ThingThree"/>
</beans>
当引用另一个bean时,如果类型是已知的,匹配就会工作正常(与前面的示例一样)。当使用简单类型的时候(例如true), Spring IoC容器无法判断值
的类型,所以也是无法匹配的,考虑代码:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造函数参数类型匹配
在前面的场景中,如果使用 type 属性显式指定构造函数参数的类型,则容器可以使用与简单类型的类型匹配。如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
构造函数参数索引
您可以使用index属性显式指定构造函数参数的索引,如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有相同类型的两个参数的歧义。
index 从0开始。
构造函数参数名称
您还可以使用构造函数参数名称消除歧义,如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
需要注意的是,解析这个配置的代码必须启用了调试标记来编译,这样Spring才可以从构造函数查找参数名称。开也可以使用@ConstructorProperties注解来显式声明构造函数的名称。 例如下面代码:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于setter方法的依赖注入
基于setter函数的依赖注入是让容器调用拥有Bean的无参构造函数,或者无参静态工厂方法,然后再来调用setter方法来实现依赖注入。
下面的例子展示了使用setter方法进行的依赖注入的过程。其中类对象只是简单的POJO,它不依赖于容器特定的接口,基类或注解。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext所管理Bean同时支持基于构造函数和基于setter方法的依赖注入,同时也支持使用setter方法在通过构造函数注入依赖之后再次注
入依赖。 开发者在BeanDefinition中可以使用PropertyEditor实例来自由选择注入方式。然而,大多数的开发者并不直接使用这些类,而是更喜欢使用XML
配置来进行bean定义, 或者基于注解的组件(例如使用 @Component,@Controller等),或者在配置了@Configuration的类上面使用@Bean的方法。 然
后,这些源在内部转换为 BeanDefinition的实例,并用于加载整个SpringIoC容器实例。
如何选择基于构造器和基于setter方法?
因为开发者可以混用两种依赖注入方式,两种方式用于处理不同的情况:必要的依赖通常通过构造函数注入,而可选的依赖则通过setter方法注入。其
中,在setter方法上添加@Required 注解可用于构造必要的依赖。
Spring团队推荐使用基于构造函数的注入,因为这种方式会促使开发者将组件开发成不可变对象并且确保注入的依赖不为null。另外,基于构造函数的
注入的组件被客户端调用的时候也已经是完全构造好的 。当然,从另一方面来说,过多的构造函数参数也是非常糟糕的代码方式,这种方式说明类附带了
太多的功能,最好重构将不同职能分离。
基于setter的注入只用于可选的依赖,但是也最好配置一些合理的默认值。否则,只能对代码的依赖进行非null值检查了。基于setter方法的注入有一
个便利之处是:对象可以重新配置和重新注入。 因此,使用setter注入管理 JMX MBeans 是很方便的依赖注入的两种风格适合大多数的情况,但是在使用
第三方库的时候,开发者可能并没有源码,那么就只能使用基于构造函数的依赖注入了。