对Spring依赖注入(DI)理解

依赖注入(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知道哪个骑士执行了什么任务。依赖注入把对象的创建交给外部去管理这就是依赖注入最大的好处,实现了相互协作的组件却保持松散耦合。

猜你喜欢

转载自blog.csdn.net/qq_41304534/article/details/80627418