依赖注入
DI是如何实现的
首先来考虑下面的Knight类
package spring;
public class DamselRescuingKnight implements Knight{
private RescueDamselQuest quest;
public DamselRescuingKnight(){
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest(){
quest.embark();
}
}
在这段代码中,DamselRescuingKnight在他的构造函数中创建了RescueDamselQuest,这使得DamselRescuingKnight和RescueDamselQuest紧紧耦合在了一起,因此极大地限制了这个骑士执行探险的能力。如果一个少女需要救援,这个骑士能够召之即来,但如果一条恶龙需要杀掉,这个骑士就爱莫能助了。
耦合具有两面性。一方面,紧密耦合的代码难以测试,难以复用、难以理解,并典型的表现出“打地鼠”式的bug特性(修补一个bug将出现一个或更多的新的bug)。另一方面,一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了。
通过DI,对象的依赖关系将由系统中负责协调个对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理他们的依赖关系,依赖关系将被自动注入到需要他们的对象中。
观察以下代码
package spring;
public class BraveKnight implements Knight{
private Quest quest;
public BraveKnight(Quest quest){
this.quest=quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
与之前的方式不同,BraveKnight没有自行创建探险任务,而是在构造式吧探险任务作为构造器参数传入,这是依赖注入的方式之一,构造器注入。
BraveKnight没有与任何特定的Quest实现发生耦合。主要被要求的任务实现了Quest借口,具体是什么类型的任务对它而言就无关紧要了。这就是DI所带来的最大收益——松耦合。如果一个对象只通过接口来表明依赖关系,那么这种依赖就能在对象本身毫不知情的情况下,用不同的具体实施先进行替换。
将Quest注入到Knight中
package spring;
import java.io.PrintStream;
public class SlayDragonQuest implements Quest{
private PrintStream stream;
public SlayDragonQuest(PrintStream stream){
this.stream=stream;
}
public void embark(){
stream.println("Embarking on quest to slay the dragon!");
}
}
创建应用组件之间协作的行为成为配装。下面展示分别展示了基于XML和Java程序进行配装的配置文件,将BraveKnight、SlayDragonQuest和printStream配装到一起。
package config;
import org.springframework.context.annotation.Configuration;
import spring.BraveKnight;
import spring.Knight;
import spring.Quest;
import spring.SlayDragonQuest;
import org.springframework.context.annotation.Bean;
@Configuration
public class KnightConfig {
@Bean
public Knight knight(){
return new BraveKnight(quest());
}
@Bean
public Quest quest(){
return new SlayDragonQuest(System.out);
}
}
面向切面编程
AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
系统有许多不同的组件组成,每一个组件负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责、诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中。
AOP应用
假设我们需要使用一个吟游诗人的服务类来记录骑士的所有事迹。
Minstrel类
package spring;
import java.io.PrintStream;
public class Minstrel {
private PrintStream stream;
public Minstrel(PrintStream stream){
this.stream=stream;
}
//探险前调用
public void singBeforeQuest(){
stream.println("the knight is so brave!");
}
public void singAfterQuest(){
stream.println("the brave knight did embark on a quest!");
}
}
这样的情况下BraveKnight必须调用Minstrel的方法才能实现吟游诗人为他记载事迹。
如果利用AOP,就可以声明吟游诗人必须歌颂骑士的事迹,而骑士本身并不用直接访问Minstrel的方法。
要将Minstrel抽象成一个切面,要在一个Spring配置文件中声明他。