Spring In Action 4(Spring 实战第四版)翻译与理解 第一章 付诸行动
这几天笔者阅读了一下Spring In Action 4,感觉还是有一定的收获的。在之前的项目上,只会简单地使用Spring MVC,对于很多概念并不理解。现在看看Spring的一些概念,如DI和AOP,以及Spring的各个模块,对于Spring这一整体框架可以有较深的了解。
这几天笔者阅读了一下 Spring In Action 4,感觉还是有一定的收获的。在之前的项目上,只会简单地使用 Spring MVC,对于很多概念并不理解。现在看看 Spring 的一些概念,如 DI 和 AOP,以及 Spring 的各个模块,对于 Spring 这一整体框架可以有较深的了解。
这篇文章将主要以翻译的形式来讲解 Spring,中间夹杂对于原文的理解,以及代码实现,相信对于读者会带来一定的帮助,也是对自己阅读的一个总结。如果读者有不同的见解,还望可以共同探讨一下。顺带推荐一下 Manning 的 In Action 系列,笔者此前读了《机器学习实战》,外加上现在看的《Spring 实战》,感觉真心不错,国外的许多书籍,相比国内简直是良心之作。但是,中文译本的更新永远无法赶上英文原本的进度,就拿 Spring 来说,第三版的中文译本还停留在 Spring 3,然而 Spring 已经升级到了 4 版本,而英文原本紧跟技术进度,相比之下,第四版的中文译本,还不知要到何年何月。当然,文章的翻译除了要花费长时间的努力,还需要译者对技术有较为深入的了解,在此,还是要感谢那些辛勤的贡献者。笔者翻译此书的目的,是为了在读书的同时,能够总结到一些有用的知识,并期望能给读者带来一定的收获。
(题外话:由于开通了邮箱通知功能,近期 Windows10 任务栏右边总是会显示邮件提示。有一些问题,由于笔者也没有去深度地探究 Spring MVC 这个框架,也无法做出具体的回答,但相信前面几篇文章对于入门开发还是存在一定的帮助的。真正的项目开发肯定会比教程中更加复杂,因为除了业务逻辑,还有许多重要的问题,如高并发、多线程,以及至关重要的安全问题,是需要考虑进来的。)
接下来,进入正题。。。
转载请说明出处:Spring In Action 4 (Spring 实战第四版) 翻译与理解 第一章 付诸行动
Part 1 Spring 核心 (Core Spring)
Spring 能做很多事情。但是对于 Spring 的所有的奇妙功能,它们的主要特征是依赖注入 (dependency indection, DI) 和面向方面编程 (aspect-oriented programming, AOP,也可以译为面向切面编程)。
第一章,付诸行动 (Springing into action),我将带给你一个 Spring 框架的快速概览,包括 Spring 中的 DI 和 AOP 的快速概览,并展示它们是如何帮助降低应用组件 (components) 的耦合度的。
第二章,装配 Bean (Wiring Beans),我们将更为深入地探讨如何将应用的各个组件拼凑在一起。我们将着眼于自动配置 (automatic configuration)、基于 Java 的配置,和 XML 配置,这三种 Spring 提供的配置方法。
第三章,高级装配 (Advanced Wiring),将展示一些小把戏和手段,它们将帮助你深度挖掘 Spring 的强大能力,包括有条件的配置 (conditional configuration),用以处理自动装配 (autowiring)、范围控制 (scoping) 以及 Spring 表达式语言 (Spring Expression Language, Spring EL)。
第四章,面向方面的 Spring (Aspect-oriented Spring) ,探究如何使用 Spring 的 AOP 特征来解除系统级的服务 (例如安全和审计 (auditing) ) 以及它们所服务的对象之间的耦合度。这一章为之后的章节搭建了一个起始平台,例如第 9、13 和 14 章,它们讲解了如何在声明式安全 (declarative security) 和缓存。
第一章 付诸行动 (Springing into action)
对于 Java 开发者来说,现在是一个很好的时期。
在 20 年的历史中,Java经历过一些好的时期以及不好的时期。尽管存在着一些粗糙点 (rough spots) ,例如 applets, Enterprise JavaBeans(EJB), Java Data Objects(JDO),以及数不尽的日志框架,Java 仍然拥有着一个丰富的多样化的历史,作为许多企业级软件的平台而言。而且,Spring 在这一段历史上占有很重要的一笔。
在早期,Spring 作为更加重量级的 Java 技术 (特别是 EJB) 的替代品被创造出来。相比 EJB,Spring 提供了一个更轻量更简洁的的编程模型。它给予了普通 Java 对象 (POJO) 更加强大的能力,而这些能力是 EJB 和一些其他 Java 规格才拥有的。
随着时间的过去,EJB 和 J2EE 得到了发展。EJB 开始自身提供简单的面向 POJO 的变成模型。现在 EJB 利用了诸如依赖注入和面向方面变成的思想,可以说其灵感来自于 Spring 的成功。
虽然 J2EE (现在被称为 JEE) 可以赶上 Spring,但是 Spring 从未停止前进的脚步。Spring 持续的发展各个领域。而这些领域,即使是现在,JEE 才刚刚开始开始探索,甚至还未引入。移动开发,社交 API 集成,NoSQL 数据库,云计算,以及大数据只是 Spring 引入的一小部分领域。而且,未来的 Spring 依然一片光明。
正如我所说,对于开发者来说,现在是一个很好的时期。
这本书是对 Spring 的一个探索。在这一章节,我们将在高层次测试 Spring,让你品尝一下 Spring 到底是什么。这一章将让你明白 Spring 到底解决了各种什么样的问题,并且未这本书的其余部分做好准备。
1.1 简化 Java 开发
Spring 是由 Rod Johnson 创造的一个开源框架。Spring 被创造出来以解决企业级应用开发的复杂问题,并且让普通的 JavaBeans (plain-vanilla JavaBeans) 能够完成此前只有 EJB 才可能完成的任务。但是 Spring 的用处并不仅仅局限于服务器端开发。而且 Java 应用可以从 Spring 中获益,例如间接性、可测试性以及松耦合。
另一种意义的 bean...虽然在表示应用组件时,Spring 大方地使用了 bean 和 JavaBean 这两个词,但这并不表示一个 Spring 组件必须严格地遵从 JavaBean 的规格。一个 Spring 组件可以是任何形式的 POJO。在本书中,我假设 JavaBean 是一个宽松的定义,与 POJO 同义。
通过本书你将了解到,Spring 做了许多事情。但是 Spring 提供的几乎每一个功能的根基,是一些非常基础的想法,全部专注于 Spring 的基本任务:Spring简化Java开发。
这里是粗体!大量的框架说要简化某一些事物。但是 Spring 旨在简化 Java 开发这一广泛主题。这还需要更多的解释。Spring 是如何简化 Java 开发的呢?
为了支持对 Java 复杂度的攻击,Spring 利用了 4 个关键策略:
-
利用 POJO 的轻量级与最小侵入式开发 (Lightweight and minimally invasive development with POJOs)
-
通过 DI 和面向接口实现松耦合 (Loose coupling through DI and interface orientation)
-
通过方面和普通惯例实现声明式编程 (Declarative programming through aspects and common conventions)
-
利用方面和模板消除陈词滥调的代码 (Eliminating boilerplate code with aspects and templates)
基本上 Spring 做的每一件事都可以追溯到这四条策略中的一条或几条上。在这一章节的其他部分,我将扩展到这些想法的每一个中去,展示具体的例子以解释 Spring 是如何完成其简化 Java 开发的目标的。让我们从 Spring 如何通过鼓励面向 POJO 的开发来保持最小化侵入式的 (minimally invasive,这点不好翻译,大致意思是不怎么需要修改 POJO 的代码) 。
1.1.1 释放POJOs的力量
如果你做过很长时间的 Java 开发工作,你可能会发现某些框架会牢牢地困住你,它们强制你去继承某一个类或实现某一个接口。侵略式编程模型一个简单目标例子 (easy-target example) 是 EJB 2-era stateless session beans (不详,在此不做翻译) 。但是即使早期的EJBs是这样一个简单目标,侵入式编程编程仍然能在早期版本的 Struts, WebWork, Tapestry,以及无数其他的 Java 规格与框架中找到。
Spring (尽可能地) 避免其 API 污染你的应用代码。Spring 几乎从来不强迫你去实现一个 Spring 接口,或集成一个 Spring 类。反而,一个基于 Spring 的应用中的类通常并没有指示它们为 Spring 所用。最坏情况,一个类可能被 Spring 的一个注解标注,但是它另一方面是一个 POJO。
为了说明,考虑 HelloWorldBean 类。
如你所见,这很简单,就是一个普通的 Java 类,一个 POJO。它并没有什么特殊的东西以表明它是一个 Spring 组件。Spring 的非侵入式编程模型表示,这个类在 Spring 中同样可以很好的工作,就像它能在其他非 Spring 应用中一样。
尽管样式非常简单,POJOs 依然可以很强大。Spring 使得 POJOs 更强大的一个方法是利用 DI 装配 (assembling) 它们。让我们看看 DI 是如何帮助解除应用对象之间的耦合度的。
1.1.2 注入依赖
依赖注入这个词听起来可能有点高大上,让人联想出一些复杂的编程技术或设计模式的概念。但是结果证明,DI 并没有听起来那么复杂。通过在你的项目中利用 DI,你将发现你的代码将得到极大地简化,更容易理解,更容易测试。
DI是如何工作的
任何非平凡的应用 (比 Hello World 要复杂的多) 都是有相互协作的多个类组成的,以执行一些业务逻辑。传统地,每一个对象负责获取对它自己的依赖 (与他协作的对象) 的引用。这将会带来高度耦合和难以测试的代码。
例如,考虑下面的 Knight 类。
package com.gaussic.knights;
// 一个专门拯救少女的骑士
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest; // 拯救少女任务
// 这里出现了与RescueDamselQuest的紧密耦合
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
// 骑士出征
public void embarkOnQuest() {
quest.embark();
}
}
相信大家都知道骑士与龙的故事,一个勇敢的骑士,除了能够打败巨龙之外,还需要拯救被囚禁的少女。但是在上面的 DamselRescuingKnight 中,我们发现他在构造器中创建了他自己的任务,一个 RescueDamselQuest (也就是说,这个骑士构造出来时心里就只有一个任务,就是拯救少女,注意这个任务是发自内心的) 。这导致了 DamselRescuingKnight 与 RescueDamselQuest 的高度耦合,这严重限制了骑士的行动。如果有一位少女需要拯救,那么这个骑士将会出现。但是如果需要屠龙呢,或者需要开个圆桌会议呢,那这个骑士将被一脚踹开。
此外,给 DamselRescuingKnight 写一个单元测试是非常困难的。在这个测试中,你需要能够保证,在骑士的 embarkOnQuest() 方法被调用时,quest 的 embark() 方法也被调用。但是在这里并没有明确的方法来完成它。不幸的是,DamselRescuingKnight 将保持未测试状态。
耦合是一个双头猛兽。一方面,紧密耦合的代码难以测试,难以重用,且难以理解,并且经常表现出“打地鼠”的 bug行为 (修复了一个 bug,又产生了一个或多个新的 bug) 。另一方面,一定数量的耦合还是有必要的,完全不耦合的代码做不了任何事情。为了做一些有用的事情,类之间应该互相具有一定的认识。耦合是必需的,但是需要仔细管理。
利用 DI,对象在构建时被给予它们的依赖,这通过一些定位系统中的每一个对象的三方来实现。对象不需要创建或获得它们的依赖。如图所示,依赖被注入到需要它们的对象中。
为了说明这一点,让我么看看 BraveKnight 类:一个骑士不仅仅要勇敢,还应该可以完成任何到来的任务。
package com.gaussic.knights;
// 一个勇敢的骑士,应该能完成任何到来的任务
public class BraveKnight implements Knight {
private Quest quest; // 任务,这是一个接口
// 构造器,注入Quest
public BraveKnight(Quest quest) {
this.quest = quest;
}
// 骑士出征
public void embarkOnQuest() {
quest.embark();
}
private BraveKnight() {}
}
如你所见,BraveKnight 不像 DamselRescuingKnight 一样创建自己的任务,而是在构造的时候被传入了一个任务作为构造参数 (也就是说,这个骑士构造出来时,有人给了他一个任务,他的目标是完成这个任务,想雇佣兵一样) 。这种类型的注入被称为 构造注入 (constructor injection) 。
此外,勇敢的骑士所赋予的任务是一个 Quest 类型,它是一个接口,所有的具体 quest 都要实现这个接口。因而 BraveKnight 可以挑战 RescueDamselQuest, SlayDragonQuest, MakeRoundTableRounderQuest,或者任何其他的 Quest。
这里的关键点是,BraveKnight 并不与任何一个特定的 Quest 的实现耦合。对于给予了什么任务,他并不介意,只要它实现了 Quest 接口。这就是 DI 的一个关键益处-松耦合。如果一个对象仅仅通过依赖的接口 (而不是它们的实现或者实例化) 来了解这些依赖,那么这个依赖就可以自由地更换为不同的实现,而一方 (在此为 BraveKnight) 不需要知道有任何的不同。
换出依赖的一个最普遍的方法是在测试时利用一个 mock 实现 (one of the most common ways a dependency is swapped out is with a mock implementation during test) 。由于紧耦合,你无法适合地测试 DamselRescuingKnight,但是你可以轻松地测试 BraveKnight,通过给它一个 Quest 的 mock 实现 (其实就是构建了一个虚拟的 Quest) ,如下所示:
注意:运行以下测试需要引入 junit 包和 mockito-core 包
package com.gaussic.knights.test;
import com.gaussic.knights.BraveKnight;
import com.gaussic.knights.Quest;
import org.junit.Test;
import org.mockito.Mockito;
// 一个勇敢的骑士,需要经历mock测试的考验
public class BraveKnightTest {
@Test
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = Mockito.mock(Quest.class); // 构建 mock Quest,其实就是虚拟的Quest
BraveKnight knight = new BraveKnight(mockQuest); // 注入mockQuest
knight.embarkOnQuest();
Mockito.verify(mockQuest, Mockito.times(1)).embark();
}
}
在此使用了一个 mock 对象框架 (Mockito) 来创建一个 Quest 接口的虚拟实现。利用虚拟对象,我们可以创建一个新的 BraveKnight 实例,并通过构造器注入这一虚拟 Quest。在调用 embarkOnQuest() 方法后,利用 Mockito 来验证虚拟 Quest 的 embark() 方法仅仅被调用一次。
将一个Quest注入到一个Knight中
现在,BraveKnight 被写成了这样的形式,你可以将任何一个任务交给骑士,那么你应该如何指定给他什么 Quest 呢?假设,例如,你想要 BraveKnight 去完成一项屠龙的任务。可能是 SlayDragonQuest,如下所示。
package com.gaussic.knights;
import java.io.PrintStream;
// 任务目标:屠杀巨龙
public class SlayDragonQuest implements Quest {
private PrintStream stream; // 允许自定义打印流
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
public SlayDragonQuest() {}
// 任务说明
public void embark() {
stream.println("Embark on quest to slay the dragon!");
}
}
如你所见,SlayDragonQuest 实现了 Quest 接口,让其能很好的适应 BraveKnight。你也可能注意到,SlayDragonQuest 在构造时请求了一个普通的 PrintStream,而不像其他的简单 Java 示例一样依靠 System.out.println()。这里有一个大问题,怎样才能把 SlayDragonQuest 交给 BraveKnight 呢?还有怎样才能把 PrintStream 交给 SlayDragonQuest 呢?
在应用组件之间建立联系的行为,通常被称之为装配 (wiring)。在 Spring 中,有很多将组件装配在一起的方法,但是通常的方法总是通过 XML 的。下面展示了一个简单的 Spring 配置文件, knight.xml,它将 BraveKnight, SlayDragonQuest 和 PrintStream 装配在了一起。
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- BraveKnight bean,id为knight -->
<bean id="knight" class="com.gaussic.knights.BraveKnight">
<!-- 构造参数,引用 id为quest的bean -->
<constructor-arg ref="quest"/>
</bean>
<!-- SlayDragonQuest bean,id为quest -->
<bean id="quest" class="com.gaussic.knights.SlayDragonQuest">
<!-- 构造参数,值为 #{T(System).out} -->
<constructor-arg value="#{T(System).out}"/>
</bean>
</beans>
在此,BraveKnight 和 SlayDragonQuest 在 Spring 中都被定义为 bean。在 BraveKnight bean 中,它在构造是传入了一个 SlayDragonQuest 的引用,作为构造参数。同时,SlayDragonQuest bean 中使用了 Spring 表达式语言来传递 System.out (这是一个PrintStream) 给 SlayDragonQuest 的构造器。
如果说 XML 配置不符合你的口味,Spring 还提供了基于 Java 的配置方法。如下所示。
package com.gaussic.knights;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// @Configuration注解,声明这是一个配置类
@Configuration
public class KnightConfig {
// 与XML中的bean目的相同,声明一个SlayDragonQuest bean,传入System.out
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
// 与XML中的bean目的相同,声明一个BraveKnight bean,传入quest
@Bean
public Knight knight() {
return new BraveKnight(quest());
}
}
无论是使用基于 XML 还是基于 Java 的配置,DI 的益处都是相同的。虽然 BraveKnight 依赖于 Quest,但它并不知道将被给予什么样的 Quest,或者这个 Quest 是从哪里来的。同样的,SlayDragonQuest 依赖于 PrintStream,但是它并不需要了解那个 PrintStream 是什么。只有 Spring,通过它的配置,知道所有的组件是如何组织到一起的。这样,我们就可以在不修改当前类的情况下,去修改它的依赖。
这个例子展示了 Spring 中装配 bean 的一个简单方法。先不要在意太多的细节,我们将会在第二章深入探讨 Spring 配置。我们也将探讨 Spring 中装配 bean 的一些其他办法,包括一种让 Spring 自动发现 bean 和创建它们之间关系的方法。
现在你已经生命了 BraveKnight 和 Quest 之间的关系,你需要载入 XML 配置文件并且打开应用。
看它工作
在 Spring 应用中,一个 _应用上下文 (application context) _ 载入 bean 的定义并将它们装配在一起。Spring 应用上下文完全负责创建并装配所有构成应用的对象。Spring 提供了多种应用上下文的实现,每一个主要的不同在于它们如何载入配置。
当 bean 被声明在 xml 文件中时,一个合适的方法是 ClassPathXmlApplicationContext。这个 Spring 上下文实现从一个或多个 (在应用的 classpath 目录的) XML 文件载入 Spring 上下文。下面的 main() 方法使用了 ClassPathXmlApplicationContext 来载入 knight.xml,并且获得对 Knight 对象的一个引用。
package com.gaussic.knights;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
// 从应用的classpath目录载入Spring配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/knight.xml");
// 获取bean,并执行
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
}
}
此处,main() 方法基于 knight.xml文件创建了 Spring 上下文。然后它利用这个上下文作为一个工厂来检索 ID 为 knight 的 bean。利用对 Knight 对象的一个引用,它调用了 embarkOnQuest() 方法来使 knight 执行给予他的任务。注意这个类并不知道骑士被赋予了什么样的任务。这样,它也不知道这个任务是交给 BraveKnight 做的。只有 knight.xml 知道具体的实现是什么。
运行 KnightMain 的 main() 方法,将得到下列结果:
利用这个例子,你应该对依赖注入有了一定的了解。在这本书中你将看到更多的 DI。现在让我们来看看 Spring 的另一个 Java 简化策略:基于方面 (aspects) 的声明式编程。
1.1.3 方面 (Aspects) 的运用
虽然 DI可以松散的绑定软件的各个组件,面向方面编程 (AOP) 还可以让你更够捕捉那些在整个应用中可重用的组件。
AOP 经常被定义为一项提升软件系统分离性 (separation of concerns) 的技术。系统往往由多个组件构成,每一个组件负责一个特定的功能。但是通常这些组件还需要承担其核心功能以外的附加功能。一些系统服务,例如日志、事务管理和安全,往往会进入一些其他的组件中,而这些组件往往负责其他的核心功能。这些系统服务通常被称为交叉相关 (cross-cutting concerns) ,因为它们在系统中往往交叉在多个组件内。
由于将这些相关性传播到了多个组件中,你向你的代码引入了两种层次的复杂度:
-
实现系统级相关性的代码在多个组件中存在重复。这表明如果你需要改变这些相关性的工作方式,你需要访问多个组件。即使你将这个相关性抽象为一个独立的模块,这样其他的组件可以通过模块调用来使用它,这个方法调用依然在多个组件里面重复。
-
你的组件被与核心功能无关的代码污染了。一个向地址簿添加一个地址的方法,应该仅仅关心如何去添加这个地址,而不是还得去关心它的安全性和事务性。
下图展示了这一复杂度。左边的业务对象太过亲密地与右边的系统服务相联系。每个对象不仅仅知道自己被日志记录、安全检查,以及涉及到事务中,而且还需要负责自己去执行这些服务。
AOP 可以模块化这些服务,然后声明式地将它们运用到需要它们的组件中。这样可以使得其他组件更加紧密地专注于负责它们自己的功能,完全无视任何系统服务的存在。简单的说,方面 (aspects) 使得 POJOs 保持普通 (plain) 。
把方面想象成一个覆盖多个组件和应用的毯子可以帮助理解,如下图所示。在其核心部分,一个包含多个模块的应用实现了业务功能。利用 AOP,你可以利用一层层的其他功能将你的核心应用覆盖。这些层可以声明式地运用到你的应用中,以一种更为灵活的方式,而不需要让你的核心应用知道他们的存在。这是一个很强大的概念,因为它保证安全、事务管理和日志不去污染你的应用的核心业务逻辑。
为了说明方面在 Spring 中是如何运用的,让我们再来看看骑士的例子,向其中添加一个基本的 Spring 方面。
AOP 实战
勇敢的骑士在屠杀巨龙和拯救少女归来之后,我们这些百姓要如何才能知道他的丰功伟绩呢?吟游诗人 (minstrel) 会将勇士的故事编成歌谣在民间传颂 (有网络红人就必然要有网络推手啊) 。我们假设你需要通过吟游诗人的服务来记录骑士出征和归来的整个过程。下面展示了这个 Minstrel 类:
package com.gaussic.knights;
import java.io.PrintStream;
// 吟游诗人
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!");
}
}
如你所见,Minstrel 是一个有两个方法的简单类。singBeforeQuest() 旨在骑士出发前调用, singAfrterQuest() 旨在骑士完成任务归来调用。在这两种情况下,Minstrel 都通过注入其构造器的 PrintStrem 来颂唱。
把这个 Minstrel 运用到你的代码中其实非常简单----你可以把它直接注入到 BraveKnight 中,对吗?让我们对 BraveKnight 做一点适当的调整,以使用 Minstrel。下面的代码展示了将 BraveKnight 和 Minstrel 运用到一起的第一次尝试:
package com.gaussic.knights;
public class BraveKnight2 {
private Quest quest;
private Minstrel minstrel;
public BraveKnight2(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
// 一个骑士应该管理自己的吟游诗人吗???
public void embarkOnQuest() throws Exception {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
上面的代码可以完成任务。现在你只需要回到 Spring 配置文件中去定义 Minstrel bean,并将其注入到 BraveKnight bean 的构造器中。但是等等。。。。
好像有什么不对劲。。。一个骑士难道应该去管理他自己的吟游诗人吗?一个伟大的骑士是不会自己去传扬自身的功绩的,有节操的吟游诗人应该主动的去传颂伟大骑士的功绩,而不接受他人的收买。那么,为什么这里的骑士他时时地去提醒吟游诗人呢?
此外,由于这个骑士需要知道这个吟游诗人,你被强制将 Minstrel 注入到 BraveKnight 中。这不仅仅复杂化了 BraveKnight 的代码,还让我想到,如果你需要一个没有吟游诗人的骑士要怎么办?如果 Minstrel 是 null 那怎么办 (骑士所处的地区,压根没有吟游诗人的存在) ?那你是不是还要引入一些非空检查逻辑到你的代码里面呢?
你的简单的 BraveKnight 类开始变得越来越复杂 (而骑士只想做一个纯粹的骑士)。但是,使用 AOP,你可以声明,吟游诗人应该主动地去颂唱骑士的功绩,而骑士只需要做好本职工作。
为了把 Minstrel 转变为一个方面,你要做的仅仅是在 Spring 配置文件中声明它。这是一个更新版的 knight.xml,加入了 Minstrel 方面的声明:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- BraveKnight bean,id为knight -->
<bean id="knight" class="com.gaussic.knights.BraveKnight">
<!-- 构造参数,引用 id为quest的bean -->
<constructor-arg ref="quest"/>
</bean>
<!-- SlayDragonQuest bean,id为quest -->
<bean id="quest" class="com.gaussic.knights.SlayDragonQuest">
<!-- 构造参数,值为 #{T(System).out} -->
<constructor-arg value="#{T(System).out}"/>
</bean>
<!-- Minstrel bean, id为minstrel -->
<bean id="minstrel" class="com.gaussic.knights.Minstrel">
<!-- 构造参数,值为 #{T(System).out} -->
<constructor-arg value="#{T(System).out}" />
</bean>
<!-- 方面配置 -->
<aop:config>
<aop:aspect ref="minstrel">
<!-- 在执行任何类的 embarkOnQuest()方法时调用 -->
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
<!-- 在embark之前,调用 singBeforeQuest -->
<aop:before pointcut-ref="embark" method="singBeforeQuest"/>
<!-- 在embark之后,调用 singAfterQuest -->
<aop:after pointcut-ref="embark" method="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>
这里你使用了 Spring 的 aop configuration 命名空间来声明 Minstrel bean 是一个方面。首先你声明 Minstrel 是一个 bean。然后在 aop:aspect 元素引入那个 bean。更进一步的定义这个 aspect,你声明 (使用 aop:before) 了 singBeforeQuest() 方法在 embarkOnQuest() 方法前调用,称之为 before advice。以及 singAfterQuest() 方法在 embarkOnQuest() 后调用,称之为 after advice。
在以上两种情况中,pointcut-ref 属性都引用了一个名为 embark 的 pointcut (切入点) 。这个 pointcut 定义在了之前的
如果你不了解 AspectJ,或者 AspectJ pointcut 表达式是如何编写的,不用担心。我们将在第 4 章对 Spring AOP 做更多的讲解。现在,知道你要请求 Spring 在 BraveKnight 在出征前后,调用 Minstrel 的 singBeforeQuest() 和 singAfterQuest() 方法足矣。
现在,不改动 KnightMain 的任何地方,运行 main() 方法,结果如下:
这就是它的全部了!只需要一点点的 XML,你就将 Minstrel 转换为了一个 Spring 方面。如果它现在还没完全说明白,不用担心,你将在第 4 章中看到更多的 Spring AOP 示例,它们将给你更明确的概念。现在,这个例子有两个重点需要强调。
第一,Minstrel 仍然是一个 POJO,没有任何代码表明它将被作为一个方面。Minstrel 只在你在 Spring context 中声明时,才是一个方面。
第二,也是最重要的,Minstrel 可以运用到 BraveKnight 中,而 BraveKnight 不需要明确地调用它。实际上,BraveKnight 甚至一直不知道还有 Minstrel 的存在。
我还应该指出,虽然你使用了一些 Spring 的魔法来将 Minstrel 转化为一个方面,你还是需要将它先声明为一个 bean。重点是,你可以用 Spring aspects 做任何的事情,像其他的 Spring beans 一样,例如将依赖注入给它们。
使用 aspects 来颂唱骑士的功绩是很好玩的。但是 Spring 的 AOP 可以用在更多实用的地方。在后面你将看到,Spring AOP 可以用来提供服务,例如声明式事务、安全,将在第 9 章和 14 章介绍。
在这之前,我们来看看 Spring 简化 Java 开发的另一个方法。
1.1.4 利用模板消除重复代码
你是否写过一些看似之前写过的代码?这不是“似曾相似“,朋友。那是重复代码 (boilerplate code) ---为了执行常见或简单的任务需要一遍一遍的写相似的代码。
不幸的是,Java API 的许多地方都含有重复的代码。使用 JDBC 做数据库查询就是一个明显的例子。如果你在使用 JDBC,你可能需要写一些如下所示的代码:
public Employee getEmployeeById(long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection(); // 建立连接
// 选择 employee
stmt = conn.prepareStatement("select id, firstname, lastname, salary from employee where id=?");
stmt.setLong(1, id);
rs = stmt.executeQuery();
Employee employee = null;
if (rs.next()) {
employee = new Employee(); // 创建对象
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
}
return employee;
} catch (SQLException e) { // 错误处理
} finally {
if (rs != null) { // 收拾残局
try {
rs.close();
} catch (SQLException e) {
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}
return null;
}
如你所见,这个 JDBC 代码的目的是查询一个员工的名字和工资情况。由于这项特定的查询任务被埋藏在一堆的 JDBC 仪式中,你首先必须创建一个连接,然后创建一个陈述 (statement) ,最后在查询结果。而且,为了安抚 JDBC 的脾气,你必须 catch SQLException,其实就算它抛出了错误,你也没什么能够做的。
最后,在所有的事情都做完了,你还得收拾残局,关闭连接 (connection) 、陈述 (statement) 、结果集 (result set) 。这还是有可能触发 JDBC 的脾气,因而你还得 catch SQLException。
上面代码最显著的问题是,如果你要做更多的 JDBC 查询,你将会写大量重复的代码。但是只有很少一部分是真正跟查询相关的,而 JDBC 重复代码要多得多。
在重复代码业务中,JDBC 并不孤独。许多的工作都包含相似的重复代码。JMS、JNDI 和大量的 REST 服务通常涉及到大量完全重复的代码。
Spring 通过将重复代码封装在模板中来消除它们。Spring 的 JdbcTemplate 可以在不需要所有传统 JDBC 仪式的情况下执行数据库操作。
例如,使用 Spring 的 SimpleJdbcTemplate (JdbcTemplate 的一个特定形式,利用了 Java 5 的优点),getEmployeeById() 方法可以重写,使得它仅关注于检索员工数据的任务,而不需要考虑 JDBC API 的需求。下面的代码展示了更新后的 getEmployeeById() 的样子:
public Employee getEmployeeById(long id) {
return jdbcTemplate.queryForObject(
"select id, firstname, lastname, salary from employee where id=?", // SQL查询
new RowMapper<Employee>() {
// 映射结果到对象
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setFirstName(rs.getString("firstname"));
employee.setLastName(rs.getString("lastname"));
employee.setSalary(rs.getBigDecimal("salary"));
return employee;
}
}, id);
}
如你所见,新版本的 getEmployeeById() 更加简洁且专注于从数据库中查询员工。模板的 queryForObject() 方法被给予一个 SQL 查询,一个 RowMapper (为了将结果集数据映射到领域对象),以及 0 个或多个查询参数。你无法在 getEmployeeById() 中找到任何的 JDBC 重复代码。这一切都交给了模板来处理。
我已经想你展示了 Spring 是如何使用面向 POJO 的开发来降低 Java 开发复杂性的,DI, aspects 和 templates。同时,还展示了如何在 XML 配置文件中配置 bean 和 aspect。但是如何载入这些文件呢?让我们看看 Spring 容器 (container) ,存放应用的 bean 的地方。
1.2 盛装你的bean
在 Spring 应用中,你的应用对象住在 Spring 容器中。如图 1.4 所示,容器创建对象,然后将它们装配在一起,配置它们,然后管理它们的生命周期,从襁褓到坟墓。
在下一章节,你将了解如何配置 Spring,使得它知道它需要创建、配置和装配什么对象。首先,知道对象什么时候 hang out (闲逛?不好翻译) 是很重要的。了解容器将帮助你了解对象是如何被管理的。
容器是 Spring 框架的核心。Spring 的容器使用 DI 来管理组成一个应用的组件。这包括建立协调组件之间的联系。本身,这些组件更简洁切更易理解,它们支持重用,且易于单元测试。
没有单独的 Spring 容器。Spring包括多种的容器实现方法,可以分为两种独立的类型。Bean工厂 (bean factory,由 org.springframework.beans.factory.BeanFactory 接口定义) 是最简单的容器,提供 DI 的基本支持。应用上下文 (application context,由 org.springframework.context.ApplicationContext 接口定义) 建立与 bean 工厂的概念之上,提供应用框架服务,例如从配置文件中读取文本消息的能力和向感兴趣的事件监听器发送应用时间的能力。
虽然使用 bean factory 和 application 都可以,bean factory 对于大部分应用来说还是太低级了。因此,相比 bean factory,application context 更受青睐。我们将专注于使用 application context,而不花更多的时间在 bean factory 上。
1.2.1 使用应用上下文
Spring 包含多种口味的应用上下文。以下是一部分你最有可能遇上的:
-
AnnotationConfigApplicationContext --- 从一个 Java 配置类中载入 Spring 应用上下文
-
AnnotationConfigWebApplicationContext --- 从一个 Java 配置类中载入 Spring web 应用上下文
-
ClassPathXmlApplicationContext --- 从一个或多个在 classpath 下的 XML 文件中载入 Spring 上下文,将上下文定义文件作为一个 classpath 资源文件
-
FileSystemXmlApplicationContext --- 从文件系统中的 XML 文件中载入上下文定义
-
XmlWebApplicationContext --- 从包含在一个 web 应用中的一个或多的 XML 文件中载入上下文定义
我们将在第八章讲解基于 web 的 Spring 应用时更加详细的说明 AnnotationConfigWebApplicationContext 和 XmlWebApplicationContext。现在,让我们用 FileSystemXmlApplicationContext 从文件系统载入 Spring 上下文,或用 ClassPathXmlApplicationContext 从 classpath 载入 Spring 上下文。
从文件系统或 classpath 载入应用上下文与从 bean factory 中载入 bean 相类似。例如,下面是如何载入 FileSystemXmlApplicationContext 的:
ApplicationContext context = new FileSystemXmlApplicationContext("c:/knight.xml");
类似地,可以使用 ClassPathXmlApplicationContext 从应用的 classpath 载入应用上下文:
ApplicationContext context = new ClassPathXmlApplicationContext("knight.xml");
FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 的不同之处在于,前者在文件系统的特定的位置寻找 knight.xml,而后者在应用的 classpath 中寻找 knight.xml。
另外,如果你更偏向从 Java 配置中载入你的应用上下文的话,可以使用 AnnotationConfigApplicationContext:
ApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class);
取代指定一个载入 Spring 应用上下文的 XML 文件外,AnnotationConfigApplicationContext 被给以一个配置类来载入 bean。
现在手头上有了一个应用上下文,你可以利用上下文的 getBean() 方法,从 Spring 容器中检索 bean 了。
现在你知道了如何创建 Spring 容器的基本方法,让我们更进一步的看看在 Bean 容器中一个 bean 的生命周期。
1.2.2 一个bean的一生
在传统的 Java 应用中,bean 的生命周期非常简单。Java 的关键词被用来实例化 bean,然后这个 bean 就可以使用了。一旦这个 bean 不再使用,将会被垃圾收集器处理。
相比之下,在 Spring 容器中的 bean 的生命周期更加复杂。理解 Spring bean 的生命周期是非常重要的,因为你可能需要利用 Spring 提供的一些有点来定制一个 bean 什么时候被创建。图 1.5展示了一个典型的 bean 在载入到应用上下文时的生命周期。
如你所见,一个 bean 工厂在 bean 可以被使用前,执行了一系列的设置操作。让我们来探讨一些细节:
- Spring 实例化 bean。
- Spring 将值和 bean 引用注入到 bean 的属性中。
- 如果一个 bean 实现了 BeanNameAware,Spring 将这个 bean 的 id 传递给 setBeanName() 方法。
- 如果一个 bean 实现了 BeanFactoryAware,Spring 调用 setBeanFactory() 方法,将一个引用传入一个附入 (enclosing) 的应用上下文。
- 如果一个 bean 实现了 BeanPostProcessor 接口,Spring 调用其 postProcessBeforeInitialization() 方法。
- 如果一个 bean 实现了 InitializingBean 接口,Spring 调用其 afterPropertiesSet() 方法。类似的,如果这个 bean 使用了 init-method 进行声明,那么特定的初始化方法将被调用。
- 如果一个 bean 实现了 BeanPostProcessor,Spring 调用其 postProcessAfterInitialization() 方法。
- 在这点,bean 就可以被应用使用了,并且 一直保持在应用上下文中,直到应用上下文被销毁。
- 如果一个 bean 实现了 DisposableBean 接口,Spring 调用其 destroy() 方法。同样,如果这个 bean 被声明了 destroy-method,将会调用特定的方法。
现在你已经知道了如何创建并载入一个 Spring 容器。但是一个空的容器本身并没有什么好处,它不含任何东西,除非你将什么放进去。为了实现 Spring DI 的好处,你必须将 Spring 容器中的应用对象装配起来。我们将在第二章节更加详细的讲解 bean 的装配。
首先,我们来调查一下现代Spring的蓝图 (landscape) ,来看看Spring框架是由什么构成的,以及后面版本的Spring提供了什么新的特性。
1.3 Spring 版图概览
如你所见,Spring 框架专注于通过 DI、AOP 和减少重复来简化企业 Java 开发。即使那是 Spring 的全部,那也是值得一用的。但是对于 Spring 来说,还有很多令你难以置信的东西。
在 Spring 框架中,你将发现许多 Spring 简化 Java 开发的方法。但是,在 Spring 框架之上,是一个更大的建立在核心框架之上的项目生态系统,将 Spring 扩展到更多的领域,例如 web services, REST, mobile 和 NoSQL。
让我们先来分解一下核心 Spring 框架,来看看它给我们带来了什么。然后我们再扩展我们的视野来看看 Spring 包的更多成员。
Spring模块
当你下载了 Spring distriution 之后,查看它的 libs 目录,你将发现多个 JAR 文件。在 Spring 4.0 中,有 20 个独立的模块,每个模块有 3 个 JAR 文件 (binary class, source, JavaDoc)。完整的 library JAR 文件如图 1.6 所示。
这些模块可以被排成 6 个功能的类,如图 1.7 所示。
总的来说,这些模块给了你任何你需要的东西,来开发企业级应用。但是你不需要让你的应用完全基于 Spring 框架。你可以自由地选择适合你的应用的模块,并且在 Spring 无法满足你的需求时,选择其他的模块。Spring 甚至提供了与许多其他框架和库的集成点,因而你不用去自己写它们。
让我们一个一个的来看看 Spring 的每一个模块,来了解一下每一个模块是如何拼凑整个 Spring 的版图的。
核心 Spring 容器
Spring 框架的中心是一个容器,它负责管理 Spring 应用中的 bean 是如何创建、配置与管理的。这个模块的内部是 Spring bean 工厂,是 Spring 提供 DI 的一部分。建立在 bean 工厂之上,你将发现 Spring 应用上下文的多个实现,每一个都提供了配置 Spring 的不同方式。
除了 bean 工厂和应用上下文之外,这个模块还提供了许多企业级服务,例如邮件、JNDI 访问、EJB 集成,和调度。
Spring 的所有模块都都是建立在核心容器之上的。你将在配置你的应用是隐式的使用这些类。我们将通篇讨论核心模块,从第二章开始,我们将更深入的挖掘 Spring DI。
Spring AOP 模块
Spring 在 AOP 模块中提供了面向方面编程的丰富支持。这个模块的作用服务于------为你自己的 Spring 应用开发你自己的 aspects。与 DI 类似,AOP 支持应用对象的松散耦合。但是利用 AOP,应用级的相关性 (例如事务和安全) 解除了它们与其他对象的耦合。
我们将在第 4 章更加深入的讲解 Spring AOP 支持。
数据访问和集成
使用 JDBC 时总是会造成大量的重复代码 (获取连接,创建 statement,处理结果集合,然后关闭连接) 。Spring 的 JDBC 和数据访问对象 (DAO) 模块将这些重复代码抽象化,因而你可以保持你的数据库代码干净和简单,并且阻止了由数据库资源访问失败导致的错误。这个模块也在多种数据库服务器返回的错误消息之上构建了一层非常有意义的 exception。你并不需要去破解神秘而专用的 SQL 错误消息。
对于那些更喜欢使用对象关系映射 (object-relational mapping, ORM) 工具的人来说,Spring 提供了 ORM 模块。Spring 的 ORM 支持建立在 DAO 支持之上,以多种 ORM 方法提供了建立 DAO 的方便方法。Spring 并没有试图去实现它自己的ORM方法,但是它提供了连接多个流行 ORM 框架的钩子 (hook) ,包括 Hibernate,Java Persistemce APUI,Java Data Objects,以及 iBATIS SQL Maps。Spring 的事务管理像支持 JDBC 一样支持这些 ORM 框架。
在第 10 章节,我们将讲解 Spring 数据访问,你将看到 Spring 的基于模板的 JDBC 抽象是如何能够大大地简化 JDBC 代码的。
这个模块还包括了一个对于 Java Message Service (JMS) 的 Spring 抽象,以通过消息机制进行与其他应用的异步集成。此外,对于 Spring 3.0,这个模块包含了 object-to-XML 映射特性,它们是 Spring Web Services 项目的根本部分。
此外,这个模块使用了 Spring 的 AOP 模块来提供 Spring 应用中对象的事务管理服务。
Web 和远程
模型-视图-控制器 (MVC) 样式已经非常广泛地被接受,以用来构建 web 应用,使得用户接口与应用逻辑向分离。Java 在 MVC 框架中并无缺陷,Apache Struts, JSF, WebWork 和 Tapestry 已经成为了非常流行的 MVC 选择。
即使 Spring 可以集成多种流行 MVC 框架,它的 web 和远程模块来自于一个可靠的 MVC 模块,它在应用 web 层充分运用了 Spring 的松散耦合技术。我们将在第 5-7 章讲解 Spring mvc 框架。
除了面向用户的 web 应用以外,为了建立可以与其他应用交互的应用,这个模块还提供了多个远程选项。Spring 的远程能力包括远程方法调用 (Remote Method Invocation, RMI) , Hessian, Burlap, JAX-WS,以及 Spring 自己的 HTTP invoker。Spring 还提供了使用 REST API 的一级支持。
在地第 15 章,我将讲解 Spring 远程。并且你将在第 16 章学习到如何创建与使用 REST API。
仪器 (INSTRUMENTATION)
Spring 的仪器 (instrumentation,是这么翻吗) 模块包括了向 JVM 添加助理 (agent) 的支持。特别地,它向 Tomcat 提供了一个迂回助理以转换类文件,就像它们是被类加载器加载一样。
如果这听起来难以理解,不要太担心。这个模块提供的仪器用途非常窄,我们将不会在这本书中讲解。
测试
意识到了开发者写的测试的重要性,Spring 提供了一个模块以测试 Spring 应用。
在这个模块中,你将发现许多的 mock 对象实现,为了撰写作用于 JNDI,servlet,和 portlet 的单元测试代码。对于集成级的测试,这个模块提供了载入 Spring 上下文的 bean 和操作 bean的支持。
在本书中,大多数例子将用测试来表示,利用Spring提供的测试方法。
1.3.2 Spring 组合 (Spring portfolio)
Spring 总比你能看到的要多得多。事实上,Spring 的模块比下载下来的要多得多。如果你仅仅停留在核心的 Spring 框架,你将错过 Spring 组合带来的一笔巨大从潜力。整个 Spring 组合包包括了多个建立在核心 Spring 框架上的框架和库。 总体上,整个 Spring 组合包带来了基本覆盖 Java 开发每一面的 Spring 编程模型。
要覆盖 Spring 组合包提供的全部内容,需要大量的篇幅,而且很多都超出了本书的范围。但是我们将了解一下 Spring 组合包的一些元素;这里是基于核心 Spring 框架的一个品尝。
Spring Web Flow 网络流
Spring Web Flow 建立在核心 Spring 框架之上,提供了将 Spring bean 发布为 web 服务的声明式方法,这些服务基于一个可论证的架构上下级的协议最后 (contract-last) 模型。服务的协议是由 Bean 的接口决定的。Spring Web Services 提供了协优先 (contract-first) web 服务模型,其中服务实现是为满足服务协议而编写的。
Spring Security 安全
安全是许多应用的关键方面。使用 Spring AOP 实现,Spring 安全为 Spring 应用提供了声明式的安全机制。你将在第 9 章了解如何向 web 层添加 Spring 安全的。我们将在第 14 章重新回到 Spring 安全来检验如何保证方法调用安全性的。
Spring Integration 集成
许多的企业级应用必须与其他的企业级应用进行交互。Spring 集成提供了多种常用交互模式的实现,以 Spring 声明式的形式。本书将不做讲解。
Spring Batch 批处理
当需要在数据上执行大量的操作时,没有什么能比得上批处理。如果你将去开发一个批处理应用,你可以利用 Spring Batch, 来使用 Spring 的鲁棒、面向 POJO 的开发方法完成它。Spring Batch 超出了本书的范围。
Spring Data 数据
Spring Data 使得操作各种数据库变得非常简单。虽然关系型数据库多年来一直用于企业应用中,现代的应用越来越意识到不是所有的数据都可以用表中的行和列来表示的。一种新产生的数据库,通常被称之为 NoSQL 数据库,提供了新的方法来操作新型数据库,而非传统关系型数据库。
不管你是在使用文档数据库如 MongoDB,图形数据库如 Neo4j,或者甚至传统关系型数据库,Spring Data 为 persistence 提供了简化的编程模型。这包括,为多种数据类型,一个自动仓库 (repository) 机制来为你创建仓库实现。
我们将在第 11 章中讲解如何用 Spring Data 来简化 Java Persistence API (JPA) 开发,然后在第 12 章时扩展性地讨论一些 NoSQL 数据库。
Spring Social 社交
社交网络在互联网上升趋势,越来越多的应用开始集成社交网站的接口,例如 Facebook 和 Twitter。如果你对这方面感兴趣,你可以了解一下 Spring Social,Spring 的一个社交网络扩展。
但是 Spring Social 并不仅仅是微博和朋友。尽管这样命名,Spring Social 相比社交 (social) 这一词更加偏向于连接 (connect) 。它帮助你利用 REST API 连接你的 Spring 应用,其中还包括很多没有任何社交目的的应用。
由于空间的限制,我们将不在本书中讲解 Spring Social。
Spring Mobile 移动
移动应用是软件开发另一个热门点。智能手机以及平板已经逐渐成为备受用户青睐的客户端。Spring Mobile 是 Spring MVC 的一个新的扩展,以支持移动 web 应用的开发。
Spring For Android 安卓
与 Spring Mobile 相关的是 Spring Android 项目。这个项目旨在利用 Spring 框架为一些安卓设备上的本地应用的开发带来一定的便利。初始地,这个项目提供了一个版本的 Spring RestTemplate,它可以运用在 Android 应用中。它还可以操作 Spring Social 来使得本地安卓 app 可以与 REST API 相连接。本书将不讨论 Spring for Andoid。
Spring BOOT 快速启动
Spring 大大简化了许多编程任务,减少甚至消除了大量你可能通常非常需要的重复代码。Spring Boot 是一个非常激动人心的新项目,它的目的是在 Spring 开发时简化 Spring 本身。
Spring Boot 大量地利用了自动配置技术,以消除大量的 (在多种情况下,可以说是所有的) Spring 配置。它还提供了许多的启动器 (starter) 项目来帮助来减少你的 Spring 项目构建文件,在你使用 Maven 或 Gradle 时。我们将在第 21 章讲解 Spring Boot。
1.4 Spring 的新功能
当这本书的第三版出版时,最晚的 Spring 版本是 3.0.5。这大概是 3 年前,并且 Spring 自那起发生了许多的改变。Spring 框架迎来了 3 个重大的发布---3.1,3.2,以及现在的 4.0---每一次发布都带来了许多新的特性与改善,以减轻应用开发。并且多个模块成员经历了巨大的改变。
这一版本的 Spring in Action 已经进行了更新,覆盖了大多数的这些版本的特性。但是现在,我们简单地来看看 Spring 更新了什么。
1.4.1 Spring 3.1 更新了什么?
Spring 3.1 有许多有用的新特性与改进,大多都专注于简化和改进配置方法。此外,Spring 3.1 提供了声明式的缓存支持,以及对于 Spring MVC 的许多改进。这里是 Spring 3.1 的一些亮点:
-
为了解决从多种环境 (例如开发,测试,与发布) 选择独立的配置的问题,Spring 3.1 引入了环境资料 (profile) 。资料使得一些事情成为可能,例如,依赖于应用的环境选择不同的数据源 bean。
-
建立在 Spring 3.0 的基于 Java 的配置的基础上,Spring 3.1 添加了多个 enable 注解以允许 Spring 用一个注解来进行特定特性的切换。
-
声明式缓存支持被引入了 Spring,使得我们可以利用简单的注解来声明缓存边界与规则,这与我们如何声明事务边界相类似。
-
一个新的 c 命名空间带来了构造器注入,与 Spring 2.0 的 p 命名空间类似 (用于属性注入) ,简化了配置方法。
-
Spring 开始支持 Servlet 3.0,包括利用基于 Java 的配置方法声明 servlet 和 filter,来取代原来的 web.xml。
-
Spring JPA 的改进使得我们可以完全的配置 JPA,而不需要 persistence.xml。
-
Spring 3.1 还包括了多个对 Spring MVC 的增强: - path variables 到 model attributes 的自动绑定
- @RequestMappingproduces 和 consumes 属性,为了匹配请求的 Accept 和 Content-Type 头
- @RequestPart 注解,允许绑定部分的 multipart 请求到 handler 方法参数
- flush 属性支持 (重定向的属性) ,和一个 RedirectAttributes 类型来在请求见传递 flash 属性
与 Spring 3.1 更新了什么同等重要的是,Spring 3.1 中哪些已经不再使用了。特别地,Spring 的 JpaTemplate 和 JpaDapSupport 类以不被建议使用,取而代之的是 EntityManager。即使它们已经不建议使用,它们依然在 Spring 3.2 中。但是你不应该使用它们,因为它们没有升级到支持 JPA 2.0,且在 Spring 4 中已经被移除。
现在我们来看看 Spring 3.2 更新了什么。
1.4.2 Spring 3.2 更新了什么?
Spring 3.1 专注于利用一些其他的改进来改善配置方法,包括 Spring MVC 的改进。Spring 3.2 主要是一个专注于 Spring MVC 的版本。Spring 3.2 有如下的改进:
- Spring 3.2 controllers 可以充分利用 Servlet 3 的异步请求,将请求的处理传给多个分离的线程,释放 servlet 线程以处理更多的请求。
- 虽然 Spring MVC controllers 自 Spring 2.5 起就可以简单地作为 POJO 进行测试,Spring 3.2 包含了一个 Spring MVC 测试框架,以针对 controllers 编写更加丰富的测试,作为 controller 来预警它们的行为,而不需要 servler container。
- 除了改进 controller 测试,Spring 3.2 包含了对测试基于 RestTemplate 的客户端的支持,而不需要发送请求到真实的 REST 终端。
- 一个 @ControllerAdvice 注解允许普通 的@ExceptionHandler,@InitBinder,@ModelAttributes 方法可以出现在单一的类中,并且运用到所有的 controllers。
- 在 Spring 3.2 之前,全内容协议 (full content negotiation) 仅仅可以通过 ContentNegotiationViewResolver 来支持。但是在 Spring 3.2 中,全内容协议在整个 Spring MVC 中都是可用的,甚至在一个依赖于消息转换的 controller 方法。
- Spring MVC 3.2 包含了一个新的 @MatrixVariable 注解,已绑定一个请求的矩阵变量来处理方法参数。
- 抽象基类 AbstractDispatcherServletInitializer 可以用来方便的配置 DispatcherServlet,而不使用 web.xml。同样,它的子类 AbstractAnnotationConfigDispatcherServletInitializer 可以在配置基于 Java 的 Spring 配置方法时使用。
- 添加了 ResponseEntityExceptionHandler 类作为 DefaultHandlerExceptionResolver 的备用而使用。ResponseEntityExceptionHandler 方法返回
ResponseEntity<Object>
而不是 ModelAndView。 - RestTemplate和@RequestBody 参数支持通用类型。
- RestTemplate和@RequestMapping 方法支持 HTTP PATCH 方法。
- 映射拦截器支持 URL 模式,以排除在拦截处理之外。
虽然 Spring MVC 是 Spring 3.2 的主要部分,它还加入了一些非 MVC 的改进。这里是一些最有趣的新特性:
- @Autowired,@Value 和 @Bean 注解可以作为 meta-annotations 使用,以创建定制化注入和bean声明注解。
- @DateTimeFormat注解不再依赖于 JodaTime。如果出现了 JodaTime,它将被使用。否则,使用 SimpleDateFormat。
- Spring 的声明式缓存支持开始初始化支持 JCache 0.5。
- 你可以定义全局 formats 以转换和处理日期和时间。
- 集成测试可以配置和载入 WebApplicationContext。
- 集成测试可以测试 request 和 session 范围的 beans。
你将看到大量的 Sping 3.2 特性,在本书的多个章节中,特别是在 web 和 REST 章节。
1.4.3 Spring 4.0 更新了什么?
Spring 4.0 是目前最新的 Spring 版本。在其中有许多的新特性,包含如下:
- Spring 现在包含了对 WebSocket 编程的支持,包括 JSR-356: Java API for WebSocket。
- 意识到 WebSocket 提供了一个低级的 API,特别需要高级的抽象。Spring 4.0 包含了一个高级的面向消息的编程模型,在 WebSocket 之上,基于 SockJS,并且包含了 STOMP 子协议支持。
- 一个来自 Spring 集成项目的带有多种类型的消息模块。这个消息模块支持 Spring 的 SockJS/STOMP 支持。它还包含发布消息的基于模板的支持。
- Spring 4.0 是最早支持 Java 8 特性 (包含 lambdas) 的框架之一。这使得利用回调接口 (例如使用 JdbcTemplate 的 RowMapper) 更加的干净易读。
- 为基于 Groovy 开发的应用带来了更加平滑的编程体验,最重要的是让 Spring 应用可以完全用 Groovy 来轻松开发。利用来自 Grails 的 BeanBuilder,使 Spring 应用能够使用 Groovy 来配置。
- 为有条件的 bean 的创建提供了更加通用的支持,其中,bean 只能在条件允许的情况下被创建。
- Spring 4.0 还包含了 Spring 的 RestTemplate 的异步实现,直接返回,但是在操作完成之后回调。
- 添加了许多 JEE 规范的支持,包括 JMS 2.0,JTA 1.2,JPA 2.1 和 Bean Validation 1.1。
如你所见,许多新的成员都在最新的 Spring 框架中出现。通过本书,我们将了解大多数的新特性,以及长期支持的特性。
1.5 总结
现在你应该知道 Spring 到底带来了什么吧。Spring 旨在使得 Java 开发更加简单化,且提倡松散耦合的代码。这其中最重要的是依赖注入和面向方面编程。
在本章,你稍微品尝了一下 Spring 的 DI。DI 是一种关联应用对象的方法,使得对象不需要知道它们的依赖来自哪里,或者它们是如何实现的。相比它们自己索取依赖,需要依赖的对象被它人给予了其所依赖的对象。因为依赖对象经常知道他们的注入对象 (通过接口) ,使得耦合度很低。
除了 DI,你稍微了解了一点 Spring 的 AOP 支持。AOP 使你能够专注于一个地方----一个方面----逻辑可以从应用中分散开来。当 Spring 将你的 bean 装配在一起时,这些方面可以在一个运行时中运行,高效的给予 bean 以新的行为。
DI 和 AOP 是 Spring 的一切的中心。因而你必须理解如何使用这些重要功能,以能够使用框架的其他部分。在本章,我们仅仅看到了 Spring 的 DI 和 AOP 的表面。在接下来的几章,我们将深入探讨。
没有其他闲事,让我们移动到第二章,来学习如何利用 Spring DI 将对象装配在一起。