Introducing to Spring Framework

Introducing to Spring Framework

作者:Rod Johnson
译者:yanger,taowen
校对:taowen

关于Spring Framework,今年夏天你可能已经听见很多的议论。在本文中,我将试图解释Spring能完成什么,和我怎么会认为它能帮助你开发J2EE应用程序。

又来一个framework?

你可能正在想“不过是另外一个的framework”。当已经有许多开放源代码(和专有) J2EE framework时,为什么你还要耐下心子读这篇文章或去下载Spring Framework?

我相信Spring是独特的,有几个原因:


  • 它关注的领域是其他许多流行的Framework未曾关注的。Spring要提供的是一种管理你的业务对象的方法。

    Spring既是全面的又是模块化的。Spring有分层的体系结构,这意味着你能选择仅仅使用它任何一个独立的部分,而它的架构又是内部一致。因此你能从你的学习中,得到最大的价值。例如,你可能选择仅仅使用Spring来简单化JDBC的使用,或用来管理所有的业务对象。

    它的设计从一开始就是要帮助你编写易于测试的代码。Spring是使用测试驱动开发的工程的理想框架。

Spring不会给你的工程添加对其他的框架依赖。Spring也许称得上是个一站式解决方案,提供了一个典型应用所需要的大部分基础架构。它还涉及到了其他framework没有考虑到的内容。

尽管它仅仅是一个从2003年2月才开始的开源项目,但Spring有深厚的历史根基。这个开源工程是起源自我在2002年晚些时候出版的《Expert One-on-One J2EE设计与开发》书中的基础性代码。这本书展示了Spring背后的基础性架构思想。然而,对这个基础架构的概念可以追溯到2000年的早些时候,并且反映了我为一系列商业工程开发基础结构的成功经验。

2003年1月,Spring已经落户于SourceForge上了。现在有10个开发人员,其中6个是高度投入的积极分子。

Spring架构上的好处

在我们进入细节之前,让我们来看看Spring能够给工程带来的种种好处:


  • Spring能有效地组织你的中间层对象,不管你是否选择使用了EJB。如果你仅仅使用了Struts或其他为J2EE的 API特制的framework,Spring致力于解决剩下的问题。

    Spring能消除在许多工程中常见的对Singleton的过多使用。根据我的经验,这是一个很大的问题,它降低了系统的可测试性和面向对象的程度。

    通过一种在不同应用程序和项目间一致的方法来处理配置文件,Spring能消除各种各样自定义格式的属性文件的需要。曾经对某个类要寻找的是哪个魔法般的属性项或系统属性感到不解,为此不得不去读Javadoc甚至源编码?有了Spring,你仅仅需要看看类的JavaBean属性。Inversion of Control的使用(在下面讨论)帮助完成了这种简化。

    通过把对接口编程而不是对类编程的代价几乎减少到没有,Spring能够促进养成好的编程习惯。

    Spring被设计为让使用它创建的应用尽可能少的依赖于他的APIs。在Spring应用中的大多数业务对象没有依赖于Spring。

    使用Spring构建的应用程序易于单元测试。

    Spring能使EJB的使用成为一个实现选择,而不是应用架构的必然选择。你能选择用POJOs或local EJBs来实现业务接口,却不会影响调用代码。

    Spring帮助你解决许多问题而无需使用EJB。Spring能提供一种EJB的替换物,它们适用于许多web应用。例如,Spring能使用AOP提供声明性事务管理而不通过EJB容器,如果你仅仅需要与单个数据库打交道,甚至不需要一个JTA实现。

    Spring为数据存取提供了一个一致的框架,不论是使用的是JDBC还是O/R mapping产品(如Hibernate)。

Spring确实使你能通过最简单可行的解决办法来解决你的问题。而这是有有很大价值的。

Spring做了些什么?

Spring提供许多功能,在此我将依次快速地展示其各个主要方面。

任务描述

首先,让我们明确Spring范围。尽管Spring覆盖了许多方面,但我们对它应该涉什么,什么不应该涉及有清楚的认识。

Spring的主要目的是使J2EE易用和促进好编程习惯。

Spring不重新轮子。因此,你发现在Spring中没有logging,没有连接池,没有分布式事务调度。所有这些东西均有开源项目提供(例如我们用于处理所有日志输出的Commons Logging以及Commons DBCP),或由你的应用程序服务器提供了。出于同样的的原因,我们没有提供O/R mapping层。对于这个问题已经有了像Hibernate和JDO这样的优秀解决方案。

Spring的目标就是让已有的技术更加易用。例如,尽管我们没有底层事务协调处理,但我们提供了一个抽象层覆盖了JTA或任何其他的事务策略。

Spring没有直接和其他的开源项目竞争,除非我们感到我们能提供新的一些东西。例如,象许多开发人员一样,我们从来没有对Struts感到高兴过,并且觉得到在MVC web framework中还有改进的余地。在某些领域,例如轻量级的IoC容器和AOP框架,Spring确实有直接的竞争,但是在这些领域还没有已经较为流行的解决方案。(Spring在这些领域是开路先锋。)

Spring也得益于内在的一致性。所有的开发者都在唱同样的的赞歌,基础想法依然与Expert One-on-One J2EE设计与开发中提出的差不多。 并且我们已经能够在多个领域中使用一些中心的概念,例如Inversion of Control。

Spring在应用服务器之间是可移植的。当然保证可移植性总是一种挑战,但是我们避免使用任何平台特有或非标准的东西,并且支持在WebLogic,Tomcat,Resin,JBoss,WebSphere和其他的应用服务器上的用户。

Inversion of Control 容器

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

下一个层面高一些的抽象是"Bean Factory"。一个Spring bean factory 是一个通用的Factory,它使对象能够按名称获取,并且能管理对象之间的关系。

Bean factories 支持两种模式的对象:


  • Singleton:在此模式中,有一个具有特定名称的共享对象实例,它在查找时被获取。这是默认的,而且是最为经常使用的。它对于无状态对象是一种理想的模式。

    Prototype:在此模式中,每次获取将创建一个独立的对象。例如,这可以被用于让用户拥有他们自己的对象。


由于 org.springframwork.beans.factory.BeanFactory是一个简单的接口,它能被大量底层存储方法实现。你能够方便地实现你自己的BeanFactory,尽管很少用户需要这么做。最为常用的BeanFactory定义是:


  • XmlBeanFactory: 可解析简单直观的定义类和命名对象属性的XML结构。 我们提供了一个DTD来使编写更容易。

    ListableBeanFactoryImpl:提供了解析存放在属性文件中的bean定义的能力,并且可通过编程创建BeanFactories。

每个bean定义可能是一个POJO(通过类名和JavaBean初始属性定义),或是一个FactoryBean。FactoryBean接口添加了一个间接层。通常,这用于创建使用AOP或其他方法的代理对象:例如,添加声明性事务管理的代理。(这在概念上和EJB的interception相似,但实现得更简单。)

BeanFactories能在一个层次结构中选择性地参与,继承ancestor(祖先)的定义。这使得在整个应用中公共配置的共享成为可能,虽然个别资源,如controller servlets,还拥有他们自己的独立的对象集合。

这种使用JavaBeans的动机在《Expert One-on-One J2EE Design and Development》的第四章中有描述,在TheServerSide网站上的有免费的PDF版本(http://www.theserverside.com/resources/article.jsp?l=RodJohnsonInterview)。

通过BeanFactory概念,Spring成为一个Inversion of Control的容器。(我不怎么喜欢container这个词,因为它使人联想到重量级容器,如EJB容器。Spring的BeanFactory是一个可通过一行代码创建的容器,并且不需要特殊的部署步骤。)

Inversion of Control背后的概念经常表述为Hollywood原则的:“Don’t call me, I’ll call you。” IoC将控制创建的职责搬进了框架中,并把它从应用代码脱离开来。涉及到配置的地方,意思是说在传统的容器体系结构中,如EJB,一个组件可以调用容器并问“我需要它给我做工作的对象X在哪里?”;使用IoC容器则只需指出组件需要X对象,在运行时容器会提供给它。容器是通过查看方法的参数表(例如JavaBean的属性)做到的,也可能根据配置数据如XML。

IoC有几个重要的好处,例如:


  • 因为组件不需要在运行时间寻找合作者,所以他们可以更简单的编写和维护。在Spring版的IoC里,组件通过暴露JavaBean的setter方法表达他们依赖的其他组件。这相当于EJB通过JNDI来查找,EJB查找需要开发人员编写代码。

    同样原因,应用代码更容易测试。JavaBean属性是简单的,属于Java核心的,并且是容易测试的:仅编写一个自包含的Junit测试方法用来创建对象和设置相关属性即可。

    一个好的IoC实现保留了强类型。如果你需要使用一个通用的factory来寻找合作者,你必须通过类型转换将返回结果转变为想要的类型。这不是一个大不了的问题,但是不雅观。使用IoC,你在你的代码中表达了强类型依赖,框架将负责类型转换。这意味着在框架配置应用时,类型不匹配将导致错误;在你的代码中,你无需担心类型转换异常。

    大部分业务对象不依赖于IoC容器的APIs。这使得很容易使用遗留下来的代码,且很容易的使用对象无论在容器内或不在容器内。例如,Spring用户经常配置Jakarta Commons DBCP数据源为一个Spring bean:不需要些任何定制代码去做这件事。我们说一个IoC容器不是侵入性的:使用它并不会使你的代码依赖于它的APIs。任何JavaBean在Spring bean factory中都能成为一个组件。

最后应该强调的是,IoC 不同于传统的容器的体系结构,如EJB,应用代码最小程度地依靠于容器。这意味着你的业务对象可以潜在的被运行在不同的IoC 框架上——或者在任何框架之外——不需要任何代码的改动。

以我和其他Spring用户的经验来说,再怎么强调IoC给应用程序代码带来的好处也不为过。

IoC不是一个新概念,但是它在J2EE团体里面刚刚到达黄金时间。 有一些可供选择的IoC 容器: 例如 Apache Avalon, PicoContainer 和 HiveMind。Avalon 从没怎么流行,尽管它很强大而且有很长的历史。Avalon相当的重和复杂,并且看起来比新的IoC解决方案更具侵入性。 PicoContainer是一个轻量级而且更强调通过构造函数表达依赖性而不是JavaBean 属性。 与Spring不同,它的设计允许每个类型一个对象的定义(可能是因为它拒绝任何Java代码外的元数据导致的局限性)。在Spring, PicoContainer 和其他 IoC frameworks之间做比较,可参看文章Spring网站上的"The Spring Framework - A Lightweight Container"位于http://www.springframework.org/docs/lightweight_container.html。这个页面里面包含了PicoContainer站点的链接 。

Spring BeanFactories 是非常轻量级的。用户已经成功地将他们应用在applets和单独的Swing应用中。(它们也很好地工作在EJB容器中。) 没有特殊的部署步骤和察觉得到的启动时间。这个能力表明一个容器在应用的任何层面几乎立即可以发挥非常大的价值。

Spring BeanFactory 概念贯穿于Spring始终, 而且是Spring如此内在一致的关键原因。在IoC容器中,Spring也是唯一的,它使用IoC作为基础概念贯穿于整个功能丰富的框架。

对应用开发人员,最重要的是,一个或多个BeanFactory提供了一个定义明确的业务对象层。这类似于local session bean层,但比它更简单。与EJBs不同,在这个层中的对象可能是相关的,并且他们的关系被拥有它们的factory管理。有一个定义明确的业务对象层对于成功的体系结构是非常重要的。

Spring ApplicationContext 是BeanFactory的子接口,为下列东西提供支持:


  • 信息查找,支持着国际化

    事件机制,允许发布应用对象以及可选的注册以接收到事件

    可移植的文件和资源访问

XmlBeanFactory 例子

Spring用户通常在XML的“bean定义”文件中配置他们的应用。Spring的XML bean定义文档的根是<beans> 元素。该元素包含一个或多个 <bean>定义。我们一般给每个bean定义的指定类和属性。我们还必须指定ID作为标识,这将成为在代码中使用该bean的名字。

让我们来看一个简单的例子,它配置了三个应用程序对象,之间的关系在J2EE应用中常常能够看到:


  • J2EE DataSource

    使用DataSource的DAO

    在处理过程中使用DAO的业务对象

在下面的例子中,我们使用一个来自Jakarta Commons DBCP项目的BasicDataSource。这个class(和其他许多已有的class一样)可以简单地被应用在Spring bean factory中,只要它提供了JavaBean格式的配置。需要在shutdown时被调用的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</value></property>
    <property name="url"><value>jdbc:mysql://localhost:3306/mydb</value></property>
    <property name="username"><value>root</value></property>
  </bean>

BasicDataSource中我们感兴趣的所有属性都是String类型的,因此我们用<value>元素来指定他们的值。如果必要的话,Spring使用标准的 JavaBean属性编辑器机制来把String转换为其他的类型。

现在,我们定义DAO,它有一个对DataSource的bean引用。Bean间关系通过<ref>元素来指定:

代码:
<bean id="exampleDataAccessObject"
      class="example.ExampleDataAccessObject">
    <property name="dataSource"><ref bean="myDataSource"/></property>
  </bean>

The business object has a reference to the DAO, and an int property (exampleParam):
<bean id="exampleBusinessObject"
      class="example.ExampleBusinessObject">
    <property name="dataAccessObject"><ref bean="exampleDataAccessObject"/></property>
    <property name="exampleParam"><value>10</value></property>
  </bean>

</beans>

对象间的关系一般在配置中明确地设置,象这个例子一样。我们认为这样做是件好事情。然而Spring还提供了我们称做"autowire"的支持, 一个la PicoContainer,其中它指出了bean间的依赖关系。这样做的局限性——PicoContainer也是如此——是如果有一个特殊类型的多个Bean,要确定那个类型所依赖的是哪个实例是不可能。好的方面是,不满足的依赖可以在factory初始化后被捕获到。(Spring 也为显式的配置提供了一种可选的依赖检查,它可以完成这个目的)

在上面的例子中,如果我们不想显式的编写他们的关系,可使用如下的autowire特性:

代码:
<bean id="exampleBusinessObject"
   class="example.ExampleBusinessObject"
   autowire="byType">

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


使用这个特性,Spring会找出exampleBusinessObject的dataSource属性应该被设置为在当前BeanFactory中找到的DataSource实现。在当前的BeanFactory中,如果所需要类型的bean不存在或多于一个,将产生一个错误。我们依然要设置exampleParam属性,因为它不是一个引用。

Autowire支持和依赖检查刚刚加入CVS并将在Spring 1.0 M2(到10/20,2003)中提供。本文中所讨论的所有其他特性都包含在当前1.0 M1版本中。

把管理从Java代码中移出来比硬编码有很大的好处,因为这样可以只改变XML文件而无需改变一行Java代码。例如,我们可以简单地改变myDataSource的bean定义引用不同的bean class以使用别的连接池,或者一个用于测试的数据源。 XML节变成另一种,我们可以用Spring的JNDI location FactoryBean从应用服务器获取一个数据源。

现在让我们来看看例子中业务对象的java 代码。注意下面列出的代码中没有对Spring的依赖。不像EJB容器,Spring BeanFactory不具有侵入性:在应用对象里面你通常不需要对Spring的存在硬编码。

代码:
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() {
      // do stuff using dao
   }
}

注意那些property setter,它们对应于bean定义文档中的XML引用。这些将在对象被使用之前由Spring调用。

这些应用程序的bean不需要依赖于Spring:他们不需要实现任何Spring的接口或者继承Spring的类。他们只需要遵守JavaBeans的命名习惯。在Spring 应用环境之外重用它们是非常简单的,例如,在一个测试环境中。只需要用它们的缺省构造函数实例化它们,并且通过调用setDataSource()和setExampleParam()手工设置它的属性。如果你想以一行代码支持程序化的创建,只要你有一个无参数的构造器,你就可以自由定义其他需要多个属性的构造函数。

注意在业务接口中没有声明将会一起使用的JavaBean属性。 他们是一个实现细节。我们可以“插入”带有不同bean属性的不同的实现类而不影响连接着的对象或者调用的代码。

当然,Spring XML bean factories 有更多的功能没有在这里描述,但是,应当让你对基本使用有了一些感觉。以及,简单的属性,有JavaBean属性编辑器的属性,Spring可以自动处理lists,maps和java.util.Properties。

Bean factories 和application contexts 通常和J2EE server定义的一个范围相关联,例如:


  • Servlet context.:在spring 的MVC 框架里, 每一个包含common objects的web 应用都定义有一个应用程序的context。Spring提供了通过listener或者servlet实例化这样的context的能力而不需要依赖于Spring 的MVC 框架,因而它也可以用于Struts,WebWork 或者其他的web框架之中。

    A Servlet:在Spring MVC 框架里每一个servlet控制器都有它自己的应用程序context,派生于根(全应用程序范围的)应用程序context。在Struts或者其他MVC框架中实现这些也很容意。

    EJB:Spring 为EJB提供方便的超类,它们简化了EJB的创建并且提供了一个从EJB Jar 文件中的XML文档载入的BeanFactory。

这些J2EE规范提供的hook通常避免了使用Singleton来创造一个bean factory。

然而,如果我们愿意的话可以用代码创建一个BeanFactory,虽然是没有什么意义的。例如,我们在以下三行代码中可以创建bean factory并且得到一个业务对象的引用:

代码:
InputStream is = getClass().getResourceAsStream("myFile.xml");
XmlBeanFactory bf = new XmlBeanFactory(is);
MyBusinessObject mbo = (MyBusinessObject) bf.getBean("exampleBusinessObject");



这段代码将能工作在一个应用服务器之外:甚至不依赖J2EE,因为Spring 的IoC容器是纯java的。

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

数据访问是Spring 的另一个闪光点。

JDBC 提供了还算不错的数据库抽象,但是需要用痛苦的API。这些问题包括:

  • 需要冗长的错误处理代码来确保ResultSets,Statements以及(最重要的)Connections在使用后关闭。这意味着对JDBC的正确使用可以快速地导致大量的代码量。它还是一个常见的错误来源。Connection leak可以在有负载的情况下快速宕掉应用程序。

    SQLException相对来说不能说明任何问题。JDBC不提供异常的层次,而是用抛出SQLException来响应所有的错误。找出到底哪里出错了——例如,问题是死锁还是无效的SQL?——要去检查SQLState或错误代码。这意味着这些值在数据库之间是变化的。


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

  • 提供API,把冗长乏味和容易出错的异常处理从程序代码移到框架之中。框架处理所有的异常处理;程序代码能够集中精力于编写恰当的SQL和提取结果上。

    为你本要处理SQLException程序代码提供有意义的异常层次。当Spring第一次从数据源取得一个连接时,它检查元数据以确定数据库。它使用这些信息把SQLException映射为自己从org.springframework.dao.DataAccessException派生下来的类层次中正确的异常。因而你的代码可以与有意义的异常打交道,并且不需要为私有的SQLState或者错误码担心。Spring的数据访问异常不是JDBC特有的,因而你的DAO并不一定会因为它们可能抛出的异常而绑死在JDBC上。


Spring提供两层JDBC API。第一个时,在org.springframework.jdbc.core包中,使用回调机制移动控制权——并且因而把错误处理和连接获取和释放——从程序的代码移到了框架之中。这是一种不同的Inversion of Control,但是和用于配置管理的几乎有同等重要的意义。

Spring使用类似的回调机制关注其他包含特殊获取和清理资源步骤的API,例如JDO(获取和释放是由PersistenceManager完成的),事务管理(使用JTA)和JNDI。Spring中完成这些回调的类被称作template。

例如,Spring的JdbcTemplate对象能够用于执行SQL查询并且在如下的列表中保存结果:

代码:
JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
   new RowCallbackHandler() {
      public void processRow(ResultSet rs) throws SQLException {
         names.add(rs.getString(1));
      }
   });


注意回调中的程序代码是能够自由抛出SQLException的:Spring将会捕捉到这些异常并且用自己的类层次重新抛出。程序的开发者可以选择哪个异常,如果有的话,被捕捉然后处理。

JdbcTemplate提供许多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起标准JDBC来说性能损失非常小,甚至在当应用中需要的结果集数量很大的时候。

在org.springframework.jdbc.object包中是对JDBC的更高层次的抽象。这是建立在核心的JDBC回调功能基础纸上的,但是提供了一个能够对RDBMS操作——无论是查询,更新或者是存储过程——使用Java对象来建模的API。这个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();
   }

   // Map a result set row to a Java object
   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) {
      // Use superclass convenience method to provide strong typing
      return (User) findObject(id);
   }
}

这个类可以在下面用上:
代码:

User user = userQuery.findUser(25);

这样的对象经常可以用作DAO的inner class。它们是线程安全的,除非子类作了一些超出常规的事情。

在org.springframework.jdbc.object包中另一个重要的类是StoredProcedure类。Spring让存储过程通过带有一个业务方法的Java类进行代理。如果你喜欢的话,你可以定义一个存储过程实现的接口,意味着你能够把你的程序代码从对存储过程的依赖中完全解脱出来。

Spring数据访问异常层次是基于unchecked(运行时)exception的。在几个工程中使用了Spring之后,我越来越确信这个决定是正确的。

数据访问异常一般是不可恢复的。例如,如果我们不能链接到数据库,某个业务对象很有可能就不能完成要解决的问题了。一个可能的异常是optimistic locking violation,但是不是所有的程序使用optimistic locking。强制编写捕捉其无法有效处理的致命的异常通常是不好的。让它们传播到上层的handler,比如servlet或者EJB 容器通常更加合适。所有的Spring对象访问异常都是DataAccessException的子类,因而如果我们确实选择了捕捉所有的Spring数据访问异常,我们可以很容易做到这点。

注意如果我们确实需要从unchecked数据访问异常中恢复,我们仍然可以这么做。我们可以编写代码仅仅处理可恢复的情况。例如,如果我们认为只有optimistic locking violation是可恢复的,我们可以在Spring的DAO中如下这么写:

代码:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}

如果Spring的数据访问异常是checked的,我们需要编写如下的代码。注意我们还是可以选择这么写:
代码:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}
catch (DataAccessException ex) {
   // Fatal; just rethrow it
}

第一个例子的潜在缺陷是——编译器不能强制处理可能的可恢复的异常——这对于第二个也是如此。因为我们被强制捕捉base exception(DataAccessException),编译器不会强制对子类(OptimisticLockingFailureException)的检查。因而编译器可能强制我们编写处理不可恢复问题的代码,但是对于强制我们处理可恢复的问题并未有任何帮助。

Spring对于数据访问异常的unchecked使用和许多——可能是大多数——成功的持久化框架是一致的。(确实,它部分是受到JDO的影响。)JDBC是少数几个使用checked exception的数据访问API之一。例如TopLink和JDO大量使用unchecked exception。Gavin King现在相信Hibernate也应该选择使用unchecked exception。

Spring的JDBC能够用以下办法帮助你:


  • 你决不需要在使用JDBC时再编写finally block。

    总的来说你需要编写的代码更少了

    你再也不需要挖掘你的RDBMS的文档以找出它为错误的列名称返回的某个罕见的错误代码。你的程序不再依赖于RDBMS特有的错误处理代码。

    无论使用的是什么持久化技术,你都会发现容易实现DAO模式,让业务代码无需依赖于任何特定的数据访问API。


在实践中,我们发现所有这些都确实有助于生产力的提高和更少的bug。我过去常常厌恶编写JDBC代码;现在我发现我能够集中精力于我要执行的SQL,而不是烦杂的JDBC资源管理。

如果需要的话Spring的JDBC抽象可以独立使用——不强迫你把它们用作Spring的一部分。
O/R mapping 集成

当然你经常需要使用O/R mapping,而不是使用关系数据访问。你总体的应用程序框架也必须支持它。因而提供了对Hibernate 2.x和JDO的集成支持。它的数据访问架构使得它能和任何底层的数据访问技术集成。Spring和Hibernate集成得尤其好。

为什么你要使用Hibernate加Spring,而不是直接使用Hibernate?


  • Session 管理 Spring提供有效率的,简单的以并且是安全的处理Hibernate Session。使用Hibernate的相关代码为了效率和恰当的事务处理一般需要使用相同的Hibernate “Session”对象。Spring让它容易透明地创建和绑定Session到当前的线程,要么使用声明式,AOP的method interceptor方法,要么在Java代码层面使用显式的,“template”包装类。因而Spring解决了在Hibernate论坛上经常出现的用法问题。

    资源管理 Spring的应用程序context能够处理Hiberante SessionFactories的位置和配置,JDBC数据源和其他相关资源。这使得这些值易于管理和改变。

    集成的事务管理 Spring让你能够把你的Hibernate代码包装起来,要么使用声明式,AOP风格的method interceptor,要么在Java代码层面显式使用“template”包装类。在两种做法中,事务语义都为你处理了,并且在异常时也做好了恰当的事务处理(回滚,等)。如下面讨论的,你还获得了能够使用和替换不同transaction manager,而不会让你相关Hibernate代码受到影响的能力。额外的,JDBC相关的代码能够完全事务性的和Hibernate代码集成。这对于处理没有在Hibernate实现的功能很有用。

    如上描述的异常包装 Spring能够包装Hibernate异常,把它们从私有的,checked异常转换为一套抽象的运行时异常。这使得你能够仅仅在恰当的层面处理大部分不可恢复的持久化异常,而不影响样板catch/throw,和异常声明。你仍然能够在任何你需要的地方捕捉和处理异常。记住JDBC异常(包括DB特有的方言)也被转换到相同的层次中,意味着你能在一致的编程模型中对JDBC执行相同的操作。

    为了避免和厂商绑定 Hibernate是强大的,灵活的,开放源代码并且免费,但是它仍然使用私有的API。给出了一些选择,使用标准或者抽象API实现主要的程序功能通常是你想要的,当你需要因为功能,性能,或者其他考虑要转换到使用其他实现时。

    让测试变简单 Spring的Inversion of Control方法使得改变Hibernate的session factories,数据源,transaction manager的实现和位置很容易,如果需要的话还能改变mapper object的实现。这使得更加容易分离和测试持久化相关的代码。
    事务管理
    抽象出一个数据访问的API是不够的;我们还需要考虑事务管理。JTA是显而易见的选择,但是它是一个直接用起来很笨重的API,因而许多J2EE开发者感到EJB CMT是对于事务管理唯一合理的选择。

    Spring提供了它自己对事务管理的抽象。Spring提供了这些:


    • 通过类似于JdbcTemplate的回调模板编程管理事务,比起直接使用JTA要容易多了

      类似于EJB CMT的声明式事务管理,但是不需要EJB容器

    Spring的事务抽象式唯一的,它不绑定到JTA或者任何其他事务管理技术。Spring使用事务策略的概念把程序代码和底层的事务架构(例如JDBC)解藕。

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

    假定你决定通过直接使用JDBC或者Hibernate的事务以避免JTA带来的额外负担。一旦你需要处理多个数据源,你必须剥开所有的事务管理代码并且使用JTA事务来替代。这不是非常有吸引力的并且导致大部分J2EE程序员,包括我自己,推荐只使用全局JTA事务。然而使用Spring事务抽象,你只需要重新配置Spring让它使用JTA,而不是JDBC或者Hibernate的事务策略,就一切OK了。这是一个配置上的改变,而不是代码的改动。因而,Spring使得你能够自由缩放应用。

    AOP

    最近在应用AOP来解决企业关注点方面大家有了很大的兴趣,例如事务管理,这些都是EJB所要解决的。

    Spring的AOP支持的首要目标是要给POJOs提供J2EE服务。这类似于JBoss 4的目标,Spring AOP由它能够在应用服务器之间移植的优势,因而没有绑死在厂商身上的风险。它既可以在web或者EJB容器中使用,也能够在WebLogic,Tomcat,JBoss,Resin,Jetty,Orion和许多其他应用服务器和web容器上使用。

    Spring AOP支持method interception。所支持关键的AOP概念包括:


    • Interception:自定义行为能够在对接口和类的调用之前和之后插入。这类似于AspectJ术语中类似的“around advice”。

      Introduction:指定advice会导致对象实现额外的接口。这混乱了继承。

      静态和动态的pointcuts:在interception发生的程序执行处指定points。静态pointcuts concern函数签名;动态pointcuts也可以在point被求值的地方考虑函数的参数。Pointcuts独立interceptors单独定义,使得标准interceptor可以应用于不同应用程序和代码上下文。

    Spring既支持有状态(一个advised对象一个实例)也支持无状态的interceptors(所有advice使用一个实例)。

    Spring不支持field interception。这是一个经过深思熟虑的设计决定。我总是感觉field interception违反了封装。我比较倾向于把AOP作为补全物,而不是与OOP冲突的东西。如果在5年或者10年后,我们在AOP学习曲线上走得更远了并且觉得应该在程序设计的桌面上给AOP一个位置,我不会惊讶的。(然而在那个时候基于语言的解决方案例如AspectJ可能比它们今天看来更加具有吸引力。)

    Spring使用动态代理实现AOP(其中存在一个接口)或者在运行时使用CGLIB生成字节码(这使得能够代理类)。两种方法都能够在任何应用服务器中使用。

    Spring是第一个实现AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。这些是定义在不同AOP框架中能够互操作interceptors的尝试。

    在TheServerSide和其他地方有一个正在进行但是不是那么引人注目的争论,就是这种interception是不是“true AOP”。我倒不怎么在意它叫什么;仅仅需要知道它是否在实践中有用就好了。我也乐于称它为“declarative middleware”(声明式中间件)。把Spring AOP认做简单,轻量级的无状态beans的替代物,这样就不需要monolithic EJB容器了,而这些仅仅是让你能够构建有你需要的服务的容器。我不推荐advising任何一个POJO,对local SLSBs的类比有助于你理解推荐的粒度。(然而,与EJB不同的是,在恰当但是少见的情况下,你可以自由地把Spring的AOP应用到粒度更好的对象上。)

    因为Spring在实例上advises 对象,而不是在class loader层面上,使用有不同advice的同一个类的多个实例是可能的,或者与advised实例一道使用unadvised 实例。

    可能Spring AOP最常见的应用是声明式事务管理。这是基于前面描述的TansactionTemplate抽象上的,并且可以给任何POJO提供声明式事务管理。取决于事务策略,底层的机制可以是JTA,JDBC,Hibernate或者任何其他提供事务管理的API。

    Spring的声明式事务管理类似于EJB CMT,在以下方面有些不同:


    • 事务管理能够应用于任何POJO。我们推荐业务对象实现接口,但是这只是一个好的编程习惯的问题,而不是由框架强制的。

      通过使用Spring的事务API能够在事务性POJO中实现编程回调。我们为此提供静态的方法,使用ThreadLoacal变量,因而你不需要传播诸如EJBContext这样的context对象来确保回滚。

      你可以声明式地定义“回滚规则”。EJB不会在未捕捉程序异常的时候自动回滚(仅仅在unchecked exceptions和其他Throwables的时候),应用程序开发者经常需要在任何异常发生时回滚。Spring事务管理让你能够声明式地指定什么异常什么子类能够导致自动回滚。缺省的行为和EJB是一致的,但是你能够在checked和unchecked异常时自动回滚。这个在最少化自编程回调代码方面有很大好处,而回调依赖于Spring的事务API(因为EJB的编程回调时在EJBContext中完成的)。

      事务管理不绑定于JTA。如前面解释过的,Spring的事务管理能够在不同事务策略中使用。

    当然还可以使用Spring AOP实现程序特有的aspects。取决于你对AOP概念的接受程度,决定你是否选择这么做,而不是Spring的能力,但是它确实非常有用。我们所见过的成功例子包括:


    • 自定义的security interception,当安全检查的复杂度超出了J2EE安全架构的能力的时候

      在开发中使用的调试和profiling aspects

      发送email通知管理员用户不寻常的举动的Interceptors

    程序自定的aspects能够成为消除需要许多函数的样板代码的有利武器。

    Spring AOP透明地与Sp

  • 需要冗长的错误处理代码来确保ResultSets,Statements以及(最重要的)Connections在使用后关闭。这意味着对JDBC的正确使用可以快速地导致大量的代码量。它还是一个常见的错误来源。Connection leak可以在有负载的情况下快速宕掉应用程序。

    SQLException相对来说不能说明任何问题。JDBC不提供异常的层次,而是用抛出SQLException来响应所有的错误。找出到底哪里出错了——例如,问题是死锁还是无效的SQL?——要去检查SQLState或错误代码。这意味着这些值在数据库之间是变化的。


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

  • 提供API,把冗长乏味和容易出错的异常处理从程序代码移到框架之中。框架处理所有的异常处理;程序代码能够集中精力于编写恰当的SQL和提取结果上。

    为你本要处理SQLException程序代码提供有意义的异常层次。当Spring第一次从数据源取得一个连接时,它检查元数据以确定数据库。它使用这些信息把SQLException映射为自己从org.springframework.dao.DataAccessException派生下来的类层次中正确的异常。因而你的代码可以与有意义的异常打交道,并且不需要为私有的SQLState或者错误码担心。Spring的数据访问异常不是JDBC特有的,因而你的DAO并不一定会因为它们可能抛出的异常而绑死在JDBC上。


Spring提供两层JDBC API。第一个时,在org.springframework.jdbc.core包中,使用回调机制移动控制权——并且因而把错误处理和连接获取和释放——从程序的代码移到了框架之中。这是一种不同的Inversion of Control,但是和用于配置管理的几乎有同等重要的意义。

Spring使用类似的回调机制关注其他包含特殊获取和清理资源步骤的API,例如JDO(获取和释放是由PersistenceManager完成的),事务管理(使用JTA)和JNDI。Spring中完成这些回调的类被称作template。

例如,Spring的JdbcTemplate对象能够用于执行SQL查询并且在如下的列表中保存结果:

代码:
JdbcTemplate template = new JdbcTemplate(dataSource);
final List names = new LinkedList();
template.query("SELECT USER.NAME FROM USER",
   new RowCallbackHandler() {
      public void processRow(ResultSet rs) throws SQLException {
         names.add(rs.getString(1));
      }
   });


注意回调中的程序代码是能够自由抛出SQLException的:Spring将会捕捉到这些异常并且用自己的类层次重新抛出。程序的开发者可以选择哪个异常,如果有的话,被捕捉然后处理。

JdbcTemplate提供许多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起标准JDBC来说性能损失非常小,甚至在当应用中需要的结果集数量很大的时候。

在org.springframework.jdbc.object包中是对JDBC的更高层次的抽象。这是建立在核心的JDBC回调功能基础纸上的,但是提供了一个能够对RDBMS操作——无论是查询,更新或者是存储过程——使用Java对象来建模的API。这个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();
   }

   // Map a result set row to a Java object
   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) {
      // Use superclass convenience method to provide strong typing
      return (User) findObject(id);
   }
}

这个类可以在下面用上:
代码:

User user = userQuery.findUser(25);

这样的对象经常可以用作DAO的inner class。它们是线程安全的,除非子类作了一些超出常规的事情。

在org.springframework.jdbc.object包中另一个重要的类是StoredProcedure类。Spring让存储过程通过带有一个业务方法的Java类进行代理。如果你喜欢的话,你可以定义一个存储过程实现的接口,意味着你能够把你的程序代码从对存储过程的依赖中完全解脱出来。

Spring数据访问异常层次是基于unchecked(运行时)exception的。在几个工程中使用了Spring之后,我越来越确信这个决定是正确的。

数据访问异常一般是不可恢复的。例如,如果我们不能链接到数据库,某个业务对象很有可能就不能完成要解决的问题了。一个可能的异常是optimistic locking violation,但是不是所有的程序使用optimistic locking。强制编写捕捉其无法有效处理的致命的异常通常是不好的。让它们传播到上层的handler,比如servlet或者EJB 容器通常更加合适。所有的Spring对象访问异常都是DataAccessException的子类,因而如果我们确实选择了捕捉所有的Spring数据访问异常,我们可以很容易做到这点。

注意如果我们确实需要从unchecked数据访问异常中恢复,我们仍然可以这么做。我们可以编写代码仅仅处理可恢复的情况。例如,如果我们认为只有optimistic locking violation是可恢复的,我们可以在Spring的DAO中如下这么写:

代码:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}

如果Spring的数据访问异常是checked的,我们需要编写如下的代码。注意我们还是可以选择这么写:
代码:

try {
   // do work
}
catch (OptimisticLockingFailureException ex) {
   // I'm interested in this
}
catch (DataAccessException ex) {
   // Fatal; just rethrow it
}

第一个例子的潜在缺陷是——编译器不能强制处理可能的可恢复的异常——这对于第二个也是如此。因为我们被强制捕捉base exception(DataAccessException),编译器不会强制对子类(OptimisticLockingFailureException)的检查。因而编译器可能强制我们编写处理不可恢复问题的代码,但是对于强制我们处理可恢复的问题并未有任何帮助。

Spring对于数据访问异常的unchecked使用和许多——可能是大多数——成功的持久化框架是一致的。(确实,它部分是受到JDO的影响。)JDBC是少数几个使用checked exception的数据访问API之一。例如TopLink和JDO大量使用unchecked exception。Gavin King现在相信Hibernate也应该选择使用unchecked exception。

Spring的JDBC能够用以下办法帮助你:


  • 你决不需要在使用JDBC时再编写finally block。

    总的来说你需要编写的代码更少了

    你再也不需要挖掘你的RDBMS的文档以找出它为错误的列名称返回的某个罕见的错误代码。你的程序不再依赖于RDBMS特有的错误处理代码。

    无论使用的是什么持久化技术,你都会发现容易实现DAO模式,让业务代码无需依赖于任何特定的数据访问API。


在实践中,我们发现所有这些都确实有助于生产力的提高和更少的bug。我过去常常厌恶编写JDBC代码;现在我发现我能够集中精力于我要执行的SQL,而不是烦杂的JDBC资源管理。

如果需要的话Spring的JDBC抽象可以独立使用——不强迫你把它们用作Spring的一部分。
O/R mapping 集成

当然你经常需要使用O/R mapping,而不是使用关系数据访问。你总体的应用程序框架也必须支持它。因而提供了对Hibernate 2.x和JDO的集成支持。它的数据访问架构使得它能和任何底层的数据访问技术集成。Spring和Hibernate集成得尤其好。

为什么你要使用Hibernate加Spring,而不是直接使用Hibernate?


  • Session 管理 Spring提供有效率的,简单的以并且是安全的处理Hibernate Session。使用Hibernate的相关代码为了效率和恰当的事务处理一般需要使用相同的Hibernate “Session”对象。Spring让它容易透明地创建和绑定Session到当前的线程,要么使用声明式,AOP的method interceptor方法,要么在Java代码层面使用显式的,“template”包装类。因而Spring解决了在Hibernate论坛上经常出现的用法问题。

    资源管理 Spring的应用程序context能够处理Hiberante SessionFactories的位置和配置,JDBC数据源和其他相关资源。这使得这些值易于管理和改变。

    集成的事务管理 Spring让你能够把你的Hibernate代码包装起来,要么使用声明式,AOP风格的method interceptor,要么在Java代码层面显式使用“template”包装类。在两种做法中,事务语义都为你处理了,并且在异常时也做好了恰当的事务处理(回滚,等)。如下面讨论的,你还获得了能够使用和替换不同transaction manager,而不会让你相关Hibernate代码受到影响的能力。额外的,JDBC相关的代码能够完全事务性的和Hibernate代码集成。这对于处理没有在Hibernate实现的功能很有用。

    如上描述的异常包装 Spring能够包装Hibernate异常,把它们从私有的,checked异常转换为一套抽象的运行时异常。这使得你能够仅仅在恰当的层面处理大部分不可恢复的持久化异常,而不影响样板catch/throw,和异常声明。你仍然能够在任何你需要的地方捕捉和处理异常。记住JDBC异常(包括DB特有的方言)也被转换到相同的层次中,意味着你能在一致的编程模型中对JDBC执行相同的操作。

    为了避免和厂商绑定 Hibernate是强大的,灵活的,开放源代码并且免费,但是它仍然使用私有的API。给出了一些选择,使用标准或者抽象API实现主要的程序功能通常是你想要的,当你需要因为功能,性能,或者其他考虑要转换到使用其他实现时。

    让测试变简单 Spring的Inversion of Control方法使得改变Hibernate的session factories,数据源,transaction manager的实现和位置很容易,如果需要的话还能改变mapper object的实现。这使得更加容易分离和测试持久化相关的代码。
    事务管理
    抽象出一个数据访问的API是不够的;我们还需要考虑事务管理。JTA是显而易见的选择,但是它是一个直接用起来很笨重的API,因而许多J2EE开发者感到EJB CMT是对于事务管理唯一合理的选择。

    Spring提供了它自己对事务管理的抽象。Spring提供了这些:


    • 通过类似于JdbcTemplate的回调模板编程管理事务,比起直接使用JTA要容易多了

      类似于EJB CMT的声明式事务管理,但是不需要EJB容器

    Spring的事务抽象式唯一的,它不绑定到JTA或者任何其他事务管理技术。Spring使用事务策略的概念把程序代码和底层的事务架构(例如JDBC)解藕。

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

    假定你决定通过直接使用JDBC或者Hibernate的事务以避免JTA带来的额外负担。一旦你需要处理多个数据源,你必须剥开所有的事务管理代码并且使用JTA事务来替代。这不是非常有吸引力的并且导致大部分J2EE程序员,包括我自己,推荐只使用全局JTA事务。然而使用Spring事务抽象,你只需要重新配置Spring让它使用JTA,而不是JDBC或者Hibernate的事务策略,就一切OK了。这是一个配置上的改变,而不是代码的改动。因而,Spring使得你能够自由缩放应用。

    AOP

    最近在应用AOP来解决企业关注点方面大家有了很大的兴趣,例如事务管理,这些都是EJB所要解决的。

    Spring的AOP支持的首要目标是要给POJOs提供J2EE服务。这类似于JBoss 4的目标,Spring AOP由它能够在应用服务器之间移植的优势,因而没有绑死在厂商身上的风险。它既可以在web或者EJB容器中使用,也能够在WebLogic,Tomcat,JBoss,Resin,Jetty,Orion和许多其他应用服务器和web容器上使用。

    Spring AOP支持method interception。所支持关键的AOP概念包括:


    • Interception:自定义行为能够在对接口和类的调用之前和之后插入。这类似于AspectJ术语中类似的“around advice”。

      Introduction:指定advice会导致对象实现额外的接口。这混乱了继承。

      静态和动态的pointcuts:在interception发生的程序执行处指定points。静态pointcuts concern函数签名;动态pointcuts也可以在point被求值的地方考虑函数的参数。Pointcuts独立interceptors单独定义,使得标准interceptor可以应用于不同应用程序和代码上下文。

    Spring既支持有状态(一个advised对象一个实例)也支持无状态的interceptors(所有advice使用一个实例)。

    Spring不支持field interception。这是一个经过深思熟虑的设计决定。我总是感觉field interception违反了封装。我比较倾向于把AOP作为补全物,而不是与OOP冲突的东西。如果在5年或者10年后,我们在AOP学习曲线上走得更远了并且觉得应该在程序设计的桌面上给AOP一个位置,我不会惊讶的。(然而在那个时候基于语言的解决方案例如AspectJ可能比它们今天看来更加具有吸引力。)

    Spring使用动态代理实现AOP(其中存在一个接口)或者在运行时使用CGLIB生成字节码(这使得能够代理类)。两种方法都能够在任何应用服务器中使用。

    Spring是第一个实现AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。这些是定义在不同AOP框架中能够互操作interceptors的尝试。

    在TheServerSide和其他地方有一个正在进行但是不是那么引人注目的争论,就是这种interception是不是“true AOP”。我倒不怎么在意它叫什么;仅仅需要知道它是否在实践中有用就好了。我也乐于称它为“declarative middleware”(声明式中间件)。把Spring AOP认做简单,轻量级的无状态beans的替代物,这样就不需要monolithic EJB容器了,而这些仅仅是让你能够构建有你需要的服务的容器。我不推荐advising任何一个POJO,对local SLSBs的类比有助于你理解推荐的粒度。(然而,与EJB不同的是,在恰当但是少见的情况下,你可以自由地把Spring的AOP应用到粒度更好的对象上。)

    因为Spring在实例上advises 对象,而不是在class loader层面上,使用有不同advice的同一个类的多个实例是可能的,或者与advised实例一道使用unadvised 实例。

    可能Spring AOP最常见的应用是声明式事务管理。这是基于前面描述的TansactionTemplate抽象上的,并且可以给任何POJO提供声明式事务管理。取决于事务策略,底层的机制可以是JTA,JDBC,Hibernate或者任何其他提供事务管理的API。

    Spring的声明式事务管理类似于EJB CMT,在以下方面有些不同:


    • 事务管理能够应用于任何POJO。我们推荐业务对象实现接口,但是这只是一个好的编程习惯的问题,而不是由框架强制的。

      通过使用Spring的事务API能够在事务性POJO中实现编程回调。我们为此提供静态的方法,使用ThreadLoacal变量,因而你不需要传播诸如EJBContext这样的context对象来确保回滚。

      你可以声明式地定义“回滚规则”。EJB不会在未捕捉程序异常的时候自动回滚(仅仅在unchecked exceptions和其他Throwables的时候),应用程序开发者经常需要在任何异常发生时回滚。Spring事务管理让你能够声明式地指定什么异常什么子类能够导致自动回滚。缺省的行为和EJB是一致的,但是你能够在checked和unchecked异常时自动回滚。这个在最少化自编程回调代码方面有很大好处,而回调依赖于Spring的事务API(因为EJB的编程回调时在EJBContext中完成的)。

      事务管理不绑定于JTA。如前面解释过的,Spring的事务管理能够在不同事务策略中使用。

    当然还可以使用Spring AOP实现程序特有的aspects。取决于你对AOP概念的接受程度,决定你是否选择这么做,而不是Spring的能力,但是它确实非常有用。我们所见过的成功例子包括:


    • 自定义的security interception,当安全检查的复杂度超出了J2EE安全架构的能力的时候

      在开发中使用的调试和profiling aspects

      发送email通知管理员用户不寻常的举动的Interceptors

    程序自定的aspects能够成为消除需要许多函数的样板代码的有利武器。

    Spring AOP透明地与Sp


  • 你决不需要在使用JDBC时再编写finally block。

    总的来说你需要编写的代码更少了

    你再也不需要挖掘你的RDBMS的文档以找出它为错误的列名称返回的某个罕见的错误代码。你的程序不再依赖于RDBMS特有的错误处理代码。

    无论使用的是什么持久化技术,你都会发现容易实现DAO模式,让业务代码无需依赖于任何特定的数据访问API。


在实践中,我们发现所有这些都确实有助于生产力的提高和更少的bug。我过去常常厌恶编写JDBC代码;现在我发现我能够集中精力于我要执行的SQL,而不是烦杂的JDBC资源管理。

如果需要的话Spring的JDBC抽象可以独立使用——不强迫你把它们用作Spring的一部分。
O/R mapping 集成

当然你经常需要使用O/R mapping,而不是使用关系数据访问。你总体的应用程序框架也必须支持它。因而提供了对Hibernate 2.x和JDO的集成支持。它的数据访问架构使得它能和任何底层的数据访问技术集成。Spring和Hibernate集成得尤其好。

为什么你要使用Hibernate加Spring,而不是直接使用Hibernate?


  • Session 管理 Spring提供有效率的,简单的以并且是安全的处理Hibernate Session。使用Hibernate的相关代码为了效率和恰当的事务处理一般需要使用相同的Hibernate “Session”对象。Spring让它容易透明地创建和绑定Session到当前的线程,要么使用声明式,AOP的method interceptor方法,要么在Java代码层面使用显式的,“template”包装类。因而Spring解决了在Hibernate论坛上经常出现的用法问题。

    资源管理 Spring的应用程序context能够处理Hiberante SessionFactories的位置和配置,JDBC数据源和其他相关资源。这使得这些值易于管理和改变。

    集成的事务管理 Spring让你能够把你的Hibernate代码包装起来,要么使用声明式,AOP风格的method interceptor,要么在Java代码层面显式使用“template”包装类。在两种做法中,事务语义都为你处理了,并且在异常时也做好了恰当的事务处理(回滚,等)。如下面讨论的,你还获得了能够使用和替换不同transaction manager,而不会让你相关Hibernate代码受到影响的能力。额外的,JDBC相关的代码能够完全事务性的和Hibernate代码集成。这对于处理没有在Hibernate实现的功能很有用。

    如上描述的异常包装 Spring能够包装Hibernate异常,把它们从私有的,checked异常转换为一套抽象的运行时异常。这使得你能够仅仅在恰当的层面处理大部分不可恢复的持久化异常,而不影响样板catch/throw,和异常声明。你仍然能够在任何你需要的地方捕捉和处理异常。记住JDBC异常(包括DB特有的方言)也被转换到相同的层次中,意味着你能在一致的编程模型中对JDBC执行相同的操作。

    为了避免和厂商绑定 Hibernate是强大的,灵活的,开放源代码并且免费,但是它仍然使用私有的API。给出了一些选择,使用标准或者抽象API实现主要的程序功能通常是你想要的,当你需要因为功能,性能,或者其他考虑要转换到使用其他实现时。

    让测试变简单 Spring的Inversion of Control方法使得改变Hibernate的session factories,数据源,transaction manager的实现和位置很容易,如果需要的话还能改变mapper object的实现。这使得更加容易分离和测试持久化相关的代码。
    事务管理
    抽象出一个数据访问的API是不够的;我们还需要考虑事务管理。JTA是显而易见的选择,但是它是一个直接用起来很笨重的API,因而许多J2EE开发者感到EJB CMT是对于事务管理唯一合理的选择。

    Spring提供了它自己对事务管理的抽象。Spring提供了这些:


    • 通过类似于JdbcTemplate的回调模板编程管理事务,比起直接使用JTA要容易多了

      类似于EJB CMT的声明式事务管理,但是不需要EJB容器

    Spring的事务抽象式唯一的,它不绑定到JTA或者任何其他事务管理技术。Spring使用事务策略的概念把程序代码和底层的事务架构(例如JDBC)解藕。

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

    假定你决定通过直接使用JDBC或者Hibernate的事务以避免JTA带来的额外负担。一旦你需要处理多个数据源,你必须剥开所有的事务管理代码并且使用JTA事务来替代。这不是非常有吸引力的并且导致大部分J2EE程序员,包括我自己,推荐只使用全局JTA事务。然而使用Spring事务抽象,你只需要重新配置Spring让它使用JTA,而不是JDBC或者Hibernate的事务策略,就一切OK了。这是一个配置上的改变,而不是代码的改动。因而,Spring使得你能够自由缩放应用。

    AOP

    最近在应用AOP来解决企业关注点方面大家有了很大的兴趣,例如事务管理,这些都是EJB所要解决的。

    Spring的AOP支持的首要目标是要给POJOs提供J2EE服务。这类似于JBoss 4的目标,Spring AOP由它能够在应用服务器之间移植的优势,因而没有绑死在厂商身上的风险。它既可以在web或者EJB容器中使用,也能够在WebLogic,Tomcat,JBoss,Resin,Jetty,Orion和许多其他应用服务器和web容器上使用。

    Spring AOP支持method interception。所支持关键的AOP概念包括:


    • Interception:自定义行为能够在对接口和类的调用之前和之后插入。这类似于AspectJ术语中类似的“around advice”。

      Introduction:指定advice会导致对象实现额外的接口。这混乱了继承。

      静态和动态的pointcuts:在interception发生的程序执行处指定points。静态pointcuts concern函数签名;动态pointcuts也可以在point被求值的地方考虑函数的参数。Pointcuts独立interceptors单独定义,使得标准interceptor可以应用于不同应用程序和代码上下文。

    Spring既支持有状态(一个advised对象一个实例)也支持无状态的interceptors(所有advice使用一个实例)。

    Spring不支持field interception。这是一个经过深思熟虑的设计决定。我总是感觉field interception违反了封装。我比较倾向于把AOP作为补全物,而不是与OOP冲突的东西。如果在5年或者10年后,我们在AOP学习曲线上走得更远了并且觉得应该在程序设计的桌面上给AOP一个位置,我不会惊讶的。(然而在那个时候基于语言的解决方案例如AspectJ可能比它们今天看来更加具有吸引力。)

    Spring使用动态代理实现AOP(其中存在一个接口)或者在运行时使用CGLIB生成字节码(这使得能够代理类)。两种方法都能够在任何应用服务器中使用。

    Spring是第一个实现AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。这些是定义在不同AOP框架中能够互操作interceptors的尝试。

    在TheServerSide和其他地方有一个正在进行但是不是那么引人注目的争论,就是这种interception是不是“true AOP”。我倒不怎么在意它叫什么;仅仅需要知道它是否在实践中有用就好了。我也乐于称它为“declarative middleware”(声明式中间件)。把Spring AOP认做简单,轻量级的无状态beans的替代物,这样就不需要monolithic EJB容器了,而这些仅仅是让你能够构建有你需要的服务的容器。我不推荐advising任何一个POJO,对local SLSBs的类比有助于你理解推荐的粒度。(然而,与EJB不同的是,在恰当但是少见的情况下,你可以自由地把Spring的AOP应用到粒度更好的对象上。)

    因为Spring在实例上advises 对象,而不是在class loader层面上,使用有不同advice的同一个类的多个实例是可能的,或者与advised实例一道使用unadvised 实例。

    可能Spring AOP最常见的应用是声明式事务管理。这是基于前面描述的TansactionTemplate抽象上的,并且可以给任何POJO提供声明式事务管理。取决于事务策略,底层的机制可以是JTA,JDBC,Hibernate或者任何其他提供事务管理的API。

    Spring的声明式事务管理类似于EJB CMT,在以下方面有些不同:


    • 事务管理能够应用于任何POJO。我们推荐业务对象实现接口,但是这只是一个好的编程习惯的问题,而不是由框架强制的。

      通过使用Spring的事务API能够在事务性POJO中实现编程回调。我们为此提供静态的方法,使用ThreadLoacal变量,因而你不需要传播诸如EJBContext这样的context对象来确保回滚。

      你可以声明式地定义“回滚规则”。EJB不会在未捕捉程序异常的时候自动回滚(仅仅在unchecked exceptions和其他Throwables的时候),应用程序开发者经常需要在任何异常发生时回滚。Spring事务管理让你能够声明式地指定什么异常什么子类能够导致自动回滚。缺省的行为和EJB是一致的,但是你能够在checked和unchecked异常时自动回滚。这个在最少化自编程回调代码方面有很大好处,而回调依赖于Spring的事务API(因为EJB的编程回调时在EJBContext中完成的)。

      事务管理不绑定于JTA。如前面解释过的,Spring的事务管理能够在不同事务策略中使用。

    当然还可以使用Spring AOP实现程序特有的aspects。取决于你对AOP概念的接受程度,决定你是否选择这么做,而不是Spring的能力,但是它确实非常有用。我们所见过的成功例子包括:


    • 自定义的security interception,当安全检查的复杂度超出了J2EE安全架构的能力的时候

      在开发中使用的调试和profiling aspects

      发送email通知管理员用户不寻常的举动的Interceptors

    程序自定的aspects能够成为消除需要许多函数的样板代码的有利武器。

    Spring AOP透明地与Sp


  • Session 管理 Spring提供有效率的,简单的以并且是安全的处理Hibernate Session。使用Hibernate的相关代码为了效率和恰当的事务处理一般需要使用相同的Hibernate “Session”对象。Spring让它容易透明地创建和绑定Session到当前的线程,要么使用声明式,AOP的method interceptor方法,要么在Java代码层面使用显式的,“template”包装类。因而Spring解决了在Hibernate论坛上经常出现的用法问题。

    资源管理 Spring的应用程序context能够处理Hiberante SessionFactories的位置和配置,JDBC数据源和其他相关资源。这使得这些值易于管理和改变。

    集成的事务管理 Spring让你能够把你的Hibernate代码包装起来,要么使用声明式,AOP风格的method interceptor,要么在Java代码层面显式使用“template”包装类。在两种做法中,事务语义都为你处理了,并且在异常时也做好了恰当的事务处理(回滚,等)。如下面讨论的,你还获得了能够使用和替换不同transaction manager,而不会让你相关Hibernate代码受到影响的能力。额外的,JDBC相关的代码能够完全事务性的和Hibernate代码集成。这对于处理没有在Hibernate实现的功能很有用。

    如上描述的异常包装 Spring能够包装Hibernate异常,把它们从私有的,checked异常转换为一套抽象的运行时异常。这使得你能够仅仅在恰当的层面处理大部分不可恢复的持久化异常,而不影响样板catch/throw,和异常声明。你仍然能够在任何你需要的地方捕捉和处理异常。记住JDBC异常(包括DB特有的方言)也被转换到相同的层次中,意味着你能在一致的编程模型中对JDBC执行相同的操作。

    为了避免和厂商绑定 Hibernate是强大的,灵活的,开放源代码并且免费,但是它仍然使用私有的API。给出了一些选择,使用标准或者抽象API实现主要的程序功能通常是你想要的,当你需要因为功能,性能,或者其他考虑要转换到使用其他实现时。

    让测试变简单 Spring的Inversion of Control方法使得改变Hibernate的session factories,数据源,transaction manager的实现和位置很容易,如果需要的话还能改变mapper object的实现。这使得更加容易分离和测试持久化相关的代码。
    事务管理
    抽象出一个数据访问的API是不够的;我们还需要考虑事务管理。JTA是显而易见的选择,但是它是一个直接用起来很笨重的API,因而许多J2EE开发者感到EJB CMT是对于事务管理唯一合理的选择。

    Spring提供了它自己对事务管理的抽象。Spring提供了这些:


    • 通过类似于JdbcTemplate的回调模板编程管理事务,比起直接使用JTA要容易多了

      类似于EJB CMT的声明式事务管理,但是不需要EJB容器

    Spring的事务抽象式唯一的,它不绑定到JTA或者任何其他事务管理技术。Spring使用事务策略的概念把程序代码和底层的事务架构(例如JDBC)解藕。

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

    假定你决定通过直接使用JDBC或者Hibernate的事务以避免JTA带来的额外负担。一旦你需要处理多个数据源,你必须剥开所有的事务管理代码并且使用JTA事务来替代。这不是非常有吸引力的并且导致大部分J2EE程序员,包括我自己,推荐只使用全局JTA事务。然而使用Spring事务抽象,你只需要重新配置Spring让它使用JTA,而不是JDBC或者Hibernate的事务策略,就一切OK了。这是一个配置上的改变,而不是代码的改动。因而,Spring使得你能够自由缩放应用。

    AOP

    最近在应用AOP来解决企业关注点方面大家有了很大的兴趣,例如事务管理,这些都是EJB所要解决的。

    Spring的AOP支持的首要目标是要给POJOs提供J2EE服务。这类似于JBoss 4的目标,Spring AOP由它能够在应用服务器之间移植的优势,因而没有绑死在厂商身上的风险。它既可以在web或者EJB容器中使用,也能够在WebLogic,Tomcat,JBoss,Resin,Jetty,Orion和许多其他应用服务器和web容器上使用。

    Spring AOP支持method interception。所支持关键的AOP概念包括:


    • Interception:自定义行为能够在对接口和类的调用之前和之后插入。这类似于AspectJ术语中类似的“around advice”。

      Introduction:指定advice会导致对象实现额外的接口。这混乱了继承。

      静态和动态的pointcuts:在interception发生的程序执行处指定points。静态pointcuts concern函数签名;动态pointcuts也可以在point被求值的地方考虑函数的参数。Pointcuts独立interceptors单独定义,使得标准interceptor可以应用于不同应用程序和代码上下文。

    Spring既支持有状态(一个advised对象一个实例)也支持无状态的interceptors(所有advice使用一个实例)。

    Spring不支持field interception。这是一个经过深思熟虑的设计决定。我总是感觉field interception违反了封装。我比较倾向于把AOP作为补全物,而不是与OOP冲突的东西。如果在5年或者10年后,我们在AOP学习曲线上走得更远了并且觉得应该在程序设计的桌面上给AOP一个位置,我不会惊讶的。(然而在那个时候基于语言的解决方案例如AspectJ可能比它们今天看来更加具有吸引力。)

    Spring使用动态代理实现AOP(其中存在一个接口)或者在运行时使用CGLIB生成字节码(这使得能够代理类)。两种方法都能够在任何应用服务器中使用。

    Spring是第一个实现AOP Alliance interfaces的AOP 框架(www.sourceforge.net/projects/aopalliance)。这些是定义在不同AOP框架中能够互操作interceptors的尝试。

    在TheServerSide和其他地方有一个正在进行但是不是那么引人注目的争论,就是这种interception是不是“true AOP”。我倒不怎么在意它叫什么;仅仅需要知道它是否在实践中有用就好了。我也乐于称它为“declarative middleware”(声明式中间件)。把Spring AOP认做简单,轻量级的无状态beans的替代物,这样就不需要monolithic EJB容器了,而这些仅仅是让你能够构建有你需要的服务的容器。我不推荐advising任何一个POJO,对local SLSBs的类比有助于你理解推荐的粒度。(然而,与EJB不同的是,在恰当但是少见的情况下,你可以自由地把Spring的AOP应用到粒度更好的对象上。)

    因为Spring在实例上advises 对象,而不是在class loader层面上,使用有不同advice的同一个类的多个实例是可能的,或者与advised实例一道使用unadvised 实例。

    可能Spring AOP最常见的应用是声明式事务管理。这是基于前面描述的TansactionTemplate抽象上的,并且可以给任何POJO提供声明式事务管理。取决于事务策略,底层的机制可以是JTA,JDBC,Hibernate或者任何其他提供事务管理的API。

    Spring的声明式事务管理类似于EJB CMT,在以下方面有些不同:


    • 事务管理能够应用于任何POJO。我们推荐业务对象实现接口,但是这只是一个好的编程习惯的问题,而不是由框架强制的。

      通过使用Spring的事务API能够在事务性POJO中实现编程回调。我们为此提供静态的方法,使用ThreadLoacal变量,因而你不需要传播诸如EJBContext这样的context对象来确保回滚。

      你可以声明式地定义“回滚规则”。EJB不会在未捕捉程序异常的时候自动回滚(仅仅在unchecked exceptions和其他Throwables的时候),应用程序开发者经常需要在任何异常发生时回滚。Spring事务管理让你能够声明式地指定什么异常什么子类能够导致自动回滚。缺省的行为和EJB是一致的,但是你能够在checked和unchecked异常时自动回滚。这个在最少化自编程回调代码方面有很大好处,而回调依赖于Spring的事务API(因为EJB的编程回调时在EJBContext中完成的)。

      事务管理不绑定于JTA。如前面解释过的,Spring的事务管理能够在不同事务策略中使用。

    当然还可以使用Spring AOP实现程序特有的aspects。取决于你对AOP概念的接受程度,决定你是否选择这么做,而不是Spring的能力,但是它确实非常有用。我们所见过的成功例子包括:


    • 自定义的security interception,当安全检查的复杂度超出了J2EE安全架构的能力的时候

      在开发中使用的调试和profiling aspects

      发送email通知管理员用户不寻常的举动的Interceptors

    程序自定的aspects能够成为消除需要许多函数的样板代码的有利武器。

    Spring AOP透明地与Sp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值