Spring简化Java开发的四个关键策略
- 基于POJO的轻量级和最小侵入编程
- 使用依赖注入(DI-Dependency Injection)和面向接口编程实现松耦合
- 基于切面和惯例的声明式编程
- 利用切面和模板减少样板代码
依赖注入
一般来说,一个功能的实现是由多个类之间相互配合来完成的,那么这些类之间就产生了依赖关系。如果由类自己管理他们的依赖对象的引用,那么将会导致高耦合。
我们引用书中经典的骑士探险的例子来说明一下糟糕的高耦合状态。
/**
* 高耦合代码
*/
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest; //探险接口的具体实现——营救少女
public DamselRescuingKnight(RescueDamselQuest quest) {
this.quest = new RescueDamselQuest(); //与RescueDamselQuest紧耦合
}
@Override
public void embarkOnQuest() {
quest.embark(); //执行任务
}
}
再来看一下低耦合的代码
/**
* 低耦合代码
*/
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
@Override
public void embarkOnQuest() {
quest.embark();
}
}
不难发现,第一位骑士明确依赖了一个具体的探险实现(营救少女),并在构造器中创建了该实现的对象,那么这位骑士的能力已经被“写死”,无法获得其他的能力;相比下面这位骑士,他只引入了表示探险的接口,并将它作为参数由构造器传入(依赖注入之构造器注入)。这样依赖耦合度就大大降低,我们也任意传入探险接口的实现来让这位骑士获得不同的能力。
小结
通过DI,对象的依赖关系将由系统中负责协调各种对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,依赖关系将被自动地注入到需要它们的对象中去。
DI带来的最大好处就是松耦合,对象只通过接口表明依赖关系(而不是通过具体实现或初始化过程),那么这种依赖将会在对象毫不知情的情况下替换其依赖的具体实现。
装配
创建应用组件之间协作的行为通常称为装配(wiring)。
Spring实现装配的方式总的来说可分为以下两种:
- 基于JavaConfig的配置(Spring 3.2版本引入,推荐)
- 基于XML的配置
关于以上两种装配方式的代码实现网上已有很多例子,在此就不再赘述。
切面
诸如日志、安全、事务等模块本身与业务逻辑无关,但他们仍然需要贯穿整个系统来配合系统的运作。
这些分散在系统多处的、与业务逻辑相分离的功能被称为横切关注点。
横切关注点被模块化为特殊的类,这些类则被称为切面。
利用切面,可以将与业务逻辑相分离的功能做成可复用的组件,它们包裹在业务组件周围,并通过声明式配置将他们融入到业务模块中,从而核心业务代码与这些功能代码分离。
同样引入书中骑士和诗人的例子,在这个例子中,核心业务就是骑士去探险,诗人吟诗则是贯穿其中的其他功能。
先来看一下诗人类:
/**诗人*/
public class Minstrel {
private PrintStream printStream;
public Minstrel(PrintStream printStream) {
this.printStream = printStream;
}
/**骑士探险前调用*/
public void singBerforQuest() {
printStream.print("啊!勇敢的骑士出发了~");
}
/**骑士探险后调用*/
public void singAfterQuest() {
printStream.print("Oh!这个骑士活着回来了!");
}
}
在不使用面向切面编程时,如果想要诗人在骑士出发前和归来后都吟诗一首,那么久必须由骑士管理这些诗人,好像是骑士在出发前后命令诗人吟诗。
/**骑士*/
public class BravenKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BravenKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
@Override
public void embarkOnQuest() {
minstrel.singBerforQuest(); //探险前吟诗
quest.embark(); //出发探险
minstrel.singAfterQuest(); //探险后吟诗
}
}
然而骑士最核心的任务是探险,不应该去管理诗人的行动,所以我们把诗人作为切面,利用AOP改善。
假设我们已经在配置文件中注册了骑士、探险、诗人的bean的id分别为knight、quest和minstrel,下面来配置AOP
<aop:config>
<aop:aspect ref="minstrel"> <!-- 将诗人类作为切面 -->
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"> <!-- 定义切点 -->
<aop:before pointcut-ref="embark" method="singBeforeQuest"> <!--
定义前置通知 -->
<aop:after pointcut-ref="embark" method="singAfterQuest"> <!-- 定义后置通知 -->
</aop:aspect>
</aop:config>
小结
前置通知会在切点前执行,后置通知会在切点后执行,所以效果和之前一样,仍是骑士出发前后诗人吟诗,而这次探险的业务代码中就不会显示得揉入诗人吟诗的代码了,二者成功地分离。
使用模板消除样板代码
Spring使用模板消除了样板代码,从而可以投入更多的经历在业务逻辑的开发上。最典型的例子是Spring的JdbcTemplate。
网友评论