Introduction to the Spring Framework

By Rod Johnson

自从这篇文章的第一版在2003年10月发表以来,Spring框架正在逐步普及。经历了1.0最终版到现在的1.2版,而且被运用于很多行业和项目中。在这篇文章中,我会解释Spring为什么会获得成功,并告诉你我十分肯定它能帮助你开发J2EE应用程序。

又是一个框架?

你可能正在想“不过是另一个的框架”。如今有这么多J2EE框架,并且你可以建立你自己的框架,为什么你应该读这篇文章或者下载Spring框架(或者你还没有下载)?社区中持续的高关注度暗示了Spring一定有它的价值;这也有很多技术原因。

以下的几个原因让我相信Spring是独一无二的:

l         它关注于很多其它框架没有关注的地方。Spring着重于提供一种管理你业务对象的方法。

l         Spring是全面的、模块化的。Spring采用分层架构,这意味着你可以仅选择其中任何一个独立的部分,而它的架构是内部一致的。因此你能从学习中获得最大的价值。例如,你可以仅用Spring来简化你的JDBC使用,或者你可以选择使用Spring来管理你的业务对象。把Spring增量地引入现有的项目中是十分容易的。

l         Spring从设计之初就是要帮助你写出易于测试的代码。Spring是测试驱动项目的一个理想框架。

l         Spring是一个日益重要的集成技术,它的角色已得到一些大厂商的认可。

Spring不需要你的项目再依赖于另一个框架。Spring也许能称得上是一个“一站式”商店,提供了大多数传统应用所需要的基础结构。它还提供了别的框架没有涉及到的东西。

作为一个从2003年2月开始的开源项目,Spring有深厚的历史背景。这个开源项目源自我在2002年底出版的《Expert One-on-One J2EE Design and Development》中的基础代码。书中展现了Spring背后的基础性架构思考。然而,这个架构概念可以追溯到2000年早期,并反映了我在一系列成功的商业项目的基础结构的开发中所获得的经验。

从2003年1月起,Spring落户于SourceForge。现在有20位开发者,一些主要人员把所有的时间都花在了Spring的开发和支持上。繁荣的开源社区帮助它茁壮成长,这远非任何个人所及。

Spring架构上的好处

在继续深入前,让我们来看看Spring带给一个项目的好处:

l         Spring可以有效组织你的中间层对象,无论你是否选择使用EJB。Spring关心那些当你只选择Struts或其他为J2EE API量身定做的框架时被留给你解决的问题。Spring的配置管理服务可以被运用于任何运行环境的各种架构性分层中,这也许是中间层中最有价值的。

l         Spring可以消除在很多项目中所常见的单例的过度使用。在我看来,它的主要问题是降低了可测试性和面向对象的程序。

l         Spring通过一种在应用程序和项目之间一致的方法来处理配置,这消除了需要自定义配置文件格式的烦恼。还记为了知道某个类要找哪个神奇的属性项或系统属性而不得不去读Javadoc,甚至读源代码吗?有了Spring你只要简单地看看类的JavaBean属性或构造参数。控制反转和依赖注入(将在下文讨论)的使用帮助实现了这一简化。

l         Spring通过把针对接口而非类编码的代价降低到几乎未零来帮助养成好的编码习惯。

l         Spring被设计为让构建在它之上的应用程序尽可能少地依赖于它的API。大多数Spring应用程序中的业务对象不依赖于Spring。

l         构建于Spring之上的应用程序很容易进行单元测试。

l         Spring使得是否使用EJB成为实现时的选择,而非架构上的决定。你能在不改变调用代码的情况下选择用POJO或EJB来实现业务接口。

l         Spring帮助你在不用EJB的情况下解决很多问题。Spring能提供一种适用于很多应用程序的EJB的替代品。例如,Spring可以无需EJB容器,用AOP来提供声明性事务管理;如果你仅与一个数据库打交道,甚至可以没有JTA实现。

l         Spring为数据访问提供了一个一致的框架,无论使用JDBC还是像TopLink、Hibernate或者JDO实现这样的实体关系映射产品。

l         Spring为很多方面提供了一种一致的简单的编程模型,这使得它成为了一种理想的架构“胶”。你可以从Spring访问JDBC、JMS、JavaMail、JNDI和很多其他重要API的途径中发现这种一致性。

Spring是一种帮助你使用POJO来构建应用程序的基础技术。要达到这个目标需要一个能将复杂性隐藏起来的成熟的框架。

因此Spring真的可以帮助你实现针对你的问题的最简单可行的解决方案。这是十分有价值的。

Spring做了些什么?

Spring提供许多功能,所以我将依次地快速浏览每个主要方面。

任务描述

首先,让我们明确一下Spring的范围。尽管Spring囊括了很多东西,但我们应该清楚的知道它该涉及什么,不该涉及什么。

Spring的主要目的是使J2EE更易于使用,培养好的编程习惯。它通过使用一种能适用于很多环境下的基于POJO的编程模型来实现这一目的。

Spring不重新发明轮子。因此你会发现Spring中没有日志,没有连接池,没有分布式事务调度。所有这些东西都由开源项目(例如提供我们所有的日志输出的Commons Logging,或者是Commons DBCP)或你的应用服务器提供。同样的道理,我们不提供实体/关系映射层。因为有像TopLink、Hibernate和JDO这样的优秀的解决方案。

Spring致力于使现有技术更加易用。例如,尽管我们没有底层业务的事务调度,但我们提供了一个凌驾于JTA或其他事物策略的抽象层。

Spring不直接与其他开源项目竞争,除非我们觉得我们能提供些新的东西。比如说,像其他开发者一样,我们从未就Struts感到满意,我们觉得MVC Web框架还有改进的余地。(随着Spring应用地快速推广,很多人也同意了我们的观点。)在很多领域,例如它的轻量级IoC容器和AOP框架,Spring有直接的竞争,但Spring确实是这些领域的先锋。

Spring得益于内部一致性。所有开发者正唱着同一首赞歌,基础思想依然忠于《Expert One-on-One J2EE Design and Development》中提出的思想。我们已经能够在多个领域中使用

些核心概念,例如控制反转。

Spring可用于各种应用服务期。当然保证可移植性一直是一个挑战,但我们避免了开发者眼中的各种平台特有的或非标准的东西,支持WebLogic、Tomcat、Resin、Jboss、Jetty、Geronimo、WebSphere和其他应用服务器。Spring的非侵入性、POJO方法是我们可以利用环境特有特性而不用放弃可移植性,就像Spring 1.2中在掩护下使用BEA特有API从而开启增强WebLogic事务管理功能。

控制反转(Inversion of Control, IoC)容器

Spring的核心是为与JavaBeans一起工作而设计的org.springframework.beans包。这个包一般不直接被用户调用,而是作为Spring功能的基础。

下一个更高的抽象层是bean工厂。一个Spring的bean工厂是一个普通的工厂,它能通过名称获得对象,并管理对象的关系。

Bean工厂支持两种模式的对象:

l         单例:这种情况下,存在一个有特定名称,在查找时能被获取的共享对象实例。这是默认的,也是最常用的模式。是无状态服务对象的理想选择。

l         原型或非单例:在这种情况下,每次获取操作都会创建一个独立的对象作为结果。例如,这能被用来使每个调用者都有自己的独立的对象引用。

因为Spring容器管理对象间的关系,它能在以下情况添加值,在诸如受管理的POJO的透明池、支持热交换之类的服务需要的地方,为在运行时交换目标引用但不影响调用者和线程安全性而由容器引入的一个间接层中。依赖注入的众多优点之一(简单讨论一下)就是这所有的一切几乎是透明的,没有API介入。

org.springframework.beans.factory.BeanFactory是一个简单的接口,它能通过多种途径被实现。BeanDefinitionReader接口将元数据格式从BeanFactory各自的实现中分离出来,所以Spring提供的普通BeanFactory实现能和不同类别的元数据一起使用。尽管很少有人发现有这个必要,你还是可以简单地实现你自己的BeanFactory或者BeanDefinitionReader。最常用的BeanFactory定义是:

l         XmlBeanFactory:它可解析简单直观的定义类和命名对象属性的XML结构。我们提供了一个DTD帮助简化编写。

l         DefaultListableBeanFactory:它提供了解析属性文件中的bean定义的能力,可通过编程创建BeanFactory。

每个bean定义能被当作一个POJO(用类名和JavaBean初始属性或构造方法参数来定义),或被当作一个FactoryBean。FactoryBean接口添加了一个间接层。一般,这用来创建用AOP或其他方法的代理对象:例如,添加声明性事物管理的代理。这在概念上和EJB的拦截机制相近,但实践起来更方便,更有效。

BeanFactory能选择性地参与于一个层次结构中,“继承”先辈的定义。这使得像控制器servlet这样的个体资源能拥有自己的独立对象集的同时,在整个应用程序中能共享公共配置。

如此使用JavaBeans的动机在《Expert One-on-One J2EE Design and Development》的第四章中已经描述过了,你同样也可以在theServerSide站点上以免费PDF的形式获得(/articles/article.tss?l=RodJohnsonInterview)。

通过bean工厂的概念,Spring成为了一个控制反转容器。(我不太喜欢容器这个词,因为它令人想起了类似EJB容器的重量级容器。一个Spring的BeanFactory是一个能用一行代码创建,无需特别部署的容器。)Spring用了名为依赖注入(Dependency Injection, DI)的控制反转,依赖注入是由Martin Fowler、Rod Johnson和PicoContainer团队在2003年底命名的。

控制反转背后的原则常被称为好莱坞原则:“不要打电话找我,我会打给你的。”IoC将创建的职责从应用程序代码中搬到了框架中。但在你的代码调用一个传统类库时,IoC框架调用你的代码。在很多API中的生命周期回调证明了这点,比如会话EJB的setSessionContext()方法。

依赖注入是IoC的一种形式,它消除了对容器API的显式依赖;普通的Java方法被用来将诸如协作对象或配置值之类的依赖注入应用程序对象实例。涉及到配置的地方,这就意味着在像EJB这样的传统容器架构中,一个组件可以调用容器并说“我需要的对象X在什么地方”,有了依赖注入容器指出组件需要对象X,并在运行时将它提供给组件。容器通过方法签名(一般是JavaBean的属性或构造方法)和可能的诸如XML的配置数据来实现这一功能。

两种主要的依赖注入是Setter注入(通过JavaBean的setter注入);和构造方法注入(通过构造方法参数注入)。Spring对两者都提供了很好的支持,你甚至可以在配置一个对象时将两者结合起来。

在支持各种形式的依赖注入的同时,Spring也提供一系列回调事件,和一个针对某些需要传统查找的API。但是,我们基本上是建议使用纯依赖注入途径的。

依赖注入有几个重要的好处。例如:

l         因为组件不需要在运行时查找协作者,所以它们更易开发和维护。在Spring版本的IoC中,组件通过暴露JavaBean的setter方法或构造方法参数来表示它们对其他组件的依赖。这相当于EJB的JNDI查找,EJB的JNDI查找需要编写代码、设置环境参数。

l         由于同样的原因,应用程序代码更易测试。例如,JavaBean属性是简单的,纯Java的并且容易测试:简单地编写一个创建对象并设置相关属性的独立Junit测试方法。

l         一个好的IoC实现保留了强类型。如果你需要用一个通用工厂来查找协作者,你可以把结果转换为需要的类型。这并不是主要问题,但这不优雅。使用IoC,你在代码中表达了强类型依赖后,框架会负责类型转换。这意味着类型不匹配会在框架配置应用程序时被当作错误抛出;你不必在你的代码中担心类转换异常。

l         依赖是显式的。例如,如果一个应用程序类尝试加载一个属性文件或通过实例连接一个数据库,不读代码可能弄不清楚环境参数(有复杂的测试并降低了部署的灵活性)。使用了依赖注入的手段,依赖是显式的,能通过构造方法或JavaBean属性得知。

l         大多数业务对象不依赖IoC容器API。这使得使用现有代码变得十分容易,并且能方便地使用IoC容器内和容器外的对象。比如说,Spring的用户经常把Jakarta Commons DBCP数据源配置为一个Spring bean:没有必要写代码来实现这一步。我们说一个IoC容器不是侵入性的:使用它并不用让你的代码依赖于它的API。几乎任何POJO能作为一个Spring bean工厂中的组件。现有的JavaBean或有多参数构造方法的对象都能很好地工作,但Spring也需要对从静态工厂方法实例化的对象或者IoC容器管理的其他对象的方法提供特别支持。

这最后一点需要强调一下。依赖注入不像传统容器架构(比如EJB)那样应用程序代码存在最小限度的对容器的依赖。这意味着你的业务对象可以潜在地被运行于其他依赖注入框架,或不使用任何框架,而不需要改变代码。

以我和Spring用户的经验来说,就算再怎么强调IoC(特别是依赖注入)带给应用程序代码的好处都不为过。

尽管依赖注入刚在J2EE社区中达到它的黄金时间,但它并不是一个新概念。还有其他可选的DI容器:notably、PicoContainer和HiveMind。PicoContainer是轻量级的且强调通过构造方法而不是JavaBean属性来表现依赖。它不在Java代码外使用元数据,与Spring相比这限制了它的功能。HiveMind在概念上更接近Spring(它也关注IoC以外的东西),但它缺乏Spring项目这样广泛的领域且没有相同规模的用户社群。EJB 3.0也会提供基本的DI能力。

Spring的BeanFactory是非常轻量级的。用户们曾成功地在applet和独立Swing应用程序中使用过它们。(它们在EJB容器中也有很好表现。)它们没有特殊的部署步骤,也没有与容器本身相关的可察觉的启动时间(尽管容器配置这类对象需要花时间去初始化)。这种在应用程序的任何一层中都能立即实例化一个容器的能力是十分有价值的。

Spring的BeanFactory的概念贯穿于整个Spring中,这也是Spring如此内部一致的一个关键原因。在IoC容器中,Spring也是唯一一个将IoC作为基本概念贯穿在一个功能丰富的框架中的。

对于一个应用程序开发者来说,一个或多个BeanFactory提供了一个定义明确的业务对象层。这类似于本地会话bean层,但更简单且更强大。不像EJB,这层中的对象可能是相关的,它们的关系被拥有它们的工厂管理着。有一个定义明确的业务对象层对于一个成功的架构来说是十分重要的。

一个Spring的ApplicationContext是BeanFactory的一个子接口,它提供了如下支持:

l         消息查找,支持国际化

l         一种事件机制,允许发布应用程序对象或者可选的注册以接收事件

l         自动识别特殊的应用程序细节或通用bean定义来自定义容器行为

l         可移植文件和访问资源

XmlBeanFactory例子

Spring用户一般用XML“bean定义”文件来配置他们的应用程序。一个Spring XML bean定义文件的根是<beans>元素。<beans>元素包含一个或多个<bean>定义。我们一般会指定每个bean定义的类和属性。我们还必须指定id,这是我们在自己的代码里使用这个bean时的名字。

让我们来看一个简单的例子,这个例子配置三个有J2EE应用程序常见关系的对象:

l         一个J2EE数据源

l         一个使用数据源的DAO

l         一个在工作中使用DAO的业务对象

下面的例子中,我们使用Jakarta Commons DBCP项目中的一个BasicDataSource。(C3PO项目中的ComboPooledDataSource也是一个不错的选择。) BasicDataSource,和许多别的现有类一样,能轻松地使用于Spring bean工厂中,因为它提供了JavaBean样式的配置。在关闭时需要调用的close方法能通过Spring的“destroy-method”属性来注册,这样BasicDataSource就不需要实现Spring的接口了。

<beans>

<bean id="myDataSource"

class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

<property name="driverClassName" value="com.mysql.jdbc.Driver" />

<property name="url" value="jdbc:mysql://localhost:3306/mydb" />

<property name="username" value="someone" />

</bean>

所有我们感兴趣的BasicDataSource属性都是字符类型的,所以我们用“value”属性来指定它们的值。(这个快捷方式是Spring 1.2引进的。这个对<value>子元素的一个方便的替代,它甚至在有问题的XML属性值中也能使用。)Spring使用标准JavaBean PropertyEditor机制在需要时将字符表达式转换为其他类型。

现在我们定义DAO,其中有一个对数据源的bean引用。Bean之间的关系用“ref”属性或<ref>元素来指定。

<bean id="exampleDataAccessObject" class="example.ExampleDataAccessObject">

<property name="dataSource" ref="myDataSource" />

</bean>

业务对象有一个DAO的引用和一个int属性(exampleParam)。在这个例子中,我用了与1.2版之前相似的子元素语法。

<bean id="exampleBusinessObject" class="example.ExampleBusinessObject">

<property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>

<property name="exampleParam"><value>10</value></property>

</bean>

</beans>

对象间的关系一般在配置中显式设置,就像例子中那样。我们认为在大多数情况下这是一件好事。但是,Spring也提供了对我们称为“自动装配 (autowire)”的支持,它指出了bean之间的依赖。这种做法的限制(和PicoContainer一样)是如果存在一个特定类型的多个bean,那么没办法判断该解析那个类型的哪个实例依赖。从积极的一面看,无法满足的依赖能在工厂初始化时被捕捉。(Spring还提供了对显式配置的一个可选依赖检查来实现这个目标。)

如果你不想显式编写这些关系,我们可以像下面这样在上面的例子中使用自动装配这个特性。

<bean id="exampleBusinessObject" class="example.ExampleBusinessObject"

autowire="byType">

<property name="exampleParam" value="10" />

</bean>

使用这种用法,Spring会判断出exampleBusinessObject的dataSource属性应该设置到当前BeanFactory中的DataSource实现。如果在当前BeanFactory中没有或有多个满足要求的类型,会产生一个错误。我们仍然需要设置exampleParam属性,因为它不是一个引用。

自动装配有降低配置量的好处。它也意味着容器能使用反射来学习应用程序结构,所以如果你添加一个附加的JavaBean属性构造方法参数,它可以成功地移植而不用改变配置。这是自动装配需要精心计算的一个折衷。

将关系从Java代码中提取出来比起硬编码来说有很大好处,因为它可以改变XML文件而不用改变一行Java代码。例如,我们可以简单地通过改变myDataSource bean定义来引用一个不同的bean类来使用另一个连接池或者测试数据源。我们能像下面这样用Spring的JNDI位置FactoryBean来从一个XML片断里的应用服务器中获得数据源。这对Java代码和任何其他bean定义没有影响。

<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName" value="jdbc/myDataSource" />

</bean>

现在让我们看看范例业务对象的Java代码。注意下面的代码中没有对Spring的依赖。与EJB容器不同,一个Spring的BeanFactory不是侵入式的:你通常不需要有意将它编入你的对象中。

public class ExampleBusinessObject implements MyBusinessObject {

private ExampleDataAccessObject dao;

private int exampleParam;

public void setDataAccessObject(ExampleDataAccessObject dao) {

this.dao = dao;

}

public void setExampleParam(int exampleParam) {

this.exampleParam = exampleParam;

}

public void myBusinessMethod() {

// 使用dao

}

}

注意属性的setter,它们符合bean定义中的XML引用。这些在对象被使用前由Spring调用。

这类应用程序bean不需要依赖Spring:他们不需要实现任何Spring接口或扩展Spring类,它们仅需要遵循JavaBeans命名规范。在Spring应用程序上下文外重用是很方便的,比如在一个测试环境中。用它的默认构造方法初始化它,并通过setDataSource()和setExampleParam()手动设置其属性。只要你有一个无参数的构造方法,你可以自由定义其他获取多个属性的构造方法如果你想支持一行代码中的可编程构造。

注意业务接口调用者中没有声明将要使用的JavaBean属性。它们是实现细节。我们能轻松插入拥有不同bean属性的实现类而不用影响关联的对象或调用代码。

当然Spring的XML bean工厂还有许多别的功能,但让你对基本的方法一个了解。和简单属性、JavaBeans PropertyEditor的属性一样,Spring能处理列表、映射和java.util.Properties。其他高级容器功能包括:

l         内部bean,一个属性元素包含一个上层看不到的匿名bean定义。

l         Post处理器,特别的自定义容器行为的bean定义。

l         方法注入,IoC的一种形式,容器实现一个抽象方法或覆盖一个具体方法来诸如一个依赖。这比依赖注入的Setter或构造方法注入更少用。但是,在为每次调用查找新的对象实例或允许配置改变额外时间时避免显式容器依赖方面这很有用,例如,在一个有SQL查询支持的方法实现和一个fil系统读入另一个。

Bean工厂和应用程序上下文经常在J2EE服务器或web容器定义的范围内存在关联,比如:

l         Servlet上下文:在Spring MVC框架中,一个为每个web程序定义的应用程序上下文包含公共对象。Spring提供了通过一个监听器或不依赖于Spring MVC框架的servlet来初始化这类上下文的能力,这也可以被用在Struts、WebWork或其他web框架中。

l         一个Servlet:Spring MVC框架中的每个控制器servlet有它自己的源自根(应用程序级)应用程序上下文的上下文。同样也能在Struts或其他web框架中实现这个。

l         EJB:Spring提供了简化EJB认证的EJB超类,还提供了一个从EJB Jar文件中加载的BeanFactory。

这些J2EE规范提供的钩避免了使用单例来自展一个bean工厂。

然而,如果我们愿意的话,可以通过编码初始化一个BeanFactory,尽管这是没有什么价值的。例如,我们能像下面两行代码那样建立一个bean工厂并获得一个定义好的业务对象的引用:

XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("myFile.xml", getClass()));

MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");

这个代码能工作在应用服务器外:它不依赖于J2EE,因为Spring的IoC容器是纯Java的。Spring Rich项目(一个用Spring简化Swing应用程序开发的框架)演示了如何在一个J2EE环境外使用Spring,还有文章下面要讨论的Spring的集成测试特性。依赖注入和相关功能太通用、太有价值以至无法被局限在一个J2EE或服务端环境中。

JDBC抽象和数据存取异常层次

数据存取是Spring的另一个闪光点。

JDBC提供了底层数据库的很好的抽象,但使用它的API十分痛苦。这些问题包括:

l         需要详细的异常处理来保证ResultSet、Statement和(最重要的) Connection在使用后被关闭。这意味着要正确使用JDBC需要很多代码。这也是一个常见的错误的源头。连接泄漏可以让应用程序很快崩溃。

l         SQLException相对来说不能说明任何问题。JDBC不提供一个异常的层次,而是用抛出SQLException来响应所有的错误。要找到是什么东西错了(例如,是死锁还是非法的SQL)需要检查SQLState的值和错误码。这些值随着数据库的不同而不同。

Spring通过两种方法解决这些问题:

l         提供API将冗长的易出错的异常处理从应用程序代码中移到框架中。框架会负责所有的异常处理;应用程序代码可以集中精力在写恰当的SQL和提取结果上。

l         为你的应用程序提供一个有意义的异常层次来代替SQLException。当Spring第一次从数据源获得一个连接时,它会检查元数据来决定是什么数据库产品。再用这信息将SQLExceptions映射到它自己的从org.springframework.dao.DataAccessException继承下来的层次中的正确异常。这样一来你的代码可以和有意义的异常打交道,不用担心私有的SQLState和错误码。Spring的数据存取异常不是JDBC特有的,所以你的DAO不用因为它们可能会抛出的异常而绑死在JDBC上。

下面的UML类图阐明了一部分数据存取异常的层次,展现了它的完善度。注意这里异常没有一个是JDBC特有的。有些JDBC特有的异常是这些异常的子类,但调用的代码一般是完全抽象于对JDBC的依赖的:如果你想用完全独立于API的DAO接口来隐藏你的持久策略,这是最基本的。

Spring提供了两层JDBC抽象API。第一层,在org.springframework.jdbc.core包中,用回调将控制权(并将相关的错误处理和连接获取与释放)从应用程序代码移到框架中。这是一种不同类型的控制反转,但与配置管理用的有一样的价值。

Spring用相似的回调方法来处理其他几个包含获得和清除资源的特殊步骤的API,例如JDO(获得和释放一个PersistenceManager),事务管理(使用JTA)和JNDI。执行这些回调的Spring类叫模板(template)。

例如,Spring的JdbcTemplate对象可以通过如下方法用来执行SQL查询和保存列表中的结果:

JdbcTemplate template = new JdbcTemplate(dataSource);

List names = template.query("SELECT USER.NAME FROM USER",

new RowMapper() {

public Object mapRow(ResultSet rs, int rowNum) throws SQLException {

return rs.getString(1);

}

});

mapRow回调方法会被ResultSet中的每一行调用。

注意回调方法内的应用程序代码可以自由抛出SQLException:Spring会捕获任何异常再在它自己的层次内重新抛出。应用程序开发者能选择捕获和处理哪个异常,如果有的话。

JdbcTemplate提供了很多方法来支持不同的场景,包括已准备的语句和批处理更新。像运行SQL函数这样的简单任务可以像下面这样不用回调来完成。这个例子也示范了绑定变量的使用:

int youngUserCount = template.queryForInt("SELECT COUNT(0) FROM USER WHERE USER.AGE < ?", new Object[] { new Integer(25) });

Spring的JDBC抽象相对标准JDBC而言性能损失非常小,甚至在处理庞大结果集的时候也是如此。(在2004年的一个项目中,我们记录到一个金融项目每个事务执行1200000条插入操作。Spring JDBC的开销是最小的,Spring的使用方便了调整批处理大小和其他参数。)

更高层的JDBC抽象包含在org.springframework.jdbc.object包中。这是建立在核心JDBC回调功能上的,但是它提供了一个API,其中的对RDBMS的操作(无论是查询、更新或者是存储过程)被做成了一个Java对象。这个API的灵感部分来自于JDO的查询API,我发现它很直观很好用。

一个返回User对象的查询对象大概是这样的:

class UserQuery extends MappingSqlQuery {

public UserQuery(DataSource datasource) {

super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?");

declareParameter(new SqlParameter(Types.NUMERIC));

compile();

}

// 将一个记录集映射到一个Java对象

protected Object mapRow(ResultSet rs, int rownum) throws SQLException {

User user = new User();

user.setId(rs.getLong("USER_ID"));

user.setForename(rs.getString("FORENAME"));

return user;

}

public User findUser(long id) {

// 用超类的方法并进行强制类型转换

return (User) findObject(id);

}

}

这个类可以这样被使用:

User user = userQuery.findUser(25);

这样的对象经常是DAO的内部类。他们是线程安全的,除非子类做了些不寻常的事。

另一个org.springframework.jdbc.object包中的重要类是StoredProcedure类。Spring通过一个带业务方法的Java类来代理一个存储过程。如果你喜欢,你能定义一个存储过程实现的接口,这意味你可以彻底把你的应用程序带从一个存储过程的依赖中解放出来。

Spring的数据存储异常层次是基于未经检查(运行时)异常。在几个项目中使用了Spring之后我越来越确信这是正确的决定。

数据存取异常一般是不可恢复的。例如,如果我们不能连接到数据库,一个特定的业务对象就不能解决要处理的问题。一个潜在的异常是乐观锁冲突,但不是所有的应用程序使用乐观锁。强制编写代码去捕获不能很好处理的致命异常一般是不太好的。把它们抛给类似Servlet或EJB容器这样的高层去处理更合适。所有的Spring数据存取异常是DataAccessException的子类,所以如果我们想要选择去捕获所有的Spring数据存取异常,我们能轻松办到。

注意,如果我们想要从一个未经检查的数据存取异常中恢复过来,我们仍然可以做到。我们能便写只处理可恢复情况的代码。例如,如果我们认为只有一个乐观锁冲突是可恢复的,我们可以在一个Spring的DAO中编写如下代码:

try {

// 工作

}

catch (OptimisticLockingFailureException ex) {

// 我对这很感兴趣

}

如果Spring数据存取异常是经过检查的,我们需要如下代码。注意,随便什么情况我们都能这么写。

try {

// 工作

}

catch (OptimisticLockingFailureException ex) {

// 我对这很感兴趣

}

catch (DataAccessException ex) {

// 致命;仅再次抛出它

}

第一个例子的一个潜在缺陷(编译器不会强制处理潜在可恢复异常)在第二个例子中仍然存在。因为我们被强制去捕获基本异常(DataAccessException),编译器不会强制对一个子类(OptimisticLockingFailureException)的检查。因此编译器可能会强制我们去写代码处理一个不可恢复的问题,但不会对强制我们处理的可恢复问题提供帮助。

Spring对未经检查的数据存取异常的使用和许多(也许是大多数)成功的持久化框架是一致的。(确实,这部分受到了JDO的影响。)JDBC是少数几个使用经检查的异常的数据存取API。例如,TopLink和JDO只使用未经检查的异常。Hibernate在版本3中可以在经检查的和未经检查的异常间切换。

Spring JDBC可以通过以下途径来帮助你:

l         你不再需要写finally块来使用JDBC。

l         连接泄漏会成为过去。

l         总的来说你写的代码少了,并且那些代码清楚地集中在需要的SQL上。

l         你不再需要翻你的RDBMS文档来找那因错误的字段名而返回的晦涩的错误码。你的应用程序不依赖于特定的RDBMS错误处理代码。

l         无论是用何种持久化技术,你会发现可以很方便地实现DAO模式而不用让业务逻辑依赖于任何特定的数据存取API。

l         你能在例如BLOB处理和调用存储过程这样的高级应用中获得更好的可移植性(与纯JDBC相比),从而获益。

在实践中我们发现,所有这些都有助提高生产力和减少错误。我曾经讨厌写JDBC代码;现在我发现我可以关注我想要执行的SQL,而不是随之而来的JDBC资源管理。

如果需要,Spring的JDBC抽象能被单独使用——你可以不使用Spring的其他部分。

实体关系映射(O/R mapping, ORM)集成

当然你经常需要使用实体关系映射,而不是使用关系型数据访问。你的整体应用程序框架也必须支持这个。因而Spring继承了Hibernate(版本2和3)、JDO(版本1和2)、TopLink和其他ORM产品。他的数据访问架构允许与任何底层数据访问技术集成。Spring和Hibernate是一个相当流行的组合。

为什么你要用一个ORM产品加上Spring,而不是直接使用它呢?Spring在以下方面增加了重要价值:

l         会话管理。Spring提供了对诸如Hibernate或Toplink会话的有效、简单而且安全的处理。相关的单独使用ORM工具的代码通常需要使用同一个“Session”对象来达到有效且合适的事务处理。Spring能使用一个声明性的AOP方法拦截器或使用显式的Java代码级的“模板”封装类来透明创建一个会话并将其绑定到当前线程。因而Spring解决了许多影响ORM技术用户的使用问题。

l         资源管理。Spring应用程序上下文能处理Hibernate SessionFactory、JDBC数据源和其他相关资源的定位和配置。这使得这些值易于管理和更改。

l         完整的事务管理。Spring允许你将你的ORM代码封装为一个声明性的AOP方法拦截器或者一个显式的Java代码级的“模板”封装类。不管哪种情况,事务语义都为你处理了,并且在异常发生时也作了恰当的事务处理(回滚等)。正如我们后面讨论的,你也可以使用并切换不同事务管理器而不影响你的ORM相关代码,并从中获益。一个额外的好处是为了支持大多数ORM工具JDBC相关代码能完全事务性地与ORM代码集成。这对于处理ORM没有实现的功能时很有用。

l         如上所述的异常封装。Spring能封装来自ORM层的异常,将它们从私有(可能是经过检查的)异常转换为一组抽象的运行时异常。这允许你可以不用烦人的样板化的catch/throw和异常声明仍能在恰当的层中处理大多数不可恢复的持久化异常。你仍能在任何需要的地方捕获和处理异常。请记住JDBC异常(包括数据库特有方言)也被转换为相同层次,这意味着你能在一致的编程模型内用JDBC执行一些操作。

l         避免和厂商绑定。ORM解决方案有不同的性能和特性,没有能满足所有情况的完美解决方案。作为选择,你会发现某一个功能正好不匹配你用的ORM工具的一个实现。这让你意识到你的数据访问对象接口的特定工具实现对你的架构有影响。一旦你因为功能、性能或其他原因需要切换到另一个实现,现在使用了Spring让最终的切换变得更容易了。Spring对你ORM工具的事务和异常的抽象,和它的IoC方法一起让你在映射器/DAO对象实现数据访问功能间轻松切换,简单地实现将ORM特有代码控制在你应用程序的一个范围里而不牺牲你ORM工具的能力。和Spring一起发布的宠物医院范例程序演示了Spring通过提供使用JDBC、Hibernate、TopLink和Apache OJB实现的不同持久层而带来的可移植性方面的价值。

l         简化测试。Spring的控制反转方法使得切换实现和诸如Hibernate会话工厂、数据源、事务管理器和映射器对象实现(如果需要)之类的资源位置变得简单了。这简化了隔离和每部分持久化相关代码的单独测试。

综上所述,Spring让混用数据存取过程更简单了。尽管ORM在很多案例里赢得了颇有价值的生产力,无论一些ORM厂商怎么宣称,ORM不是所有问题的解决方案。即使你混用各种持久化方法,即使你不用JTA,Spring仍能提供一致的架构和事务策略。

在ORM并不理想的地方,Spring的简化JDBC并不是唯一选择:iBATIS SQL Maps提供的“映射语句”也值得一看。它在保持自动从查询结果中创建映射对象的同时提供了对SQL的高层控制。Spring集成了SQL Maps。Spring的宠物店范例程序演示了iBATIS集成。

事务管理

仅抽象一个数据访问API是不够的;我们还需要考虑事务管理。JTA是当然的解决方案,但直接使用它的API是很笨重的,因而许多J2EE开发者曾觉得EJB CMT是事务管理的唯一明智的选择。Spring改变了这一点。

Spring提供了它自己的针对事务管理的抽象。Spring使用它来提供:

l         通过一个类似JdbcTemplate的回调模板来实现可编程的事务管理,比起直接使用JTA容易许多。

l         类似EJB CMT的声明性事务管理,但不需要EJB容器。实际上,正如我们所见,Spring的声明性事务管理能力是EJB CMT语义上的超集,有些独特的重要的好处。

Spring的事务抽象的独特之处在于它不绑定于JTA或其他任何事务管理技术。Spring使用事务策略概念,这减弱了底层事务基础部分(例如JDBC)对应用程序代码的影响。

为什么你需要关心这个?JTA不是对所有事务管理问题的最好答案吗?如果你正在写一个只与一个数据库打交道的应用程序,你不需要JTA的复杂性。你不关心XA事务或两阶段提交。你甚至可以不需要一个提供这些东西的高端应用服务器。但另一方面,你不会愿意在和多个数据源打交道时重写你的代码。

假定你决定直接使用JDBC或Hibernate的事务来避免JTA的开销。一旦你需要与多个数据源打交道,你会不得不找出所有事务管理代码并用JTA事务来替换它们。这难道不具吸引力?这导致包括我在内的大多数J2EE开发者推荐只使用全局JTA事务,并用像Tomcat这样的简单Web容器来有效管理事务程序。不管怎样,使用Spring事务抽象,你只要重新配置Spring来使用JTA,而不是JDBC或Hibernate的事务策略就可以了。这是一个配置的修改,不是代码变更。因此,Spring让你的应用程序能随意缩放。

AOP

从2003年起在企业关注问题上(例如一般使用EJB来做的事务管理)使用AOP解决方案大家有了很大的兴趣。

Spring的AOP支持的首要目标是为POJO提供J2EE支持。Spring AOP能够在应用服务器之间移植,所以没有厂商绑定的风险。它可以工作在web容器或者是EJB容器中,而且已被成功应用于WebLogic、Tomcat、JBoss、Resin、Jetty、Orion和其他很多应用服务器及web容器上。

Spring AOP支持方法拦截。所支持的关键AOP概念包括:

l         拦截(Interception):自定义的行为能插入到任何接口和类的方法调用的前面或后面。这和AspectJ术语中的“around advice”相近。

l         引入(Introduction):一个执行逻辑(advice)会导致一个对象实现额外的接口。这会引起继承混合。

l         静态和动态切入点:在程序执行过程中指定发生拦截的地方。静态切入点关注方法签名;动态关注点还能考虑被计算处的方法参数。切入点与拦截器分开定义,这使得一个标准拦截器可以被用在不同应用程序和代码上下文中。

Spring支持有状态的(每个执行逻辑对象一个实例)和无状态的(所有执行逻辑用一个实例)拦截器。

Spring不支持字段拦截。这是一个经过深思熟虑的设计决定。我总觉得字段拦截不符合封装原则。我更愿意认为AOP是OOP的一个补充而不是与它冲突。在5到10年后,我们在AOP的学习曲线上走得更远了很自然地会觉得应该在应用程序设计的桌面上给AOP留个位置。(那时像AspectJ这样的基于语言的解决方案会比今天更具吸引力。)

Spring用动态代理(需要存在一个接口)或运行时CGLIB字节码生成(这使得类的代理成为可能)来实现AOP。这两种方法能在任何应用服务器中运行,也能工作在独立环境中。

Spring是第一个实现了AOP联盟接口(www.sourceforge.net/projects/aopalliance)的AOP框架。这些代表了不同AOP框架的拦截器也许可以协同工作。

Spring集成了AspectJ,提供了将AspectJ的方面无逢集成到Spring应用程序中的能力。从Spring 1.1开始已经可以用Spring的IoC容器依赖注入AspectJ的方面,就像任何Java类一样。因此AspectJ的方面可以依赖于任何Spring管理的对象。对即将到来的AspectJ版本5的集成更令人激动,基于注释驱动切入点,AspectJ开始提供用Spring依赖注入任何POJO的能力。

因为Spring拦截的是实例级对象而不是类加载器级的,所以可以在同一个类的不同实例上使用不同的执行逻辑,或把未拦截的对象和已拦截的对象一起使用。

也许Spring AOP的常见用途是用于声明性事务管理。这构建于前面讨论过的事务抽象之上,并且可以用任何POJO进行声明性事务管理。依靠事务策略,底层机制可以是JTA、JDBC、Hibernate或是其他任何提供事务管理的API。

以下是与EJB CMT的主要不同:

l         事务管理可以应用于任何POJO。我们建议业务对象实现接口,这只是一个好的编程习惯,不是由框架所强制的。

l         通过使用Spring的事务API在一个事务性POJO中实现可编程的回滚。我们为此提供了静态方法,使用ThreadLocal变量,所以你不必传播一个类似EJBContext这样的上下文对象来保证回滚。

l         你能定义声明式的回滚规则。应用程序开发者常想要事务能够在遇到任何异常时回滚,但EJB在遇到未捕获的应用程序异常时不会自动回滚(仅仅在未检查的异常和其他Throwable异常还有“系统”异常时才回滚)。Spring的事务管理允许你声明式地指定哪个异常和子类是应该引起自动回滚的。默认的行为和EJB一样,但你可以指定经检查异常和未经检查异常一样回滚。这在最小化可编程回滚需要上有很大好处,而这可编程回滚也建立了对Spring事务API的依赖(就像EJB可编程回滚建立对EJBContext的依赖一样)。

l         底层Spring事务抽象支持保存点(如果底层事务基本结构支持的话),所以Spring的声明性事务管理能支持嵌套事务,和EJB CMT特有的传播模式(Spring支持和EJB一样的语义)。因而,举例来说,如果你在Oracle上执行JDBC操作,你能通过Spring使用声明性嵌套事务。

l         事务管理没有绑定JTA。正如前面解释的,Spring事务管理能和不同事务策略合作。

你还可以使用Spring AOP实现应用程序特有方面。是否要这样做取决于你对AOP概念的理解程度,而不是Spring的能力,但这会是很有用的。我们见到的成功例子包括:

l         在安全检查要求的复杂度超过了标准J2EE安全基础结构的地方自定义安全拦截器。(当然,在开始你自己的安全结构前,你应该看看Acegi Security for Spring,一个强大、灵活的用AOP和Spring整合的安全框架,这也反映了Spring架构上的方法。)

l         在开发中使用调式和记录方面。

l         实现在一个地方使用一致的异常处理策略的方面。

l         发送邮件给管理员或用户,警告不正常情况的拦截器。

应用程序特有方面能成为消除许多方法对样板代码需要的有效途径。

Spring AOP透明地与Spring BeanFactory概念集成。代码从一个Spring的BeanFactory中获得一个对象的时候不用知道它是不是被拦截。和任何对象一样,契约是在对象实现的接口上定义的。

下面的XML片断描述了如何定义一个AOP代理:

<bean id="myTest"

class="org.springframework.aop.framework.ProxyFactoryBean">

<property name="proxyInterfaces">

<value>org.springframework.beans.ITestBean</value>

</property>

<property name="interceptorNames">

<list>

<value>txInterceptor</value>

<value>target</value>

</list>

</property>

</bean>

注意,尽管引用的或BeanFactory的getBean()方法返回的bean类型依赖于代理接口,bean定义里的类总是AOP框架的ProxyFactoryBean。(支持多代理方法。) ProxyFactoryBean 的“interceptorNames”属性接受一个String列表。(必须用bean名称而不是bean引用,因为如果代理是一个原型那么新的有状态拦截器需要被创建。)列表里的名字可以是拦截器或者切入点(拦截器和何时该被应用的信息)。上面列表里的“target”值自动建立一个“调用拦截器”封装目标对象。它是工厂里的一个实现了代理接口的bean的名称。例子里的myTest bean能像工厂里的其他bean那样被使用。例如其他对象可以通过<ref>元素引用它,并且这些引用可以用Spring IoC设置。

有很多方法能更简单地建立代理,如果你不需要AOP框架的全部功能(例如不用XML而用Java 5.0的注释来驱动事务性代理,或者用一段简单的XML实现对一个Spring工厂里的许多bean应用一致的代理策略)。

还可以不用BeanFactory而用编程方法来构建AOP代理,虽然这种方法很少用:

TestBean target = new TestBean();

DebugInterceptor di = new DebugInterceptor();

MyInterceptor mi = new MyInterceptor();

ProxyFactory factory = new ProxyFactory(target);

factory.addInterceptor(0, di);

factory.addInterceptor(1, mi);

// 一个用来封装目标的“调用拦截器”被自动添加

ITestBean tb = (ITestBean) factory.getProxy();

我们相信最好把应用程序的装配从Java代码里拿出来,AOP也不例外。

使用AOP作为EJB(版本2或以上版本)的替代物来进行企业服务是的重要性正在加大。Spring很成功地展现了这个主张的价值。

MVC web框架

Spring包括一个强大且高度可配置的MVC web框架。

Spring的MVC模型尽管不是源自Struts,但和Struts的很相似。一个Spring的Controller和Struts的Action很像,它们都是多线程服务对象,只有一个实例代表所有客户端执行。但是,我们相信Spring MVC比起Struts有一些显著的优点。例如:

l         Spring在控制器、JavaBean模型和视图间提供清晰的划分。

l         Spring的MVC非常灵活。不像Struts那样强迫你的Action和Form对象有具体的继承(你只能用Java的继承),Spring的MVC完全基于接口。此外,几乎Spring MVC框架的每个部分都能通过插入你自己的接口来配置。当然,我们也提供了简单的类作为一个可选的实现。

l         Spring,像WebWork一样,提供拦截器和控制器,这使得提取处理多个请求的公共行为变得容易了。

l         Spring MVC是真正视图无关的。如果你不愿意你不会被强制使用JSP;你能用Velocity、XLST或其他视图技术。如果你想用自定义的视图机制(比如你自己的模板语言),你可以轻松实现Spring的View接口来整合它。

l         Spring的Controller和其他对象一样是通过IoC来配置的。这让它们易于测试,和其他Spring管理的对象漂亮地集成在一起。

l         因为没有强迫使用具体的继承和显式地依赖于调度器Servlet的控制器,Spring MVC的web层比起Struts的web层更易于测试。

l         Web层变成了业务对象层上的薄薄一层。这鼓励了好的习惯。Struts和其他专门的web框架让你自己实现你的业务对象;Spring为你的应用程序提供了一个完整的框架。

和Struts 1.1及更高版本一样,你能根据你的需要在Spring MVC应用程序中拥有多个调度器Servlet。

下面的范例演示了一个简单的Spring Controller如何访问同一个应用程序上下文中定一个业务对象。这个控制器在它的handleRequest()方法中执行了一个Google查询:

public class GoogleSearchController implements Controller {

private IGoogleSearchPort google;

private String googleKey;

public void setGoogle(IGoogleSearchPort google) {

this.google = google;

}

public void setGoogleKey(String googleKey) {

this.googleKey = googleKey;

}

public ModelAndView handleRequest(HttpServletRequest request,

HttpServletResponse response) throws ServletException, IOException {

String query = request.getParameter("query");

GoogleSearchResult result =

// Google属性定义忽略

// 使用Google业务对象

google.doGoogleSearch(this.googleKey, query,start, maxResults, filter, restrict,

safeSearch, lr, ie, oe);

return new ModelAndView("googleResults", "result", result);

}

}

这个代码的原型IGoogleSearchPort是一个GLUE web服务代理,由一个Spring FactoryBean返回。然而,Spring IoC将这个控制器从底层web服务库中隔离出来。也可以用一个简单的Java对象、测试桩、模拟对象或者用像下面要讨论的EJB代理来实现这个接口。这个控制器没有包含资源查找;除了支持它的web交互的必要代码外没有别的东西了。

Spring也提供了对数据绑定、表单、向导和更复杂的工作流的支持。马上要发表的这个系列文章中的一篇文章会详细讨论Spring MVC。

如果你的需求真的很复杂,你应该考虑Spring Web Flow,一个提供比传统web MVC框架更高层次web工作流抽象的强大的框架,它的架构师Keith Donald会在最近的TSS文章上讨论它的。

一个不错的Spring MVC框架的入门材料是Thomas Risberg的Spring MVC指南(http://www.springframework.org/docs/MVC-step-by-step/Spring-MVC-step-by-step.html)。也可以看《Web MVC with the Spring Framework》(http://www.springframework.org/docs/web_mvc.html)。

如果对你喜欢的MVC框架情有独钟,Spring的分层结构允许你使用MVC层以外的Spring的其他部分。我们有把Spring作为中间层管理和数据访问但在web层使用Struts、WebWork、Tapestry或JSF的用户。

实现EJB

如果你选择使用EJB,Spring可以令你从EJB实现和客户端对EJB的访问中获益。

将业务逻辑重构进EJB外观后的POJO中是一个被广泛认同的最佳实践。(和其它实践相比,这使得对业务逻辑的单元测试更加容易,因为EJB极其依赖容器且很难独立测试。) Spring为会话bean和消息驱动bean提供了好用的超类,通过自动加载包含在EJB Jar文件中的基于一个XML文档的BeanFactory来方便实现这点。

这意味着一个无状态的会话EJB可以这样来获得并使用一个协作者:

import org.springframework.ejb.support.AbstractStatelessSessionBean;

public class MyEJB extends AbstractStatelessSessionBean implements MyBusinessInterface {

private MyPOJO myPOJO;

protected void onEjbCreate() {

this.myPOJO = getBeanFactory().getBean("myPOJO");

}

public void myBusinessMethod() {

this.myPOJO.invokeMethod();

}

}

假定MyPOJO是一个接口,实现类(和任何它要求的诸如原始属性和进一步的协作者之类的配置)隐藏在XML bean工厂的定义中。

我们通过一个在标准ejb-jar.xml部署描述符中的名为ejb/BeanFactoryPath的环境变量定义告诉Spring到什么地方去加载XML文档,就像这样:

<session>

<ejb-name>myComponent</ejb-name>

<local-home>com.test.ejb.myEjbBeanLocalHome</local-home>

<local>com.mycom.MyComponentLocal</local>

<ejb-class>com.mycom.MyComponentEJB</ejb-class>

<session-type>Stateless</session-type>

<transaction-type>Container</transaction-type>

<env-entry>

<env-entry-name>ejb/BeanFactoryPath</env-entry-name>

<env-entry-type>java.lang.String</env-entry-type>

<env-entry-value>/myComponent-ejb-beans.xml</env-entry-value>

</env-entry>

</session>

myComponent-ejb-beans.xml文件会被从classpath中加载:在本例中,是在EJB Jar文件的根部。每个EJB能指定它自己的XML文件,所以这个机制能在每个EJB Jar文件中多次使用。

Spring超类实现了EJB的setSessionContext()和ejbCreate()一类的生命周期方法,让应用程序开发者只要选择性地实现Spring的onEjbCreate()方法就可以了。

当EJB 3.0蓝图公布后,我们会对使用Spring IoC容器在那个环境中提供更丰富的依赖注入语义提供支持。我们也同样会将JSR-220 实体/关系映射API作为一个被支持的数据访问API整合进来。

使用EJB

Spring除了让实现EJB更容易外,还让它的使用变得简单了。许多EJB应用程序使用服务定位器和业务代理模式。这比在客户代码中遍布JNDI查找好得多,但它们的一般实现存在明显的缺陷。例如:

l         依赖于服务定位器或业务代理单例的典型EJB调用代码很难测试。

l         在没有一个业务代理的服务定位器模式中,应用程序代码仍然要调用一个EJB home的create()方法,并处理可能的异常。因而它仍然绑定了EJB API和EJB编程模型的复杂性。

l         实现业务代理模式通常会导致明显的代码重复,我们不得不写很多方法来简单地调用同一个EJB方法。

为这些和其他一些原因,传统的EJB访问(就像Sun Adventure Builder和OTN J2EE虚拟大卖场里演示的)会降低生产力并带来显著的复杂度。

Spring通过引入少量代码业务代理在这方面先行一步。有了Spring你不再需要在手工编写的业务代理中写另外的服务定位器、JNDI查找或重复方法除非你加入了真正的价值。

举例来说,想象我们有一个使用本地EJB的web控制器。我们会遵循最佳实践并使用EJB业务方法接口模式,所以EJB本地接口要扩展一个非EJB特有的业务方法接口。(这样做的一个主要原因是确保本地接口和bean实现类的自动同步。)我们称这个业务方法接口为MyComponent。当然我们也需要实现本地home接口并提供一个实现了SessionBean和MyComponent业务方法接口的bean实现类。

有了Spring EJB访问,我们为挂接我们的web层控制器和EJB实现所需的唯一的Java编码就是暴露控制器上的一个MyComponent的setter方法。这会像一个实例变量那样保存引用:

private MyComponent myComponent;

public void setMyComponent(MyComponent myComponent) {

this.myComponent = myComponent;

}

随后我们就能在任何业务方法中使用这个实例变量了。

Spring通过XML bean定义项来自动完成剩下的工作。LocalStatelessSessionProxyFactoryBean是一个能被任何EJB使用的通用工厂bean。它创建的对象能自动被Spring转换为MyComponent类型。

<bean id="myComponent"

class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">

<property name="jndiName" value="myComponent" />

<property name="businessInterface" value="com.mycom.MyComponent" />

</bean>

<bean id="myController" class = "com.mycom.myController">

<property name="myComponent" ref="myComponent" />

</bean>

幕后发生了很多魔法般的事情,出于Spring AOP框架的谦虚,你没有被强迫使用AOP概念来享受这结果。“myComponent”bean定义建立了一个实现了业务方法接口的EJB代理。EJB本地home在启动时被缓存,所以一般只需要一次JNDI查找。(也有对失败时重试的支持,所以一次EJB重部署不会导致客户端失败。)EJB每次被调用时,代理调用本地EJB的create()方法并调用EJB的相应业务方法。

myController bean定义将控制类的myController属性设置到这个代理。

这个EJB访问机制对应用程序代码进行了大量简化:

l         Web层代码没有了对使用EJB的依赖。如果我们想要用一个POJO、模拟对象或其他测试桩来代替这个EJB引用,我们可以简单地改变myComponent bean定义而不用修改一行Java代码。

l         我们没有必要写一行JNDI查找代码或其他EJB组装代码来作为我们应用程序的一部分。

我们还能通过相似的org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean工厂bean将相同的方法运用于远程EJB。但是,要把一个远程EJB业务方法接口的RemoteExceptions隐藏起来却是很棘手的。(如果你希望提供一个匹配EJB远程接口但方法签名中没有“throws RemoteException”语句的客户端服务接口,Spring确实能让你办到这点。)

测试

正如你所注意到的,我和其他Spring的开发者都是全面单元测试的忠实拥护者。我们相信框架经过彻底的单元测试是很重要的,而且框架设计的一个主要目的应该是使构建于框架之上的应用程序应该是易于进行单元测试的。

Spring自己有一个出色的单元测试包。我们发现这个项目的测试优先带来的好处是实实在在的。例如,它使得一个国际化分布式开发团队工作极富效率,而且用户们评论CVS快照往往很稳定,用起来很安全。

我们相信构建在Spring上的应用程序测试很方便,有以下原因:

l         IoC易于进行单元测试。

l         应用程序不包含那些一般很难测试的直接使用例如JNDI之类的J2EE服务的代码。

l         Spring的bean工厂或上下文能在容器外建立。

在容器外建立一个Spring的bean工厂为开发过程提供了有趣的选择权。在几个使用Spring的web应用程序项目中,工作始于定义业务接口和在一个web容器外进行它们的实现的集成测试。只有在业务功能彻底完成后,再在上面加薄薄一层提供web接口。

从Spring 1.1开始,Spring提供了对在部署环紧外进行集成测试的强大且独特的支持。这并不是有意要作为单元测试或针对部署环境的测试的代替品。然而,这可以显著提高生产力。

org.springframework.test包提供了使用一个Spring容器而不用依赖于一个应用服务器或其他部署环境进行集成测试的很有价值的超类。这样的测试可以在Junit里运行(甚至是在一个IDE里)而不用特殊的部署步骤。他们运行起来会比单元测试稍慢,但比Cactus测试或靠部署到一个应用服务器的测试要快得多。通常很可能在几秒钟而不是几分钟或几小时内要运行好几百个针对一个开发数据库的测试(一般不是一个嵌入式数据库,而是生产中用的数据库产品)。这样的测试能快速检验你Spring上下文和使用JDBC或ORM工具的数据访问的正确装配,比如SQL语句的正确性。举例来说,你可以测试你的DAO实现类。

org.springframework.test包中实现的功能包括:

l         通过依赖注入实现JUnit测试用例移植的能力。这使测试时重用Spring XML配置和消除针对测试的自定义设置代码成为可能。

l         在测试用例之间缓存容器配置的能力,这在用到那些比如JDBC连接池或Hibernate的SessionFactory之类的初始化很慢的资源的地方大大提高了的性能。

l         默认情况下为每个测试方法建立事务并在测试结束时回滚的基础结构。这将允许测试进行各种各样的数据存取而不用担心影响其它测试的环境。从我在几个复杂项目中使用这一功能的的经验看来,用这样一个基于回滚的方法带来的生产力和速度的提升是很明显的。

谁在使用Spring?

有很多应用程序产品使用Spring。用户包括投资和零售的银行组织、知名互联网公司、全球性顾问公司、学院机构、政府机关、防卫设备承包商、几家航空公司和科研组织(包括CERN)。

很多用户使用全部的Spring,但有些使用单独的组件。例如,一些用户从使用我们的JDBC或其它数据存取功能开始。

发展历程

自从这篇文章的第一版在2003年10月发表以来,Spring经历了1.0最终发布版(2004年3月)到1.1版(2004年9月)到1.2最终版(2005年5月)的过程。我们相信这样一个哲学“早发布,勤发布”,所以维护性发布和小的改进一般4到6周发布一次。

从那时起的改进包括:

l         引入一个支持包括RMI和多种web服务协议在内的多协议远程框架。

l         支持方法注入和其他的IoC容器增强,例如管理从静态或实例工厂方法的调用中获得的对象的能力。

l         集成更多数据访问技术,在最近的1.2版中包括TopLink、 Hibernate 版本3。

l         支持用Java 5.0注释配置的声明性事务管理,不需要用XML元数据来识别事务性方法(1.2)。

l         支持Spring所管理对象的JMX管理(1.2)。

l         集成了Jasper报告、Quartz计划和AspectJ。

l         将JSF作为一种web层技术集成进来。

我们打算继续快速的革新和增强。下一个主要版本会是1.3。计划中的增强包括:

l         XML配置增强(计划在1.3版内),这将允许用自定义XML标签通过定义一个或多个单一的、合法的标签来扩展基本的Spring配置格式。这不仅可能显著简化典型配置并减少配置错误,还会成为基于Spring的第三方产品的开发者的理想选择。

l         把Spring Web Flow集成到Spring的核心中(计划在1.3版内)。

l         支持运行时应用程序动态重配置。

l         支持用Java外的语言编写的应用程序对象,比如Groovy、Jython或其他运行于Java平台上的脚本语言。这些对象能得益于Spring IoC容器的完整服务和在脚本改变时动态重新装载而不影响由IoC容器引用到它们的对象。

作为一个敏捷项目,Spring主要由用户需求驱动。所以我们不会开发没有任用的功能,我们也会仔细倾听来自我们用户社群的声音。

Spring Modules是一个由Interface 21的Rob Harrop领导的相关项目,它将Spring平台扩展到那些Spring核心没有必要完全支持的,但仍然对很多用户有价值的领域。这个项目也作为一个孵化器,所以其中的一些功能会最终集成到Spring核心中。Spring Modules目前包括如下领域:与Lucene搜索引擎和OSWorkflow工作流引擎的集成、一个基于AOP的声明性缓存解决方案、与Commons Validator框架的集成。

有趣的是,尽管这篇文章的第一版发表在Spring 1.0最终版发布的六个月前,几乎所有的示范代码和配置仍然能不经改动地在今天的1.2版上运行。我们为自己在向下兼容方面的骄人记录感到自豪。这也展现了依赖注入和AOP实现非侵入性API的能力,还表现了我们尽心尽力为社区提供一个能够运行重大应用程序的稳定框架的严谨。

总结

Spring是一个解决了许多常见J2EE问题的强大框架。许多Spring的功能也可以被运用于很多超越经典J2EE的Java环境中。

Spring提供了一种以一致方法管理业务对象的方法,并鼓励好的编程习惯,例如针对接口而不是类编程。Spring的架构基础是一个使用JavaBean属性的控制反转容器。可是,这只是Spring全貌的一部分:Spring在将IoC容器作为所有架构层的完整解决方案的基本构建块方面是独一无二的。

Spring提供了一个独特的数据访问抽象,包括一个大大改善生产力并降低错误可能的简单而有效的JDBC框架。Spring的数据访问架构还集成了TopLink、Hibernate、JDO和其他实体/关系映射解决方案。

Spring提供了唯一的事务管理抽象,这能够在类似JTA或JDBC这样的不同底层事务技术上使用一致的编程模型。

Spring提供了一个用标准Java写的AOP框架,它提供了声明性事务管理和其他用于POJO的企业服务或者(如果你希望)也能用于实现你自己的自定义方面。这个框架强大到足以使很多应用程序在享受传统的EJB相关的关键服务的同时放弃EJB的复杂性。

Spring还提供了一个可整合到整个IoC容器中的强大且灵活的MVC web框架。

更多信息

需要更多的关于Spring的信息请参阅:

l         Interface21提供的一个核心Spring培训课程——http://www.springframework.com/training。

l         《Expert One-on-One J2EE Design and Development》(Rod Johnson, Wrox, 2002)。尽管Spring在此书出版后有了很明显的改进,但它仍然是理解Spring动机的好地方。

l         《J2EE without EJB》(Rod Johnson与Juergen Hoeller合著Wrox, 2004)。《Expert One-on-One J2EE Design and Development》的后续作品,讨论Spring和它的轻量级容器架构的基本原理。

l         《Spring参考手册》。Spring 1.2的打印版本又超过240页。Spring还带了几个展示最佳实践并可用作你自己的应用程序模板的范例。

l         《Pro Spring》:由核心开发者Rob Harrop深入讨论Spring。

l         《Spring: A Developer’s Notebook》:由Bruce Tate和Justin Gehtland所著的入门。

l         Spring框架主页:http://www.springframework.org/,这里包括Javadoc和几个教程。

l         Sourceforge上的论坛和下载。

l         Spring开发者邮件列表。

我们为对待论坛和邮件列表中问题的认真态度和出色的回复率感到自豪。我们欢迎您早日加入我们的社区。

关于作者

Rod Johnson拥有差不多十年作为Java开发者和架构师的经验,并从J2EE平台出现后就在其上进行开发。他是畅销书《Expert One-on-One J2EE Design and Development》(Wrox, 2002)和《J2EE without EJB》(Wrox, 2004与Juergen Hoeller合著)的作者,也参与过其它J2EE著作的编写。Rod参与了两个Java标准委员会并经常在大会发言。他是Interface21的CEO,这是一家国际化咨询公司,领导Spring框架开发,提供Spring框架和J2EE方面的专业服务。

Introduction to the Spring Framework By Rod Johnson(完整中文翻译版)相关推荐

  1. spring (由Rod Johnson创建的一个开源框架)

    你可能正在想"Spring不过是另外一个的framework".当已经有许多开放源代码(和专有)J2EEframework时,我们为什么还需要Spring Framework? S ...

  2. Spring之父Rod Johnson

    Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位.更令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位.有着相当丰富的C/C++技术背景的Rod早在1996年就开始了对Java服 ...

  3. Spring Framework 开发参考手册中文(在线HTML)

    '原文:http://blog.csdn.net/zfrong/article/details/3971722 Spring Framework 开发参考手册中文(在线HTML) Spring Fra ...

  4. Spring Framework(框架)整体架构

    原文链接:https://blog.csdn.net/wd2014610/article/details/80061808 Spring 在这个Spring框架大行其道的软件开发世界里,尚有很多工程师 ...

  5. Spring Framework 源码解析课程大纲

    首先明确我们读Spring Framework的源码的目的是什么? 是为了显示自己很牛X么? Spring源码阅读的难度在于其呢内容庞大,并不是里面有很复杂的算法,需要高智商才能理解.所以你搞懂了Sp ...

  6. Spring 框架(Spring Framework)使用详解

    概述 Spring 体系概述 Spring 是于2003年兴起的一个 full-stack 轻量级的 Java 开源框架,由 Rod Johnson 创建,使用 Spring 可以更快.更轻松.更安全 ...

  7. SpringSource创始人Rod Johnson执掌十年后离开

    长期以来一直有传言,但今天我们得到了证实– Spring Framework的创始人 Rod Johnson即将离开VMware. 约翰逊(Johnson)在SpringSource博客上发表了一个似 ...

  8. Spring Framework 基础入门

    参考资料 spring官网 https://spring.io/ https://www.bilibili.com/video/BV1Fi4y1S7ix?p=4&vd_source=c5810 ...

  9. Spring Framework(框架)整体架构(不知道就有些搞笑了哈)

    Spring 在这个Spring框架大行其道的软件开发世界里,尚有很多工程师天天在用,但是从来不会去思考下,Spring框架的整体架构到底是什么样子的啊. 一.首先通过维基百科看看什么是Spring框 ...

最新文章

  1. hdoj-2039-三角形
  2. Problem 63 何时该用glDrawTexiOES?
  3. 实战并发编程 - 01多线程读写同一共享变量的线程安全问题深入剖析
  4. leetcode 994. Rotting Oranges | 994. 腐烂的橘子(BFS)
  5. 基本包装类和System类
  6. 英语笔记:写作:Nothing succeeds without a strong will
  7. 图灵奖得主 Bengio:深度学习不会被取代!
  8. idea 报错is already defined as class
  9. Python分布式爬虫打造搜索引擎(四)
  10. Delphi入门教程
  11. 打开CAD图纸转换成dwf格式的文件
  12. IDEA 打包忽略测试
  13. 给树莓派安装手柄驱动
  14. 基于halcon的颜色识别---杜邦线识别
  15. Android Studio 实现播放本地/网络视频
  16. 自问自答(JavaScript篇)
  17. dataType和contentType的区别
  18. 用iOS/Android实现家庭自动化远程控制
  19. 【1+X Web前端等级考证 】 | Web前端开发中级理论 (附答案)
  20. Android笔记之天气预报接口

热门文章

  1. springcloud中Gateway的限流熔断机制!
  2. 软件项目开发业界通常报价和收费方式
  3. go语言的魔幻旅程20-io包
  4. Guido 转身离去,Python 何去何从?
  5. 笔记本被清除的文件怎么恢复
  6. 大一计算机在线考试,(word)大一计算机考试试题.doc
  7. ps cs5 gif 动画 分解
  8. centos7 日志定期清理及修改系统日志保留时间
  9. 嵌入式PC104架构开发系统板CE9200-3 (NEW)
  10. 2023全国特种作业操作证高处安装、维护、拆除模拟试卷一[安考星]