依赖注入(Dependency Injection)
简称DI是Spring的核心特性之一。
1.DI有什么作用呢?
一般情况,每个对象负责管理与自己有依赖关系的对象的引用,但这通常会导致高度的耦合,而且不利于代码的测试,DI可以降低程序之间的耦合度,并且提高程序的重复使用性,方便测试也方便以后更改。举个例子:
一个骑士可以执行解救少女的任务,用Java代码实现:
package com.spring.di.knights;
//定义接口
public interface Knight {
void Rescue();
}
package com.spring.di.knights;
public class RescuingDamselKnight implements Knight {private RescueDamselQuest quest; //解救少女的任务
public RescuingDamselKnight (){ //接受任务
this.quest= new RescueDamselQuest ();
}
public void Rescue(){
quest.workOn(); //执行任务
}
public static void main(String[] args) {
RescuingDamselKnight aKnight = new RescuingDamselKnight();
aKnight.Rescue();
}
}
public class RescueDamselQuest {
public void workOn() {
System.out.println("The quest is to save a damsel!");
}
}
可以看出,在RescuingDamselKnight 的构造函数中实例化了RescueDamselQuest ,所以骑士只能执行这一个任务。要想执行其他任务,就只能重新赋予属性。一方面,骑士与解救任务紧密耦合在一起,这极大的限制了骑士的能力。另一方面,耦合度太高不利于测试代码。如Rescue()方法执行的时候, quest.workOn()方法也要被调用。
这时候利用DI可以很好的实现解耦合。
同样是骑士执行任务,代码实现:
package com.spring.di.knights;public class BraveKnight implements Knight {
private Quest quest; // 定义接口作为属性,事先并不知道骑士可以进行什么任务
public BraveKnight(Quest quest) {
this.quest = quest; // 传入任务
}
public void Rescue() {
quest.workOn(); // 执行任务
}
}
public interface Quest {
void workOn();
}
与前面不同的是,这次并没有指定骑士的任务,而是在构造的时候将任务作为构造参数传入(即构造器注入)。由于传入的是接口类型,所执行的任务必须先实现Quest,因此骑士可以执行任意实现的quest,从而实现了解耦,这也是DI的最大好处(可以在对象本身不知情的情况下用不同的实现进行替换)。
现在BraveKnight 类可以接受任何Quest的实现,但到底是怎样的任务呢?
明确要执行的任务
package com.spring.di.knights;import java.io.PrintStream;
public class SlayDragonQuest implements Quest {
private PrintStream stream;
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
@Override
public void workOn() {
stream.println("The quest is to slay a dragno!");
}
}
SlayDragonQuest 实现了Quest接口,这样就明确了斩杀魔龙的任务了。
现在有一个可以执行任意任务的骑士,有个斩杀魔龙的任务,但是怎么将任务交给骑士呢?骑士又如何去执行这个任务呢?可以采用Spring的xml装配方式(装配指创建应用组件之间协作的行为)。
将BraveKnight,SlayDragonQuest和PrintStream装配到一起(knights.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.spring.di.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.spring.di.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean>
</beans>
这里BraveKnight和SlayDragonQuest被声明为Spring中的bean。BraveKnight在构造时传入了对SlayDragonQuest的引用,将其作为了构造器参数,SlayDragonQuest也将System.out传入到了SlayDragonQuest的构造器中。
除了XML配置,Spring支持使用Java来描述配置
package com.spring.di.knights;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class KnightConfig {
@Bean
public Knight knight() {
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}
这时的knights.xml为:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入约束 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.spring.di.knights">
</context:component-scan>
</beans>
Spring通过应用上下文(ApplicationContext)装载bean的定义并把他们组装起来,Spring应用上下文全权负责对象的创建和组装。
测试程序
package com.spring.di.knights;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
"/spring/knights.xml");
BraveKnight knight=(BraveKnight) context.getBean(BraveKnight.class);
knight.Rescue();
}
}
这里的main()方法基于knights.xml文件创建了Spring应用上下文,然后获取一个id为knight的bean。得到Knight对象的引用后,调用Rescue()就可以执行赋予的任务了。
文件设置:
注意:KnightMain 类并不知道骑士应该接受哪种任务,而且也没意识到是BraveKnight来执行的。只有knights.xml知道哪个骑士执行了什么任务。依赖注入把对象的创建交给外部去管理,这就是依赖注入最大的好处,实现了相互协作的组件却保持松散耦合。