jpetstore 4.0是ibatis的最新示例程序,基于struts mvc框架(注:非传统struts开发模式),以ibatis作为持久化层。该示例程序设计优雅,层次清晰,可以学习以及作为一个高效率的编程模型参考。本文是在其基础上,采用spring对其中间层(业务层)进行改造。使开发量进一步减少,同时又拥有了spring的一些好处…
1. 前言
jpetstore 4.0是ibatis的最新示例程序。ibatis是开源的持久层产品,包含sql maps 2.0 和 data access objects 2.0 框架。jpetstore示例程序很好的展示了如何利用ibatis来开发一个典型的j2ee web应用程序。jpetstore有如下特点:
- ibatis数据层
- pojo业务层
- pojo领域类
- struts mvc
- jsp 表示层
以下是本文用到的关键技术介绍,本文假设您已经对struts,springframewok,ibatis有一定的了解。
- struts 是目前java web mvc框架中不争的王者。经过长达五年的发展,struts已经逐渐成长为一个稳定、成熟的框架,并且占有了mvc框架中最大的市场份额。但是struts某些技术特性上已经落后于新兴的mvc框架。面对spring mvc、webwork2 这些设计更精密,扩展性更强的框架,struts受到了前所未有的挑战。但站在产品开发的角度而言,struts仍然是最稳妥的选择。本文的原型例子jpetstore 4.0就是基于struts开发的,但是不拘泥于struts的传统固定用法,例如只用了一个自定义action类,并且在form bean类的定义上也是开创性的,令人耳目一新,稍后将具体剖析一下。
- spring framework 实际上是expert one-on-one j2ee design and development 一书中所阐述的设计思想的具体实现。spring framework的功能非常多。包含aop、orm、dao、context、web、mvc等几个部分组成。web、mvc暂不用考虑,jpetstore 4.0用的是更成熟的struts和jsp;dao由于目前hibernate、jdo、ibatis的流行,也不考虑,jpetstore 4.0用的就是ibatis。因此最需要用的是aop、orm、context。context中,最重要的是beanfactory,它能将接口与实现分开,非常强大。目前aop应用最成熟的还是在事务管理上。
- ibatis 是一个功能强大实用的sql map工具,不同于其他orm工具(如hibernate),它是将sql语句映射成java对象,而对于orm工具,它的sql语句是根据映射定义生成的。ibatis 以sql开发的工作量和数据库移植性上的让步,为系统设计提供了更大的自由空间。有ibatis代码生成的工具,可以根据ddl自动生成ibatis代码,能减少很多工作量。
2. jpetstore简述
2.1. 背景
最初是sun公司的j2ee petstore,其最主要目的是用于学习j2ee,但是其缺点也很明显,就是过度设计了。接着oracle用j2ee petstore来比较各应用服务器的性能。微软推出了基于。net平台的 pet shop,用于竞争j2ee petstore.而jpetstore则是经过改良的基于struts的轻便框架j2ee web应用程序,相比来说,jpetstore设计和架构更优良,各层定义清晰,使用了很多最佳实践和模式,避免了很多"反模式",如使用存储过程,在java代码中嵌入sql语句,把html存储在数据库中等等。最新版本是jpetstore 4.0.
2.2. jpetstore开发运行环境的建立
1、开发环境
- java sdk 1.4.2
- apache tomcat 4.1.31
- eclipse-sdk-3.0.1-win32
- hsqldb 1.7.2
2、eclipse插件
- emf sdk 2.0.1:eclipse建模框架,lomboz插件需要,可以使用runtime版本。
- lomboz 3.0:j2ee插件,用来在eclipse中开发j2ee应用程序
- spring ide 1.0.3:spring bean配置管理插件
- xmlbuddy_2.0.10:编辑xml,用免费版功能即可
- tomcatpluginv3:tomcat管理插件
- properties editor:编辑java的属性文件,并可以预览以及自动存盘为unicode格式。免去了手工或者ant调用native2ascii的麻烦。
3.4. 持久层
在讨论业务层之前,我们先看一下持久层,如下图所示:
在上文中,我们把iface包下的dao接口归为业务层,在这里不需要做修改。ibatis的sql配置文件也不需要改。要改的是dao实现类,并在spring的配置文件中配置起来。
1、修改基类
所有的dao实现类都继承于basesqlmapdao类。修改basesqlmapdao类如下:
public class basesqlmapdao extends sqlmapclientdaosupport {
protected static final int page_size = 4;
protected sqlmapclienttemplate smctemplate = this.getsqlmapclienttemplate();
public basesqlmapdao() {
}
}使basesqlmapdao类改为继承于spring提供的sqlmapclientdaosupport类,并定义了一个保护属性smctemplate,其类型为sqlmapclienttemplate。关于sqlmapclienttemplate类的详细说明请参照附录中的"spring中文参考手册"
2、修改dao实现类
所有的dao实现类还是继承于basesqlmapdao类,实现相应的dao接口,但其相应的dao操作委托sqlmapclienttemplate来执行,以accountsqlmapdao类为例,部分代码如下:
public list getusernamelist() {
return smctemplate.queryforlist("getusernamelist", null);
}
public account getaccount(string username, string password) {
account account = new account();
account.setusername(username);
account.setpassword(password);
return (account) smctemplate.queryforobject("getaccountbyusernameandpassword", account);
}
public void insertaccount(account account) {
smctemplate.update("insertaccount", account);
smctemplate.update("insertprofile", account);
smctemplate.update("insertsignon", account);
}
就这么简单,所有函数的签名都是一样的,只需要查找替换就可以了!
3、除去工厂类以及相应的配置文件
除去daoconfig.java这个dao工厂类和相应的配置文件dao.xml,因为dao的获取现在要用spring来管理。
4、dao在spring中的配置(applicationcontext.xml)
<bean id="datasource"
class="org.springframework.jdbc.datasource.drivermanagerdatasource">
<property name="driverclassname">
<value>org.hsqldb.jdbcdriver</value>
</property>
<property name="url">
<value>jdbc:hsqldb:hsql://localhost/xdb</value>
</property>
<property name="username">
<value>sa</value>
</property>
<property name="password">
<value></value>
</property>
</bean>
<!-- ibatis sqlmapclient config -->
<bean id="sqlmapclient"
class="org.springframework.orm.ibatis.sqlmapclientfactorybean">
<property name="configlocation">
<value>
classpath:comibatisjpetstorepersistencesqlmapdaosqlsql-map-config.xml
</value>
</property>
<property name="datasource">
<ref bean="datasource"/>
</property>
</bean>
<!-- transactions -->
<bean id="transactionmanager"
class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
<property name="datasource">
<ref bean="datasource"/>
</property>
</bean>
<!-- persistence layer -->
<bean id="accountdao"
class="com.ibatis.jpetstore.persistence.sqlmapdao.accountsqlmapdao">
<property name="sqlmapclient">
<ref local="sqlmapclient"/>
</property>
</bean>具体的语法请参照附录中的"spring中文参考手册".在这里只简单解释一下:
1. 我们首先创建一个数据源datasource,在这里配置的是hsqldb数据库。如果是oracle数据库,driverclassname的值是"oracle.jdbc.driver.oracledriver",url的值类似于"jdbc:oracle:thin:@wugfmobile:1521:cdcf".数据源现在由spring来管理,那么现在我们就可以去掉properties目录下database.properties这个配置文件了;还有不要忘记修改sql-map-config.xml,去掉 对它的引用。
2. sqlmapclient节点。这个是针对ibatis sqlmap的sqlmapclientfactorybean配置。实际上配置了一个sqlmapclient的创建工厂类。configlocation属性配置了ibatis映射文件的名称。datasource属性指向了使用的数据源,这样所有使用sqlmapclient的dao都默认使用了该数据源,除非在dao的配置中另外显式指定。
3. transactionmanager节点。定义了事务,使用的是datasourcetransactionmanager.
4. 下面就可以定义dao节点了,如accountdao,它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.accountsqlmapdao,使用的sql配置从sqlmapclient中读取,数据库连接没有特别列出,那么就是默认使用sqlmapclient配置的数据源datasource.
这样,我们就把持久层改造完了,其他的dao配置类似于accountdao.怎么样?简单吧。这次有接口了:) accountdao接口->accountsqlmapdao实现。
3.5. 业务层
业务层的位置以及相关类,如下图所示:
在这个例子中只有3个业务类,我们以orderservice类为例来改造,这个类是最复杂的,其中涉及了事务。
1、在applicationcontext配置文件中增加bean的配置:
<bean id="orderservice"
class="org.springframework.transaction.interceptor.transactionproxyfactorybean">
<property name="transactionmanager">
<ref local="transactionmanager"></ref>
</property>
<property name="target">
<bean class="com.ibatis.jpetstore.service.orderservice">
<property name="itemdao">
<ref bean="itemdao"/>
</property>
<property name="orderdao">
<ref bean="orderdao"/>
</property>
<property name="sequencedao">
<ref bean="sequencedao"/>
</property>
</bean>
</property>
<property name="transactionattributes">
<props>
<prop key="insert*">propagation_required</prop>
</props>
</property>
</bean>
定义了一个orderservice,还是很容易懂的。为了简单起见,使用了嵌套bean,其实现类是com.ibatis.jpetstore.service.orderservice,分别引用了itemdao,orderdao,sequencedao。该bean的insert*实现了事务管理(aop方式)。transactionproxyfactorybean自动创建一个事务advisor, 该advisor包括一个基于事务属性的pointcut,因此只有事务性的方法被拦截。
- ibatis示例程序jpetstore 4.0 http://www.ibatis.com/jpetstore/jpetstore.html
- 改造后的源程序(+spring)(源码链接)
2.3. 架构
图1 jpetstore架构图
图1 是jpetstore架构图,更详细的内容请参见jpetstore的白皮书。参照这个架构图,让我们稍微剖析一下源代码,得出jpetstore 4.0的具体实现图(见图2),思路一下子就豁然开朗了。前言中提到的非传统的struts开发模式,关键就在struts action类和form bean类上。
struts action类只有一个:beanaction.没错,确实是一个!与传统的struts编程方式很不同。再仔细研究beanaction类,发现它其实是一个通用类,利用反射原理,根据url来决定调用formbean的哪个方法。beanaction大大简化了struts的编程模式,降低了对struts的依赖(与struts以及web容器有关的几个类都放在com.ibatis.struts包下,其它的类都可以直接复用)。利用这种模式,我们会很容易的把它移植到新的框架如jsf,spring.
这样重心就转移到form bean上了,它已经不是普通意义上的form bean了。查看源代码,可以看到它不仅仅有数据和校验/重置方法,而且已经具有了行为,从这个意义上来说,它更像一个bo(business object)。这就是前文讲到的,beanaction类利用反射原理,根据url来决定调用form bean的哪个方法(行为)。form bean的这些方法的签名很简单,例如:
public string myactionmethod() {
//..work
return "success";
}
方法的返回值直接就是字符串,对应的是forward的名称,而不再是actionforward对象,创建actionforward对象的任务已经由beanaction类代劳了。
另外,程序还提供了actioncontext工具类,该工具类封装了request 、response、form parameters、request attributes、session attributes和 application attributes中的数据存取操作,简单而线程安全,form bean类使用该工具类可以进一步从表现层框架解耦。[iocblog.net 来源]
在这里需要特别指出的是,beanaction类是对struts扩展的一个有益尝试,虽然提供了非常好的应用开发模式,但是它还非常新,一直在发展中。
图2 jpetstore 4.0具体实现
2.4. 代码剖析
下面就让我们开始进一步分析jpetstore4.0的源代码,为下面的改造铺路。
- beanaction.java是唯一一个struts action类,位于com.ibatis.struts包下。正如上文所言,它是一个通用的控制类,利用反射机制,把控制转移到form bean的某个方法来处理。详细处理过程参考其源代码,简单明晰。
-
form bean类位于com.ibatis.jpetstore.presentation包下,命名规则为***bean。form bean类全部继承于basebean类,而basebean类实际继承于actionform,因此,form bean类就是struts的 actionform,form bean类的属性数据就由struts框架自动填充。而实际上,jpetstore4.0扩展了struts中actionform的应用: form bean类还具有行为,更像一个bo,其行为(方法)由beanaction根据配置(struts-config.xml)的url来调用。虽然如此,我们还是把form bean类定位于表现层。
struts-config.xml的配置里有3种映射方式,来告诉beanaction把控制转到哪个form bean对象的哪个方法来处理。
以这个请求连接为例http://localhost/jpetstore4/shop/vieworder.do
1. url pattern
<action path="/shop/vieworder" type="com.ibatis.struts.beanaction"
name="orderbean" scope="session"
validate="false">
<forward name="success" path="/order/vieworder.jsp"/>
</action>
此种方式表示,控制将被转发到"orderbean"这个form bean对象 的"vieworder"方法(行为)来处理。方法名取"path"参数的以"/"分隔的最后一部分。
2. method parameter
<action path="/shop/vieworder" type="com.ibatis.struts.beanaction"
name="orderbean" parameter="vieworder" scope="session"
validate="false">
<forward name="success" path="/order/vieworder.jsp"/>
</action>
此种方式表示,控制将被转发到"orderbean"这个form bean对象的"vieworder"方法(行为)来处理。配置中的"parameter"参数表示form bean类上的方法。"parameter"参数优先于"path"参数。
3. no method call
<action path="/shop/vieworder" type="com.ibatis.struts.beanaction"
name="orderbean" parameter="*" scope="session"
validate="false">
<forward name="success" path="/order/vieworder.jsp"/>
</action>
此种方式表示,form bean上没有任何方法被调用。如果存在"name"属性,则struts把表单参数等数据填充到form bean对象后,把控制转发到"success".否则,如果name为空,则直接转发控制到"success".
这就相当于struts内置的org.apache.struts.actions.forwardaction的功能
<action path="/shop/vieworder" type="org.apache.struts.actions.forwardaction"
parameter="/order/vieworder.jsp " scope="session" validate="false">
</action>
- service类位于com.ibatis.jpetstore.service包下,属于业务层。这些类封装了业务以及相应的事务控制。service类由form bean类来调用。
- com.ibatis.jpetstore.persistence.iface包下的类是dao接口,属于业务层,其屏蔽了底层的数据库操作,供具体的service类来调用。daoconfig类是工具类(dao工厂类),service类通过daoconfig类来获得相应的dao接口,而不用关心底层的具体数据库操作,实现了如图2中{耦合2}的解耦。
- com.ibatis.jpetstore.persistence.sqlmapdao包下的类是对应dao接口的具体实现,在jpetstore4.0中采用了ibatis来实现orm。这些实现类继承basesqlmapdao类,而basesqlmapdao类则继承ibatis dao 框架中的sqlmapdaotemplate类。ibatis的配置文件存放在com.ibatis.jpetstore.persistence.sqlmapdao.sql目录下。这些类和配置文件位于数据层
- domain类位于com.ibatis.jpetstore.domain包下,是普通的javabean。在这里用作数据传输对象(dto),贯穿视图层、业务层和数据层,用于在不同层之间传输数据。
剩下的部分就比较简单了,请看具体的源代码,非常清晰。
2.5. 需要改造的地方
jpetstore4.0的关键就在struts action类和form bean类上,这也是其精华之一(虽然该实现方式是试验性,待扩充和验证),在此次改造中我们要保留下来,即控制层一点不变,表现层获取相应业务类的方式变了(要加载spring环境),其它保持不变。要特别关注的改动是业务层和持久层,幸运的是jpetstore4.0设计非常好,需要改动的地方非常少,而且由模式可循,如下:
1. 业务层和数据层用spring beanfactory机制管理。
2. 业务层的事务由spring 的aop通过声明来完成。
3. 表现层(form bean)获取业务类的方法改由自定义工厂类来实现(加载spring环境)。
3. jpetstore的改造
3.1. 改造后的架构
其中红色部分是要增加的部分,蓝色部分是要修改的部分。下面就让我们逐一剖析。
3.2. spring context的加载
为了在struts中加载spring context,一般会在struts-config.xml的最后添加如下部分:
<plug-in classname="org.springframework.web.struts.contextloaderplugin">
<set-property property="contextconfiglocation"
value="/web-inf/applicationcontext.xml" />
</plug-in>
spring在设计时就充分考虑到了与struts的协同工作,通过内置的struts plug-in在两者之间提供了良好的结合点。但是,因为在这里我们一点也不改动jpetstore的控制层(这是jpetstore4.0的精华之一),所以本文不准备采用此方式来加载applicationcontext.我们利用的是spring framework 的beanfactory机制,采用自定义的工具类(bean工厂类)来加载spring的配置文件,从中可以看出spring有多灵活,它提供了各种不同的方式来使用其不同的部分/层次,您只需要用你想用的,不需要的部分可以不用。
具体的来说,就是在com.ibatis.spring包下创建custombeanfactory类,spring的配置文件applicationcontext.xml也放在这个目录下。以下就是该类的全部代码,很简单:
public final class custombeanfactory {
static xmlbeanfactory factory = null;
static {
resource is = new
inputstreamresource( custombeanfactory.class.getresourceasstream("applicationcontext.xml"));
factory = new xmlbeanfactory(is);
}
public static object getbean(string beanname){
return factory.getbean(beanname);
}
}
实际上就是封装了spring 的xmlbeanfactory而已,并且spring的配置文件只需要加载一次,以后就可以直接用custombeanfactory.getbean("somebean")来获得需要的对象了(例如somebean),而不需要知道具体的类。custombeanfactory类用于{耦合1}的解耦。
custombeanfactory类在本文中只用于表现层的form bean对象获得service类的对象,因为我们没有把form bean对象配置在applicationcontext.xml中。但是,为什么不把表现层的form bean类也配置起来呢,这样就用不着这custombeanfactory个类了,spring会帮助我们创建需要的一切?问题的答案就在于form bean类是struts的actionform类!如果大家熟悉struts,就会知道actionform类是struts自动创建的:在一次请求中,struts判断,如果actionform实例不存在,就创建一个actionform对象,把客户提交的表单数据保存到actionform对象中。因此formbean类的对象就不能由spring来创建,但是service类以及数据层的dao类可以,所以只有他们在spring中配置。
所以,很自然的,我们就创建了custombeanfactory类,在表现层来衔接struts和spring.就这么简单,实现了另一种方式的{耦合一}的解耦。
3.3. 表现层
上面分析到,struts和spring是在表现层衔接起来的,那么表现层就要做稍微的更改,即所需要的service类的对象创建上。以表现层的accountbean类为例:
原来的源代码如下
private static final accountservice accountservice = accountservice.getinstance();
private static final catalogservice catalogservice = catalogservice.getinstance();
改造后的源代码如下
private static final accountservice accountservice = (accountservice)custombeanfactory.getbean("accountservice");
private static final catalogservice catalogservice = (catalogservice)custombeanfactory.getbean("catalogservice");
其他的几个presentation类以同样方式改造。这样,表现层就完成了。关于表现层的其它部分如jsp等一概不动。也许您会说,没有看出什么特别之处的好处啊?你还是额外实现了一个工厂类。别着急,帷幕刚刚开启,spring是在表现层引入,但您发没发现:
- presentation类仅仅面向service类的接口编程,具体"accountservice"是哪个实现类,presentation类不知道,是在spring的配置文件里配置。(本例中,为了最大限度的保持原来的代码不作变化,没有抽象出接口)。spring鼓励面向接口编程,因为是如此的方便和自然,当然您也可以不这么做。
- custombeanfactory这个工厂类为什么会如此简单,因为其直接使用了spring的beanfactory。spring从其核心而言,是一个di容器,其设计哲学是提供一种无侵入式的高扩展性的框架。为了实现这个目标,spring 大量引入了java 的reflection机制,通过动态调用的方式避免硬编码方式的约束,并在此基础上建立了其核心组件beanfactory,以此作为其依赖注入机制的实现基础。org.springframework.beans包中包括了这些核心组件的实现类,核心中的核心为beanwrapper和beanfactory类。[iocblog.net 来源]
2、业务类的修改以orderservice为例:
public class orderservice {/* private fields */
private itemdao itemdao;
private orderdao orderdao;
private sequencedao sequencedao;/* constructors */
public orderservice() {
}/**
* @param itemdao 要设置的 itemdao。
*/
public final void setitemdao(itemdao itemdao) {
this.itemdao = itemdao;
}
/**
* @param orderdao 要设置的 orderdao。
*/
public final void setorderdao(orderdao orderdao) {
this.orderdao = orderdao;
}
/**
* @param sequencedao 要设置的 sequencedao。
*/
public final void setsequencedao(sequencedao sequencedao) {
this.sequencedao = sequencedao;
}
//剩下的部分
…….
}红色部分为修改部分。spring采用的是type2的设置依赖注入,所以我们只需要定义属性和相应的设值函数就可以了,itemdao,orderdao,sequencedao的值由spring在运行期间注入。构造函数就可以为空了,另外也不需要自己编写代码处理事务了(事务在配置中声明),daomanager.starttransaction();等与事务相关的语句也可以去掉了。和原来的代码比较一下,是不是处理精简了很多!可以更关注业务的实现。
4. 结束语
ibatis是一个功能强大实用的sql map工具,可以直接控制sql,为系统设计提供了更大的自由空间。其提供的最新示例程序jpetstore 4.0,设计优雅,应用了迄今为止很多最佳实践和设计模式,非常适于学习以及在此基础上创建轻量级的j2ee web应用程序。jpetstore 4.0是基于struts的,本文在此基础上,最大程度保持了原有设计的精华以及最小的代码改动量,在业务层和持久化层引入了spring。在您阅读了本文以及改造后的源代码后,会深切的感受到spring带来的种种好处:自然的面向接口的编程,业务对象的依赖注入,一致的数据存取框架和声明式的事务处理,统一的配置文件…更重要的是spring既是全面的又是模块化的,spring有分层的体系结构,这意味着您能选择仅仅使用它任何一个独立的部分,就像本文,而它的架构又是内部一致。