这段代码将能工作在一个应用服务器之外:甚至不依赖J2EE,因为Spring 的IoC容器是纯java的。
JDBC 抽象和数据存储异常层次
数据访问是Spring 的另一个闪光点。
JDBC 提供了还算不错的数据库抽象,但是需要用痛苦的API。这些问题包括:
| java代码: |
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对象的查询对象可能是这样的:
| java代码: |
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); } }
|
这个类可以在下面用上:
| java代码: |
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中如下这么写:
| java代码: |
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this }
|
如果Spring的数据访问异常是checked的,我们需要编写如下的代码。注意我们还是可以选择这么写:
| java代码: |
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能够用以下办法帮助你:
需要冗长的错误处理代码来确保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查询并且在如下的列表中保存结果:
你决不需要在使用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?
事务管理
抽象出一个数据访问的API是不够的;我们还需要考虑事务管理。JTA是显而易见的选择,但是它是一个直接用起来很笨重的API,因而许多J2EE开发者感到EJB CMT是对于事务管理唯一合理的选择。
Spring提供了它自己对事务管理的抽象。Spring提供了这些:
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的实现。这使得更加容易分离和测试持久化相关的代码。
通过类似于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概念包括:
| java代码: |
<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>
|
注意bean类的定义总是AOP框架的ProxyFactoryBean,虽然bean的类型在引用中使用或者由BeanFactory的getBean()方法返回时依赖的是代理接口。(多个代理方法是被支持的。)ProxyFactoryBean的“interceptorNames”属性需要一个字符串列表。(因为如果代理是一个“prototype”而不是singleton,有状态interceptors可能需要创建新的实例,所以必须使用Bean的名字而不是bean的引用。)列表中的名字可以是interceptor或者pointcuts(interceptors和有关它们合适被使用的信息)。列表中的“target”值自动创建一个“invoker interceptor”封装target对象。实现代理接口的是在factory中的bean的名字。这个例子中的myTest可以和其他bean factory中的bean一样使用。例如,其他对象可以使用<ref>元素引用它而且这些引用是由Spring IoC设置的。
还可以不用BeanFactory,编程构建AOP代理,虽然这个很少用得上:
| java代码: |
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); // An "invoker interceptor" is automatically added to wrap the target ITestBean tb = (ITestBean) factory.getProxy();
|
我们相信最好把程序装配从Java代码中移出来,而AOP也不例外。
Spring在它的AOP能力方面的直接竞争者是Jon Tirsen的Nanning Aspects(http://nanning.codehaus.org)。
我觉得AOP作为EJB的替代无提供企业服务这个用法方面的进步是重要的。随着时间,这将成为Spring很重要的关注点。
MVC web 框架
Spring包括一个强大而且高度可配置的MVC web 框架。
Spring的MVC model类似于Struts。在多线程服务对象这点上,Spring的Controller类似于Struts Action,只有一个实例处理所有客户的请求。然而,我们相信Spring的MVC比起Struts有很多优点,例如:
| java代码: |
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 property definitions omitted...
// Use google business object google.doGoogleSearch(this.googleKey, query, start, maxResults, filter, restrict, safeSearch, lr, ie, oe);
return new ModelAndView("googleResults", "result", result); } }
|
这段代码使用的prototype中,IGoogleSearchPort是一个GLUE web services代理,由Spring FActoryBean返回。然而,Spring把controller从底层web service库中分离出来。接口可以使用普通的Java对象,test stub,mock对象或者如下面要讨论的EJB代理实现。这个contorller不包括资源查找;除了支持它的web交互的必要代码之外没有别的什么了。
Spring还提供了数据绑定,forms,wizards和更复杂的工作流的支持。
对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的分层架构使得你能够使用Spring的其他部分而不用MVC层。我们有使用Spring做中间层管理和数据访问,但是在web层使用Struts,WebWork或者Tapestry的用户。
实现EJB
如果你选择使用EJB,Spring能在EJB实现和客户端访问EJB两方面都提供很大的好处。
对业务逻辑进行重构,把它从EJB facades中取出到POJO已经得到了广泛的认同。(不讲别的,这使得业务逻辑更容易单元测试,因为EJB严重依赖于容器而难于分离测试。)Spring为session bean和message driver bean提供了方便的超类,使得通过自动载入基于包含在EJB Jar文件中的XML文档BeanFactory让这变得很容易。
这意味着stateless session EJB可以这么获得和使用所需对象:
| java代码: |
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 factory 定义中隐藏。
我们通过在ejb-jar.xmldeployment descriptor中名为ejb/BeanFactoryPath的环境变量定义告诉Spring去哪儿装载XML文档。如下:
| java代码: |
<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> </env-entry> </session>
|
myComponent-ejb-beans.xml 文件将会从classpath装载:在本例中,是EJB Jar文件的根目录。每个EJB都能指定自己的XML文档,因而这个机制能在每个EJB Jar文件中使用多次。
Spring 的超类实现了EJB中诸如setSessionContext()和ejbCreate()的生命周期管理的方法,让应用程序开发者只需选择是否实现Spring的onEjbCreate()方法。
使用EJB
Spring还让实现EJB变得更加容易。许多EJB程序使用Service Locator和Business Delegate模式。这些比在客户代码中遍布JNDI查找强多了,但是它们常见的实现方式有显著的缺点,例如:
| java代码: |
private MyComponent myComponent;
public void setMyComponent(MyComponent myComponent) { this.myComponent = myComponent; }
|
我们随后在任何业务方法中使用这个实例变量。
Spring自动完称剩下的工作,通过像这样的XML bean定义。LocalStatelessSessionProxyFactoryBean是一个可以用于任何EJB的通用factory bean。它创建的对象能够自动被Spring转型为MyComponent类型。
| java代码: |
<bean id="myComponent" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName"><value>myComponent</value></property> <property name="businessInterface"><value>com.mycom.MyComponent</value></property> </bean>
|