Spring可以做很多事情,但这些功能都依赖于两个核心特性:依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Proramming,AOP),本章将从Spring策略出发,讲一下Spring框架和DI,AOP 的基本样例。
Spring最恨本的使命是简化Java开发,为了降低Java开发的复杂性,Spring采取了以下4种关键策略:
1.基于POJO的轻量级和最小入侵性编程;
2.通过依赖注入和面向接口实现松耦合;
3.基于切面和惯例进行声明式编程;
4.通过切面和模板减少样板代码;
几乎所有Spring做的任何事情都可以追述到上面的策略,下面我们将逐条讲解
很多框架通过强迫应用继承他们的类或实现它们的接口从而导致应用于框架绑死,比如EJB2时代的无状态会话bean,早期版本的Struts、WebWork、Tapestry等其他jaba规范和框架。Spring不会强迫你实现Spring所规范的接口或继承。比如下面的HelloWordBean类:
package com.guo.spring
public class HelloWordBean {
public String sayHello() {
return "Hello World"
}
}
可以看到,这是一个简单普通的Java类——POJO。没有任何地方表明它是一个Spring组件。Spring的非侵入式编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。
任何一个有实际意义的应用都会由两个或更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。按照传统的做法,每个对象负责管理与自己相互协作的对象的引用(即它所依赖的对象),这就会导致高度耦合和难以测试的代码。
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight ( RescueDamselQuest quest) {
//与RescueDamselQuest紧耦合
this.quest = new RescueDamselQuest();
}
@Override
public void embarkOnQuest() {
quest.embark();
}
}
DamselRescueingKnight在它的构造函数中自行创建了RescueDamselQuest,这使得两者紧紧的耦合在一起。耦合具有两面性:
为了完成更有实际意义的功能,不同的类必须以适当的方式进行交互。总而言之,耦合是必须的,但需要谨慎对待。
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候设定,对象无需自行创建或管理他们的依赖关系,依赖关系将被自动注入到需要它们的对象中。
public class BraveKnight implements Knight {
public Quest quest;
public BraveKnight(Quest quest) { //Quest被注入进来,构造注入
this.quest = quest;
}
@Override
public void embarkOnQuest() {
quest.embark();
}
}
不同于之前的DamselRescuingKnight,BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造参数传入。这是依赖注入的方式之一,即构造注入(constructor injection)。这里的要点是BraveKnight没有有特定的Quest实现发生耦合。对他来说,被要求挑战的探险任务只要实现了Quest接口,那么具体的是那种类型就无关紧要了。这就是DI带来最大的收益——松耦合。如果一个对象只通过接口(而不是具体的实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不情况的情况下,用不同的具体实例进行替换。
那么在Spring中如何通过DI来实现将Quest注入到Knight中呢?这里通过一个简单的例子来介绍下,创建应用组件之间协作的行为成为装配。Spring有多种装配Bean的方式。
(1)采用XML来装配
类SlayDragonQuest时要注入到BraveKnight中的Quest实现:
public class SlayDragonQuest implements Quest {
private PrintStream stream;
public SlayDragonQuest(PrintStream stream) {
this.stream = stream;
}
@Override
public void embark() {
stream.println("Embarking on quest to slay the dragon!");
}
knights.xml,该文件将BraveKnight,SlayDragonQuest和PrintStream装配到一起。
在这里,BraveKnight和SlayDragonQuest被声明为Spring中的bean。就BraveKnight bean来讲,他在构造时传入对SlayDragonQuest bean的引用,将其作为构造器参数。同时,SlayDragonQuest bean 的声明使用了Spring表达式语言(Spring Expression Language),将System.out(一个PrintStream)传入到了SlayDragonQuest的构造器中,
在SpEL中, 使用T()运算符会调用类作用域的方法和常量. 例如, 在SpEL中使用Java的Math类, 我们可以像下面的示例这样使用T()运算符:
T(java.lang.Math)
T()运算符的结果会返回一个java.lang.Math类对象.
(2)基于Java装配
import guo.knights.BraveKnight;
import guo.knights.Knight;
import guo.knights.Quest;
import guo.knights.SlayDragonQuest;
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);
}
}
不管使用的是基于XML的配置还是基于Java的配置,DI所带来的收益都是相同的。尽管BraveKnight依赖于Quest,但是它并不知道传递给它的是什么类型的Quest,与之类似,SlayDragonQuest依赖于PrintStream,但是编译时,并不知道PrintStream长啥样子。只有Spring通过他的配置,能够了解这些组成部分是如何装配起来的。这样就可以在不改变 所依赖的类的情况下,修改依赖关系。
接下来,我们只需要装载XML配置文件,并把应用启动起来。
Spring通过应用上下文(Application context) 装载bean的定义,并把它们组装起来。Spring应用上下文全权负责对象的创建个组装,Spring自带了多种应用上下文的实现,他们之间的主要区别仅仅在于如何加载配置。
因为knights.xml中的bean是使用XML文件进行配置的,所以选择ClassPathXmlApplicationContext作为应用上下文相对是比较合适的。该类加载位于应用程序类路径下的一个或多个Xml配置文件。对于基于Java的配置,可以使用AnnotaionConfigApplicationContext作为应用上下文。
public class KnightMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring/knights.xml"); //加载Sprinig应用上下文
Knight knight = context.getBean(Knight.class); //获取knight bean
knight.embarkOnQuest(); //使用knight调用方法
context.close(); //关闭应用上下文
}
}
这里的main()方法基于knight.xml文件创建了spring应用上下文。随后他调用该应用上下文获取一个ID为knighht的bean。得到Knighht对象的引用后,只需要简单调用embarkOnQuest方法就可以执行所赋予的探险任务了
DI能够让相互协作的软件组件保持松耦合,而面向切面编程(aspect-oriented programming AOP) 允许你把遍布应用各处的功能分离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术,系统由许多不同的组件组成,每个组件各负责一特定的功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理、和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中。这些系统通常被称为横切关注点,因此他们会跨越系统多个组件。如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性:
我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是由哪些实现各自业务功能模块组成的,借助AOP,可以使用各种功能层去包裹核心业务层,,这些层以声明的方式灵活的应用到系统中,你的核心应用甚至根本不知道他们的存在。这是一个非常强大的理念,可以将安全,事务,日志关注点与核心业务相分离。
假设我们需要使用咏游诗人这个服务类来记载骑士的所有事迹。Minstrel只有两个简单的方法的类,在骑士执行每一个探险任务之前,singBeforeQuest()被调用;在骑士完成探险任务之后,singAfterQuest()方法被调用。
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 brabe"); //探险之前调用
}
public void singAfterQuest() {
stream.println("Tee hee hhe,the brave knight " + "did embark on a quest"); //探险之后调用
}
}
适当做一下调整,让BraveKnight可以使用Minstrel:
package sia.knights;
package sia.knights;
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() {
minstrel.singBeforeQuest(); //Knight应该管理Minstrel吗?
quest.embark();
minstrel.singAfterQuest();
}
}
这样可以达到预期效果,但是简单的BraveKnight开始变得复杂,不仅需要探险还需要管理吟游诗人,还需要应对没有吟游诗人(为null)的场景
利用AOP,你可以声明咏游诗人歌颂骑士的探险事迹,而骑士本身不直接访问Minstrel的方法要将一个Minstrel抽象为一个切面,只需在Spring配置文件中声明它
这里使用了Spring的aop配置命名空间把Minstrel声明为一个切面。首先需要把Minstrel声明为一个bean,然后在aop:aspect中引用该bean。为了进一步定义切面,声明(aop:before)在embarkOnQuest()方法执行前调用Minstrel的singBeforeQuest()方法,这种方式被称为前置通知。同时声明(aop:after)在embarkOnQuest()方法执行后调用Minstrel的singAfterQuest ()方法,这种方式被称为后置通知。pointcut-ref属性都引用列名为为“embark”的切入点,该切入点实在前面的元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用的是aspectJ的切点表达式语言。
Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用,其次最重要的是Minstrel可以被应用到BraveKnight中,而BraveKnight不需要显示的调用它,实际上,BraveKnight完全不知道MInstrel的存在
在许多编程场景中,会为了实现通用和简单的任务,重复编写相似的代码,这些代码被称为样板式代码。它们中很多是因为使用Java API而导致的样板式代码,比如在JDBC中,为了查询数据库,而不得不写的创建数据库连接、语句对象代码,以及必须捕捉的SQLException操作、查询完数据库后关闭数据库连接、语句和结果集操作。其中只有少量代码与真正的业务逻辑处理有关,其余代码都是样板式代码。
Spring旨在通过模板封装来消除样板式代码,Spring的JdbcTemplate避免了数据库操作时再去编写样板式代码。有关JDBC部分应用将在后续章节中给出。
在Spring中,应用对象生存与Spring容器中,Spring容器负责创建对象、装配它们、配置它们并管理它们整个生命周期。Spring自带了多个容器实现,可以归为两类:
两者区别:
Spring自带了多种类型的应用上下文。下面罗列几种常见的应用上下文:
AnnotationConfigApplicationContext: 从一个或多个基于Java的配置类中加载Spring应用上下文。
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
AnnotationConfigWebApplicationContext: 从一个或多个基于Java的配置类中加载Spring Web应用上下文。
ClassPathXmlApplicationContext: 从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
ApplicationContext context = new ClassPathXmlApplicationContext(“config.xml”);
FileSystemXmlApplicationContext: 从文件系统下的一个或多个XML配置文件中加载上下文定义。
ApplicationContext context = new FileSystemXmlApplicationContext(“c:/config.xml”);
XmlWebApplicationContext: 从Web应用下的一个或多个XML配置文件中加载上下文定义。
bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。
在bean准备就绪之前,bean工厂执行了若干启动步骤:
Spring框架由6个定义良好的模块分类组成。
Spring核心容器
容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能、Spring应用上下文,以及许多企业服务,如E-mail,JNDI访问,EJB继承和调度。
Spring的AOP模块
这个模块是Spring应用系统中开发切面的基础。AOP可以帮助应用对象解耦。借助于AOP,可以将遍布系统的关注点(例如事务和安全)从它们所应用的对象中解耦出来。
数据访问与集成
使用JDBC编写代码通常会导致大量的样板式代码,例如获得数据库连接、创建语句、处理结果集到最后关闭数据库连接。Spring的JDBC和DAO(Data Access Object)模块抽象了这些样板式代码,使我们的数据库代码变得简单明了,还可以避免因为关闭数据库资源失败而引发的问题。对于那些更喜欢ORM(Object-Relational Mapping)工具而不愿意直接使用JDBC的开发者,Spring提供了ORM模块。Spring的ORM模块建立在对DAO的支持之上,并为多个ORM框架提供了一种构建DAO的简便方式。Spring对许多流行的ORM框架进行了集成,包括Hibernate、Java Persisternce API、Java Data Object和iBATIS SQL Maps。Spring的事务管理支持所有的ORM框架以及JDBC。本模块同样包含了在JMS(Java Message Service)之上构建的Spring抽象层,它会使用消息以异步的方式与其他应用集成。除此之外,本模块会使用Spring AOP模块为Spring应用中的对象提供事务管理服务。
Web与远程调用
MVC(Model-View-Controller)模式是一种普遍被接受的构建Web应用的方法,它可以帮助用户将界面逻辑与应用逻辑分离。该模块还提供了多种构建与其他应用交互的远程调用方案。Spring远程调用功能集成了RMI(Remote Method Invocation)、Hessian、Burlap、JAX-WS,同时Spring还自带了一个远程调用框架:HTTP invoker。Spring还提供了暴露和使用REST API的良好支持。
Instrumentation
Spring的Instrumentation模块提供了为JVM添加代理(agent)的功能。具体来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。
测试
鉴于开发者自测的重要性,Spring提供了测试模块以致力于Spring应用的测试。通过该模块,你会发现Spring为使用JNDI、Servlet和Portlet编写单元测试提供了一系列的mock对象实现。对于集成测试,该模块为加载Spring应用上下文中的bean集合以及与Spring上下文中的bean进行交互提供了支持。
Spring Portfolio
Spring Portfolio包含构建于核心Spring框架之上的框架和类库,概括地说,Spring Portfolio为每一个领域的Java开发都提供了Spring编程模型
Spring Web Flow: 建立于Spring MVC框架之上,为基于流程的会话式Web应用提供支持。
Spring Web Service: 提供契约优先的Web Service模型,服务的实现都是为了满足服务的契约而编写的。
Spring Security: 为Spring应用提供声明式安全机制。
Spring Integration: 提供多种通用应用集成模式的声明式风格实现。
Spring Batch: 对数据进行大量操作时,可以通过Spring Batch开发批处理应用。
Spring Data: 无论是关系型数据库还是NoSQL、图形数据库等数据模式,Spring Data都为这些持久化提供了一种简单的编程模型。
Spring Social: 一个社交网络扩展模块。
Spring Mobile: 支持移动Web应用开发。
Spring for Android: 旨在通过Spring框架为开发基于Android设备的本地应用提供某些简单的支持。
Spring Boot: 依赖自动配置技术,消除大部分Spring配置,减小Spring工程构建文件大小。