为了下降 Java 开发的复杂性,Spring 采起了如下 4 种关键策略:spring
这是一个简单普通的 Java 类 —— POJO。没有任何地方代表它是一个 Spring 组件。Spring 的非侵入编程模型意味着这个类在 Spring 应用和非 Spring 应用中均可以发挥一样的做用。express
public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest){ this.quest = quest; } public void embarkOnQuest(){ quest.embark(); } }
Spring 赋予 POJO 魔力的方式之一就是经过 DI 来装配它们。让咱们看看 DI 是如何帮助应用对象彼此之间保持松散耦合的。编程
BraveKnight 足够灵活能够接受任何赋予他的探险任务:安全
public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest){ //构造器参数注入 this.quest = quest; } public void embarkOnQuest(){ quest.embark(); } }
这里的要点是 BraveKnight 没有与任何特定的 Quest 实现发生耦合。对它来讲,被要求挑战的探险任务只要实现了 Quest 接口,那么具体是哪一种类型的探险就可有可无了。这就是 DI 所带来的最大收益 —— 松耦合。若是一个对象只经过接口(而不是具体实现或初始化过程)来代表依赖关系,那么这种依赖就可以在对象自己绝不知情的状况下,用不一样的具体实现进行替换。模块化
SlayDragonQuest 是要注入到 BraveKnight 中的 Quest 实现:函数
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!"); } }
这里最大的问题在于,咱们该如何将 SlayDragonQuest 交给 BraveKnight 呢?又如何将 PrintStream 交给 SlayDragonQuest 呢?
建立应用组件之间协做的行为一般称为装配(wiring)。this
Spring 提供了基于 Java 的配置,可做为 XML 的替代方案:编码
@Configuration //至关于spring的配置文件XML public class KnightConfig { @Bean //声明为 Spring 中的 bean,bean 的各类名称……虽然 Spring 用 bean 或者 JavaBean 来表示应用组件,但并不意味着 Spring 组件必需要遵循 JavaBean 规范。一个 Spring 组件能够是任何形式的 POJO。 public Knight knight(){ return new BraveKnight(quest()); } @Bean public Quest quest() { return new SlayDragonQuest(System.out); } }
尽管 BraveKnight 依赖于 Quest,可是它并不知道传递给它的是什么类型的 Quest,也不知道这个 Quest 来自哪里。与之相似,SlayDragonQuest 依赖于 PrintStream,可是在编码时它并不须要知道这个 PrintStream 是什么样子的。只有 Spring 经过它的配置,可以了解这些组成部分是如何装配起来的。这样的话,就能够在不改变所依赖的类的状况下,修改依赖关系。spa
启动程序:日志
public class KnightMain { public static void main(String[] args) { KnightConfig knightConfig = new KnightConfig(); Knight knight = knightConfig.knight(); knight.embarkOnQuest(); } }
获得 Knight 对象的引用后,只需简单调用 embarkOnQuest() 方法就能够执行所赋予的探险任务了。注意这个类彻底不知道咱们的英雄骑士接受哪一种探险任务,并且彻底没有意识到这是由 BraveKnight 来执行的。只有KnightConfig知道哪一个骑士执行哪一种探险任务。
DI 可以让相互协做的软件组件保持松散耦合,而面向切面编程(aspect-oriented programming,AOP)容许你把遍及应用各处的功能分离出来造成可重用的组件。
图 1.2 展现了这种复杂性。左边的业务对象与系统级服务结合得过于紧密。每一个对象不但要知道它须要记日志、进行安全控制和参与事务,还要亲自执行这些服务。
AOP 可以使这些服务模块化,并以声明的方式将它们应用到它们须要影响的组件中去。
好比看看《Spring in Action》书中的一个例子:使用骑士的例子,这里咱们为他添加一个切面,假设咱们须要使用吟游诗人这个服务类来记载骑士的全部事迹。
//添加吟游诗人这个服务类 public class Minstrel { private PrintStream stream; public Minstrel(PrintStream stream) { this.stream = stream; } public void singBeforeQuest() { stream.println("Fa la la, the knight is so brave!"); } public void singAfterQuest() { stream.println("Tee hee hee, the brave knight " + "did embark on a quest!"); } }
咱们能够经过DI构造函数来注入这个类。
public class BraveKnight implements Knight { private Quest quest; private Minstrel minstrel; public BraveKnight(Quest quest, Minstrel minstrel) { this.quest = quest; this.minstrel = minstrel; } public void embarkOnQuest() throws QuestException { minstrel.singBeforeQuest(); quest.embark(); minstrl.singAfterQuest(); } }
可是管理他的吟游诗人并非骑士职责范围内的工做。毕竟,用诗歌记载骑士的探险事迹,这是吟游诗人的职责。为何骑士还须要提醒吟游诗人去作他分内的事情呢?
此外,由于骑士须要知道吟游诗人,因此就必须把吟游诗人注入到 BarveKnight 类中。这不只使 BraveKnight 的代码复杂化了,并且还让我疑惑是否还须要一个不须要吟游诗人的骑士呢?若是 Minstrel 为 null 会发生什么呢?我是否应该引入一个空值校验逻辑来覆盖该场景?
简单的 BraveKnight 类开始变得复杂,若是你还须要应对没有吟游诗人时的场景,那代码会变得更复杂。但利用 AOP,你能够声明吟游诗人必须歌颂骑士的探险事迹,而骑士自己并不用直接访问 Minstrel 的方法。
<bean id="minstrel" class="sia.knights.Minstrel"> <constructor-arg value="#{T(System).out}" /> </bean> <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 的 aop 配置命名空间把 Minstrel bean 声明为一个切面。首先,须要把 Minstrel 声明为一个 bean,而后在元素中引用该 bean。为了进一步定义切面,声明 (使用)在 embarkOnQuest() 方法执行前调用 Minstrel 的 singBeforeQuest() 方法。这种方式被称为前置通知(before advice)。同时声明(使用)在 embarkOnQuest() 方法执行后调用 singAfterQuest() 方 法。这种方式被称为后置通知(after advice)。
这就是咱们须要作的全部的事情!经过少许的 XML 配置,就能够把 Minstrel 声明为一个 Spring 切面