Eclipse是一个非常不错的开源开发工具,使用Eclipse开发和使用JBuilder将有完全不同的开发方式。我们使用Eclipse基于Jdon框架开发一个完全Web应用,或者可以说,开发一个轻量(lightweight)的J2EE应用系统。
通过这个轻量系统开发,说明Jdon框架对完全POJO架构的支持,因为EJB分布式集群计算能力,随着访问量提升,可能需要引入EJB架构,这时只要使用EJB session Bean包装POJO服务则可以无缝升级到EJB。使用Jdon框架可实现方便简单地架构升迁。
Eclipse安装简要说明
1.下载Eclipse:在http://www.eclipse.org 的下载点中选择tds ISP 比较快。
2.安装免费插件:
编辑Jsp需要lomboz : http://www.objectlearn.com/projects/download.jsp
注意对应的Eclipse版本。
编辑XML,使用Xmlbuddy: http://xmlbuddy.com/
基本上这两个插件就够了。
如果希望开发 Hibernate,插件:
http://www.binamics.com/hibernatesync
代码折叠
http://www.coffee-bytes.com/eclipse/update-site/site.xml
3. 关键学习ant的编写build.xml,在build.xml将你的jsp javaclass打包成war或jar或ear就可以。都可以使用ant的jar打包,build.xml只要参考一个模板就可以:SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.
然后在这个模板上修改,参考 ant的命令参考:
http://ant.apache.org/manual/tasksoverview.html
网上有中文版的ant参考,在google搜索就能找到。
关键是学习ant的build.xml编辑,SimpleJdonFrameworkTest.rar 有一个现成的,可拷贝到其它项目后修改后就可用.
用ant编译替代Eclipse的缺省编译:选择项目属性-->Builders ---> new --> Ant Builder --->选择本项目的build.xml workspace 选择本项目
eclipse开发就这些,非常简单,不象Jbuilder那样智能化,导致项目目录很大。eclipse只负责源码开发,其它都由ant负责
架构设计要点
Jdon-JPetstore除了保留iBATIS-JPetstore 4.0.5的域模型、持久层ibatis实现以及Jsp页面外,其余部分因为使用了Jdon框架而和其有所不同。
保留域模型和Jsp页面主要是在不更改系统需求的前提下,重构其架构实现为Jdon框架,通过对比其原来的实现或Spring的JPetstore实现,可以发现Jdon框架的使用特点。
在原来jpetstore iBatis包会延伸到表现层,例如它的分页查询PaginatedList,iBatis只是持久层框架,它的作用范围应该只限定在持久层,这是它的专业范围,如果超过范围,显得 ….。所以,在Jdon-Jpetstore中将iBatis封装在持久层(砍掉PaginatedList这只太长的手),Jdon框架是一种中间层框架,联系前后台的工作应该由Jdon这样的中间层框架完成。
在iBatis 4.0.5版本中,它使用了一个利用方法映射Reflection的小框架,这样,将原来需要在Action实现方法整入了ActionForm中实现,ActionForm变成了一个复杂的对象:页面表单抽象以及与后台Service交互,在ActionForm中调用后台服务是通过单态模式实现,这是在一般J2EE开发中忌讳的一点,关于单态模式的讨论可见:http://www.jdon.com/jive/article.jsp?forum=91&thread=17578
Jdon框架使用了微容器替代单态,消除了Jpetstore的单态隐患,而且也简化了ActionForm和服务层的交互动作(通过配置实现)。
用户注册登陆模块实现
用户域建模(Model)
首先,我们需要从域建模开始,建立正确的领域模型,以用户账号为例,根据业务需求我们确立用户账号的域模型Account,该模型需要继承Jdon框架中的com.jdon.controller.model.Model。
public class Account extends Model {
private String username;
private String password;
private String email;
private String firstName;
private String lastName;
……
}
username是主键。
域模型建立好之后,就可以花开两朵各表一支,表现层和持久层可以同时开发,先谈谈持久层关于用户模型的CRUD功能实现。
持久层Account CRUD实现
主要是用户的新增和修改,主要用于注册新用户和用户资料修改。
public interface AccountDao {
Account getAccount(String username); //获得一个Account
void insertAccount(Account account); //新增
void updateAccount(Account account); //修改
}
持久层可以使用多种技术实现,例如Jdon框架的JdbcTemp代码实现比较方便,如果你的sql语句可能经常改动,使用iBatis的sql语句XML定义有一定好处,本例程使用Jpetstore原来的持久层实现iBatis。见源码包中的Account.xml
表现层Account表单创建(ModelForm)
这是在Domain Model建立后最重要的一步,是前台表现层Struts开发的起步,表单创建有以下注意点:
表单类必须继承com.jdon.model.ModelForm
表单类基本是Domain Model的影子,每一个Model对应一个ModelForm实例,所谓对应:就是字段名称一致。ModelForm实例是由Model实例复制获得的。
public class AccountForm extends ModelForm {
private String username;
private String password;
private String email;
private String firstName;
private String lastName;
……
}
当然AccountForm可能有一些与显示有关的字段,例如注册时有英文和中文选择,以及类别的选择,那么增加两个字段在AccountForm中:
private List languages;
private List categories;
这两个字段需要初始化值的,因为在AccountForm对应的Jsp的页面中要显示出来,这样用户才可能进行选择。选择后的值将放置在专门的字段中。
有两种方式初始化这两个字段:
1. 在AccountForm构造方法中初始化,前提是:这些初始化值是常量,如:
public AccountForm() {
languages = new ArrayList();
languages.add("english");
languages .add("japanese");
}
2.如果初始化值是必须从数据库中获取,那么采取前面章节介绍的使用ModelHandler来实现,这部分又涉及配置和代码实现,缺省时我们考虑通过jdonframework.xml配置实现。
Account CRUD的struts-config.xml的配置
第一步配置ActionForm:
上节编写了ModelForm代码,ModelForm也就是struts的ActionForm,在struts-config.xml中配置ActionForm如下:
<form-bean name="accountFrom" type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>
第二步配置Action:
这需要根据你的CRUD功能实现需求配置,例如本例中用户注册和用户修改分开,这样,配置两套ModelViewAction和ModelSaveAction,具体配置见源码包中的struts-config-security.xml,这里将struts-config.xml根据模块划分成相应的模块配置,实现多模块开发,本模块是用户注册登陆系统,因此取名struts-config-security.xml。
Account CRUD的Jdonframework.xml配置
<model key="username" class ="com.jdon.framework.samples.jpetstore.domain.Account">
<actionForm name="accountForm"/>
<handler>
<service ref="accountService">
<initMethod name="initAccount" />
<getMethod name="getAccount" />
<createMethod name="insertAccount" />
<updateMethod name="updateAccount" />
<deleteMethod name="deleteAccount" />
</service>
</handler>
</model>
.其中有一个initMethod主要用于AccuntForm对象的初始化。其他都是增删改查的常规实现。
Account CRUD 的Jsp页面实现
在编辑页面EditAccountForm.jsp中加入:
<html:hidden name="accountFrom" property="action" value="create" />
在新增页面NewAccountForm.jsp加入:
<html:hidden name="accountFrom" property="action" value="edit" />
所有的字段都是直接来自accountFrom。
整理模块配置
商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-security.xml,这样以后扩展修改起来方便。
商品查询模块实现
在iBATIS-JPetstore中没有单独的CategoryForm,而是将三个Model:Category、Product、,Item合并在一个CatalogBean中,这样做的缺点是拓展性不强,将来这三个Model也许需要单独的ActionForm。
由于我们使用Jdon框架的CRUD功能配置实现,因此,不怕细分这三个Model带来代码复杂和琐碎。
由于原来的Jpetstore“偷懒”,没有实现Category Product等的CRUD功能,只实现它们的查询功能,因此,我们使用Jdon框架的批量查询来实现查询。
持久层 Product批量查询实现
商品查询主要有两种批量查询,根据其类别ID:CategoryId查询所有该商品目录下所有的商品;根据关键字搜索符合条件的所有商品,下面以前一个功能为例子:
iBatis-jpetstore使用PaginatedList作为分页的主要对象,该对象需要保存到HttpSession中,然后使用PaginatedList的NextPage等直接遍历,这种方法只适合在小数据量合适,J2EE编程中不推荐向HttpSession放入大量数据,不利于cluster。
根据Jdon批量查询的持久层要求,批量查询需要两种SQL语句实现:符合条件的ID集合和符合条件的总数:以及单个Model查询。
//获得ID集合
List getProductIDsListByCategory(String categoryId, int pagessize);
//获得总数
int getProductIDsListByCategoryCount(String categoryId);
//单个Model查询
Product getProduct(String productId) ;
这里我们需要更改一下iBatis原来的Product.xml配置,原来,它设计返回的是符合条件的所有Product集合,而我们要求是Product ID集合。
修改Product.xml如下:
<resultMap id="productIDsResult" class="java.lang.String">
<result property="value" column="PRODUCTID"/>
</resultMap>
<select id="getProductListByCategory" resultMap="productIDsResult" parameterClass="string">
select PRODUCTID from PRODUCT where CATEGORY = #value#
</select>
<select id="getProductListByCategoryCount" resultClass="java.lang.Integer" parameterClass="string">
select count(1) as value from PRODUCT where CATEGORY = #value#
</select>
ProductDao是IBatis DAO实现,读取Product.xml中配置:
public List getProductIDsListByCategory(String categoryId, int start, int pagessize) {
return sqlMapDaoTemplate.queryForList(
"getProductListByCategory", categoryId, start, pagessize);
}
public int getProductIDsListByCategoryCount(String categoryId){
Integer countI = (Integer)sqlMapDaoTemplate.queryForObject(
"getProductListByCategoryCount", categoryId);
return countI.intValue();
}
这样,结合配置的iBatis DAO和Jdon框架批量查询,在ProductManagerImp中创建PageIterator,当然这部分代码也可以在ProductDao实现,创建PageIterator代码如下:
public PageIterator getProductIDsListByCategory(String categoryId, int start, int count)
{
PageIterator pageIterator = null;
try {
List list = productDao.getProductIDsListByCategory(categoryId, start, count);
int allCount = productDao.getProductIDsListByCategoryCount(categoryId);
int currentCount = start + list.size();
pageIterator = new PageIterator(allCount, list.toArray(), start,
(currentCount < allCount)?true:false);
} catch (DaoException daoe) {
Debug.logError(" Dao error : " + daoe, module);
}
return pageIterator;
表现层Product批量查询实现
根据批量查询的编程步骤,在表现层主要是实现ModelListAction继承、配置和Jsp编写,下面分步说:
第一步,创建一个ModelListAction子类ProductListAction,实现两个方法:getPageIterator和findModelByKey,getPageIterator方法如下:
public PageIterator getPageIterator(HttpServletRequest request, int start,
int count) {
ProductManager productManager = (ProductManager) WebAppUtil.getService(
"productManager", request);
String categoryId = request.getParameter("categoryId");
return productManager.getProductIDsListByCategory(categoryId, start, count);
}
findModelByKey方法如下:
public Model findModelByKey(HttpServletRequest request, Object key) {
ProductManager productManager = (ProductManager) WebAppUtil.getService(
"productManager", request);
return productManager.getProduct((String)key);
}
由于我们实现的是查询一个商品目录下所有商品功能,因此,需要显示商品目录名称,而前面操作的都是Product模型,所以在显示页面也要加入商品目录Category模型,我们使用ModelListAction的customizeListForm方法:
public void customizeListForm(ActionMapping actionMapping,
ActionForm actionForm, HttpServletRequest request,
ModelListForm modelListForm) throws Exception {
ModelListForm listForm = (ModelListForm) actionForm;
ProductManager productManager = (ProductManager) WebAppUtil.getService(
"productManager", request);
String categoryId = request.getParameter("categoryId");
Category category = productManager.getCategory(categoryId);
listForm.setOneModel(category);
}
第二步,配置struts-config.xml,配置ActionForm和Action:
<form-bean name="productListForm" type="com.jdon.strutsutil.ModelListForm"/>
action配置如下:
<action path="/shop/viewCategory"
type="com.jdon.framework.samples.jpetstore.presentation.action.ProductListAction"
name="productListForm" scope="request"
validate="false" >
<forward name="success" path="/catalog/Category.jsp"/>
</action>
第三步,编写Category.jsp
从productListForm中取出我们要显示两个模型,一个是oneModel中的Category;另外一个是Product Model集合list,Jsp语法如下:
<bean:define id="category" name="productListForm" property="oneModel" />
<bean:define id="productList" name="productListForm" property="list" />
我们可以显示商品目录名称如下:
<h2><bean:write name="category" property="name" /></h2>
这样我们就可以遍历productList中的Product如下:
<logic:iterate id="product" name="productList" >
<tr bgcolor="#FFFF88">
<td><b><html:link paramId="productId" paramName="product" paramProperty="productId" page="/shop/viewProduct.shtml"><font color="BLACK"><bean:write name="product" property="productId" /></font></html:link></b></td>
<td><bean:write name="product" property="name" /></td>
</tr>
</logic:iterate>
加上分页标签库如下:
<MultiPages:pager actionFormName="productListForm " page="/shop/viewCategory.do"
paramId="categoryId" paramName="category" paramProperty="categoryId">
<MultiPages:prev><img src="../images/button_prev.gif" border="0"></MultiPages:prev>
<MultiPages:index />
<MultiPages:next><img src="../images/button_next.gif" border="0"></MultiPages:next>
</MultiPages:pager>
至此,一个商品目录下的所有商品批量查询功能完成,由于是基于框架的模板化编程,直接上线运行成功率高。
商品搜索批量查询:
参考上面步骤,商品搜索也可以顺利实现,从后台到前台按照批量查询这条线索分别涉及的类有:
持久层实现:ProductDao中的三个方法:
List searchProductIDsList(String keywords, int start, int pagessize); //ID集合
int searchProductIDsListCount(String keywords); //总数
Product getProduct(String productId) ; //单个Model
表现层:建立ProductSearchAction类,配置struts-config.xml如下:
<action path="/shop/searchProducts"
type="com.jdon.framework.samples.jpetstore.presentation.action.ProductSearchAction"
name="productListForm" scope="request"
validate="false">
<forward name="success" path="/catalog/SearchProducts.jsp"/>
</action>
与前面使用的都是同一个ActionForm:productListForm
编写SearchProducts .jsp,与Category.jsp类似,相同的是ActionForm;不同的是action。
商品条目Item批量查询
条目Item批量实现与Product批量查询类似:
持久层:ItemDao提供三个方法:
List getItemIDsListByProduct(String productId, int start, int pagessize);//ID集合
int getItemIDsListByProductCount(String productId);//总数
Item getItem(String itemId); //单个Model
表现层:创建一个ItemListAction继承ModelListAction:完成getPageIterator和findModelByKey,如下:
public class ItemListAction extends ModelListAction {
public PageIterator getPageIterator(HttpServletRequest request, int start,
int count) {
ProductManager productManager = (ProductManager) WebAppUtil.getService(
"productManager", request);
String productId = request.getParameter("productId");
return productManager.getItemIDsListByProduct(productId, start, count);
}
public Model findModelByKey(HttpServletRequest request, Object key) {
ProductManager productManager = (ProductManager) WebAppUtil.getService(
"productManager", request);
return productManager.getItem((String)key);
}
public void customizeListForm……….
}
与前面的ProductListAction相比,非常类似,不同的是Model名称不一样,一个是Product一个是Item;
struts-config.xml配置如下:
<form-bean name="itemListForm" type="com.jdon.strutsutil.ModelListForm"/>
<action path="/shop/viewProduct"
type="com.jdon.framework.samples.jpetstore.presentation.action.ItemListAction"
name="itemtListForm" scope="request"
validate="false">
<forward name="success" path="/catalog/Product.jsp"/>
</action>
比较前面product的配置,非常类似,其实itemListForm和productListForm是同一个ModelListForm类型,可以合并起来统一命名为listForm,节省ActionForm的配置。
Product.jsp页面与前面的Category.jsp SearchProdcuts.jsp类似。
<bean:define id="product" name="itemListForm" property="oneModel" />
<bean:define id="itemList" name="itemListForm" property="list" />
分页显示:
<MultiPages:pager actionFormName="itemListForm" page="/shop/viewProduct.do"
paramId="productId" paramName="product" paramProperty="productId">
…..
商品条目Item单条查询
单个显示属于CRUD中的一个查询功能,我们需要建立Model对应的ModelForm,将Item的字段拷贝到ItemForm中。配置这个ActionForm如下:
<form-bean name="itemForm"
type="com.jdon.framework.samples.jpetstore.presentation.form.ItemForm"/>
第二步:因为这个功能属于CRUD一种,无需编程,但是需要配置jdonframework.xml:
<model key="itemId" class ="com.jdon.framework.samples.jpetstore.domain.Item">
<actionForm name="itemForm"/>
<handler>
<service ref="productManager">
<getMethod name="getItem" />
</service>
</handler>
</model>
配置中只要一个方法getMethod就可以,因为只用到CRUD中的读取方式。
第三步:配置struts-config.xml如下:
<action path="/shop/viewItem" type="com.jdon.strutsutil.ModelDispAction"
name="itemForm" scope="request"
validate="false">
<forward name="success" path="/catalog/Item.jsp" />
<forward name="failure" path="/catalog/Product.jsp" />
</action>
第四步编辑Item.jsp,现在开始发现一个问题,Item.jsp中不只是显示Item信息,还有Product信息,而前面我们定义的是Item信息,如果使得Item.jsp显示Product信息呢,这就从设计起源Domain Model上考虑,在Item的Model中有Product引用:
private Product product;
public Product getProduct() { return product; }
public void setProduct(Product product) { this.product = product; }
Item和Product的多对一关系其实应该在域建模开始就考虑到了。
那么,我们只要在持久层查询Item时,能够将其中的Product字段查询就可以。在持久层的iBatis的Product.xml实现有下列SQL语句:
<select id="getItem" resultMap="resultWithQuantity" parameterClass="string">
select
I.ITEMID, LISTPRICE, UNITCOST, SUPPLIER, I.PRODUCTID, NAME,
DESCN, CATEGORY, STATUS, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5, QTY
from ITEM I, INVENTORY V, PRODUCT P where P.PRODUCTID = I.PRODUCTID and I.ITEMID = V.ITEMID and I.ITEMID = #value#
</select>
这段语法实际在查询Item时,已经将Product查询出来,这样Item Model中已经有Product数据,因为ActionForm是Model映射,因此,前台Jsp也可以显示Product数据。
在Item.jsp中,进行下面定义:
<bean:define id="product" name="itemForm " property="product" />
<bean:define id="item" name="itemForm " />
将itemForm中product属性定义为product即可;这样不必大幅度修改原来的Item.jsp了。
整理模块配置
商品模块功能完成,struts提供了多模块开发,因此我们可以将这一模块单独保存在一个配置中:/WEB-INF/struts-config-catalog.xml,这样以后扩展修改起来方便。
购物车模块实现
购物车属于一种有状态数据,也就是说,购物车的scope生命周期是用户,除非这个用户离开,否则购物车一直在内存中存在。
有态POJO服务
现在有两种解决方案:
第一,将购物车状态作为数据类,保存到ActionForm中,设置scope为session,这种形式下,对购物车的数据操作如加入条目等实现不很方便,iBatis-jpetstore 4.0.5就采取这个方案,在数据类Cart中存在大量数据操作方法,那么Cart这个类到底属于数据类Model?还是属于处理服务类呢?
在我们J2EE编程中,通常使用两种类来实现功能,一种是数据类,也就是我们设计的Model;一种是服务类,如POJO服务或EJB服务,服务属于一种处理器,处理过程。使用这两种分类比较方便我们来解析业务需求,EJB中实体Bean和Session Bean也是属于这两种类型。
iBatis-jpetstore 4.0.5则是将服务和数据类混合在一个类中,这也属于一种设计,但是我们认为它破坏了解决问题的规律性,而且造成数据和操作行为耦合性很强,在设计模式中我们还使用桥模式来分离抽象和行为,因此这种做法可以说是反模式的。那么我们采取数据类和服务分离的方式方案来试试看:
第二.购物车功能主要是对购物车这个Model的CRUD,与通常的CRUD区别是,数据是保存到HttpSession,而不是持久化到数据库中,是数据状态保存不同而已。所以如果我们实现一个CartService,它提供add或update或delete等方法,只不过操作对象不是数据库,而是其属性为购物车Cart,然后将该CarService实例保存到HttpSession,实现每个用户一个CartService实例,这个我们成为有状态的POJO服务。
这种处理方式类似EJB架构处理,如果我们业务服务层使用EJB,那么使用有态会话Bean实现这个功能。
现在问题是,Jdon框架目前好像没有提供有状态POJO服务实例的获得,那么我们自己在WebAppUtil.getService获得实例后,保存到HttpSession中,下次再到HttpSession中获得,这种有状态处理需要表现层更多代码,这就不能使用Jdon框架的CRUD配置实现了,需要我们代码实现ModelHandler子类。
考虑到可能在其他应用系统还有这种需求,那么能不能将有状态的POJO服务提炼到Jdon框架中呢?关键使用什么方式加入框架,因为这是设计目标服务实例的获得,框架主要流程代码又不能修改,怎么办?
Jdon框架的AOP功能在这里显示了强大灵活性,我们可以将有状态的POJO服务实例获得作为一个拦截器,拦截在原来POJO服务实例获得之前。在Jdon框架设计中,目标服务实例的获得一般只有一次。
创建有状态POJO服务拦截器com.jdon.aop.interceptor. StatefulInterceptor,再创建一个空接口:com.jdon.controller.service.StatefulPOJOService,需要实现有状态实例的POJO类只要继承这个接口就可以。
配置aspect.xml,加入这个拦截器:
<interceptor name="statefulInterceptor" class="com.jdon.aop.interceptor.StatefulInterceptor"
pointcut="pojoServices" />
这里需要注意的是:你不能让一个POJO服务类同时继承Poolable,然后又继承Stateful,因为这是两种不同的类型,前者适合无状态POJO;后者适合CartService这样有状态处理;这种选择和EJB的有态/无态选择是一样的。
Model和Service设计
购物车模块主要围绕域模型Cart展开,需要首先明确Cart是一个什么样的业务模型,购物车页面是类似商品条目批量查询页面,不过购物车中显示的不但是商品条目,还有数量,那么我们专门创建一个Model来指代它,取名为CartItem,CartItem是Item父集,多了一个数量。
这样购物车页面就是CartItem的批量查询页面,然后还有CartItem的CRUD操作,所以购物车功能主要是CartItem的CRUD和批量查询功能。
iBatis 4.0.5原来设计了专门Cart Model,其实这个Cart主要是一个功能类,因为它的数据项只有一个Map和List,这根本不能代表业务需求中的一个模型。虽然iBatis 4..0.5也可以自圆其说实现了购物车功能,但是这种实现是随心所欲,无规律性可遵循,因而以后维护起来也是困难,维护人员理解困难,修改起来也没有章程可循,甚至乱改一气。
CartItem可以使用iBatis原来的CartItem,这样也可保持Cart.jsp页面修改量降低。删除原来的Cart这个Model,建立对应的CartService,实现原来的Cart一些功能。
public interface CartService {
CartItem getCartItem(String itemId);
void addCartItem(EventModel em);
void updateCartItem(EventModel em);
void deleteCartItem(EventModel em);
PageIterator getCartItems();
}
CartServiceImp是CartService子类,它是一个有状态POJO服务,代码简要如下:
public class CartServiceImp implements CartService, Stateful{
private ProductManager productManager;
//将原来iBatis 中Cart类中两个属性移植到CartServiceImp中
private final Map itemMap = Collections.synchronizedMap(new HashMap());
private List itemList = new ArrayList();
public CartServiceImp(ProductManager productManager) {
super();
this.productManager = productManager;
}
……
}
itemMap是装载CartItem的一个Map,是类属性,由于CartServiceImp是有状态的,每个用户一个实例,那么也就是每个用户有自己的itemMap列表,也就是购物车。
CartServiceImp中的 getCartItemIDs是查询购物车当前页面的购物条目,属于批量分页查询实现,这里有一个需要考量的地方,是getCartItems方法还是getCartItemIDs方法?也就是返回CartIem的实例集合还是CartItem的ItemId集合?按照前面标准的Jdon框架批量分页查询实现,应该返回CartItem的ItemId集合,然后由Jdon框架的ModelListAction根据ItemId首先从缓存中获得CartItem实例,但是本例CartItem本身不是持久化在数据库,而也是内存HttpSession中,所以ModelListAction这种流程似乎没有必要。
如果将来业务需求变化,购物车状态不是保存在内存而是数据库,这样,用户下次登陆时,可以知道他上次购物车里的商品条目,那么采取Jdon框架标准查询方案还是有一定扩展性的。
这里,我们就事论事,采取返回CartIem的实例集合,展示一下如何灵活应用Jdon框架的批量查询功能。下面是CartServiceImp 的getCartItems方法详细代码:
public PageIterator getCartItems(int start, int count) {
int offset = itemList.size() - start; //获得未显示的总个数
int pageCount = (count < offset)?count:offset;
List pageList = new ArrayList(pageCount); //当前页面记录集合
for(int i=start; i< pageCount + start;i++){
pageList.add(itemList.get(i));
}
int allCount = itemList.size();
int currentCount = start + pageCount;
return new PageIterator(allCount, pageList.toArray(new CartItem[0]), start,
(currentCount < allCount)?true:false);
}
getCartItems方法是从购物车所有条目itemList中查询获得当前页面的条目,并创建一个PageIterator。
注意,现在这个PageIterator中keys属性中装载的不是数据ID集合,而是完整的CartItem集合,因为上面代码中pageList中对象是从itemList中获得,而itemList中装载的都是CartItem。
表现层购物车显示功能
由于PageIterator中封装的是完整Model集合,而不是ID集合,所以现在表现层有两种方案,继承框架的ModelListAction;或重新自己实现一个Action,替代ModelListAction。
这里使用继承框架的ModelListAction方案,巧妙地实现我们的目的,省却编码:
public class CartListAction extends ModelListAction {
public PageIterator getPageIterator(HttpServletRequest request, int arg1, int arg2) {
CartService cartService = (CartService)WebAppUtil.getService("cartService", request);
return cartService.getCartItems();
}
public Model findModelByKey(HttpServletRequest arg0, Object key) {
return (Model)key; //因为key不是主键,而是完整的Model,直接返回
}
protected boolean isEnableCache(){
return false; //无需缓存,CartItem本身实际是在内存中。
}
}
配置struts-config.xml:
<form-beans>
<form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>
</form-beans>
<action-mappings>
<action path="/shop/viewCart"
type="com.jdon.framework.samples.jpetstore.presentation.action.CartListAction"
name="listForm" scope="request"
validate="false">
<forward name="success" path="/cart/Cart.jsp"/>
</action>
……
</action-mappings>
上面是购物车显示实现,只要调用/shop/viewCart.shtml就可以显示购物车了。
在Cart.jsp页面插入下面标签:
<logic:iterate id="cartItem" name="listForm" property="list">
….
</logic:iterate>
分页显示标签如下:
<MultiPages:pager actionFormName="listForm" page="/shop/viewCart.shtml">
<MultiPages:prev name="<font color=green><B><< Prev</B></font>"/>
<MultiPages:index />
<MultiPages:next name="<font color=green><B>Next >></B></font>"/>
</MultiPages:pager>
购物车新增删除条目功能
前面完成了购物车显示功能,下面是设计购物车的新增和删除、修改功能。
参考Jdon框架的CRUD功能实现,Model是CartItem,配置jdonframework.xml使其完成新增删除功能:
<model key="workingItemId"
class="com.jdon.framework.samples.jpetstore.domain.CartItem">
<actionForm name="cartItemForm"/>
<handler>
<service ref="cartService">
<createMethod name="addCartItem"/>
<deleteMethod name="deleteCartItem"/>
</service>
</handler>
</model>
在这个配置中,只有新增和删除方法,修改方法没有,因为购物车修改主要是其中商品条目的数量修改,它不是逐条修改,而是一次性批量修改,这里的Model是CartItem,这是购物车里的一个条目,因此如果这里写修改,也只是CartItem一个条目的修改,不符合我们要求。下面专门章节实现这个修改。
表现层主要是配置,没有代码,代码都依靠cartService中的addCartItem和deleteCartItem实现:例如:
public void addCartItem(EventModel em) {
CartItem cartItem = (CartItem) em.getModel();
String workingItemId = cartItem.getWorkingItemId();
……
}
注意addCartItem中从EventModel实例中获取的Model是CartItem,这与我们在jdonframework.xml中上述定义的Model类型是统一的。
Struts-config.xml中定义是CRUD的标准定义,注意,这里只有ModelSaveAction无需ModelViewAction,因为将商品条目加入或删除购物车这个功能没有专门的显示页面:
<action path="/shop/addItemToCart" type="com.jdon.strutsutil.ModelSaveAction"
name="cartItemForm" scope="request"
validate="false">
<forward name="success" path="/cart/Cart.jsp"/>
</action>
<action path="/shop/removeItemFromCart" type="com.jdon.strutsutil.ModelSaveAction"
name="cartItemForm" scope="request"
validate="false">
<forward name="success" path="/cart/Cart.jsp"/>
</action>
注意,调用删除功能时,需要附加action参数:
/shop/removeItemFromCart.shtml?action=delete
而/shop/addItemToCart.shtml是新增属性,缺省后面无需跟参数。
购物车条目批量修改功能
上面基本完成了购物车主要功能;购物车功能一个复杂性在于其显示功能和修改功能合并在一起,修改功能是指修改购物车里所有商品条目的数量。
既然有修改功能,而且这个修改功能比较特殊,我们需要设计一个独立的ActionForm,用来实现商品条目数量的批量修改。
首先设计一个ActionForm(ModelForm),该ModelForm主要用来实现购物车条目数量的更改,取名为CartItemsForm,其内容如下:
public class CartItemsForm extends ModelForm {
private String[] itemId;
private int[]quantity;
private BigDecimal totalCost;
…..
}
itemId和quantity设计成数组,这样,Jsp页面可以一次性提交多个itemId和quantity数值。
现在CartItemsForm已经包含前台jsp输入的数据,我们还是将其传递递交到服务层实现处理。因此建立一个Model,内容与CartItemsForm类似,这里的Model名为CartItems,实际是一个传输对象。
public class CartItems extends Model{
private String[] itemId;
private int[] quantity;
private BigDecimal totalCost;
……
}
表现层在jdonframework.xml定义配置就无需编码,配置如下:
<model key=" "
class="com.jdon.framework.samples.jpetstore.domain.CartItems">
<actionForm name="cartItemsForm"/>
<handler>
<service ref="cartService">
<updateMethod name="updateCartItems"/>
</service>
</handler>
</model>
上面配置中,Model是CartItems,ActionForm是cartItemsForm,这两个是专门为批量修改设立的。只有一个方法updateMethod。因为在这个更新功能中,没有根据主键从数据库查询Model的功能,因此,这里model的key可以为空值。
服务层CartServiceImp的updateCartItems方法实现购物车条目数量更新:
public void updateCartItems(EventModel em) {
CartItems cartItems = (CartItems) em.getModel();
try {
String[] itemIds = cartItems.getItemId();
int[] qtys = cartItems.getQuantity();
int length = itemIds.length;
for (int i = 0; i < length; i++) {
updateCartItem(itemIds[i], qtys[i]);//逐条更新购物车中的数量
}
} catch (Exception ex) {
logger.error(ex);
}
}
注意updateCartItems中从EventModel取出的是CartItems,和前面addCartItem方法中取出的是CartItem Model类型不一样,这是因为这里我们在jdonframework.xml中定义与updateCartItems相对应的Model是CartItems.
最后一步工作是Cat.jsp中加入CartItemsForm,能够在购物车显示页面有一个表单提交,客户按提交按钮,能够立即实现当前页面购物车数量的批量修改。Cat.jsp加入如下代码:
<html:form action="/shop/updateCartQuantities.shtml" method="post" >
<html:hidden property="action" value="edit" />
……
<input type="hidden" name="itemId" value="<bean:write name="cartItem" property="workingItemId"/>">
<input type="text" size="3" name="quantity" value="<bean:write name="cartItem"
property="quantity"/>" />
…….
注意,一定要有action赋值edit这一行,这样提交给updateCartQuantities.shtml实际是ModelSaveAction时,框架才知道操作性质。
购物车总价显示功能
最后,还有一个功能需要完成,在购物车显示时,需要显示当前购物车的总价格,注意不是显示当前页面的总价格,所以无法在Cart.jsp直接实现,必须遍历购物车里所有CartItem计算总数。
该功能是购物车显示时一起实现,购物车显示是通过CartListAction实现的,这个CartListAction实际是生成一个ModelListForm,如果ModelListForm能够增加一个getTotalPrice方法就可以,因此有两种实现方式:继承ModelListForm加入自己的getTotalPrice方法;第二种无需再实现自己的ModelListForm,ModelListForm可以携带一个Model,通过setOneModel即可,这个方法是在ModelListAction的子类CartListAction可以override覆盖实现的,在CartListAction加入下列方法:
protected Model setOneModel(HttpServletRequest request){
CartService cartService = (CartService)WebAppUtil.getService("cartService", request);
CartItems cartItems = new CartItems();
cartItems.setTotalCost(cartService.getSubTotal());
return cartItems;
}
我们使用空的CartItems作为携带价格总数的Model,然后在Cart.jsp中再取出来显示:
<bean:define id="cartItems " name="listForm" property="oneModel" />
<b>Sub Total: <bean:write name="cartItems" property="subTotal" format="$#,##0.00" />
将当前页面listForm中属性oneModel定义为cartItems,它实际是我们定义的CartItems,
下一行取出总价即可。
用户喜欢商品列表功能
在显示购物车时,需要一起显示该用户喜欢的商品列表,很显然这是一个批量分页查询实现,但是它有些特殊,它首先显示的第一页不是由URL调用的,而是嵌入在购物车显示中,那么只能在购物车显示页面的ModellistForm中做文章。
在上节中,在CartListAction中setOneModel方法中,使用CartItems作为价格总数的载体,现在恐怕我们也要将之作为本功能实现载体。
还有一种实现载体,就是其他scope为session的ActionForm,AccountForm很适合做这样的载体,而且和本功能意义非常吻合,所以在AccountForm/Account中增加一个myList字段,在myList字段中,放置的是该用户喜欢的商品Product集合,注意不必放置Product的主键集合,因为我们只要显示用户喜欢商品的第一页,这一页是嵌入购物车显示页面中,所以第一页显示的个数是由程序员可事先在程序中定义。
这样在Account获得时,一起将myList集合值获得。
订单模块实现
我们还是从域模型开始,Order是订单模块的核心实体,其内容可以确定如下:
public class Order extends Model {
/* Private Fields */
private int orderId;
private String username;
private Date orderDate;
private String shipAddress1;
private String shipAddress2;
…..
}
第二步,建立与Model对应的ModelForm,我们可以称之为边界模型,代码从Order拷贝过来即可。当然OrderForm还有一些特殊的字段以及初始化:
public class OrderForm extends ModelForm
private boolean shippingAddressRequired;
private boolean confirmed;
static {
List cardList = new ArrayList();
cardList.add("Visa");
cardList.add("MasterCard");
cardList.add("American Express");
CARD_TYPE_LIST = Collections.unmodifiableList(cardList);
}
public OrderForm(){
this.shippingAddressRequired = false;
this.confirmed = false;
}
…..
}
第三步,建立Order Model的业务服务接口,如下:
public interface OrderService {
void insertOrder(Order order);
Order getOrder(int orderId);
List getOrdersByUsername(String username);
}
第四步,实现OrderService的POJO子类:OrderServiceImp。
第五步,表现层实现,本步骤可和第四步同时进行。
OrderService中有订单的插入创建功能,我们使用Jdon框架的CRUD中create配置实现,配置struts-config.xml和jdonframework.xml:
<form-bean name="orderForm"
type="com.jdon.framework.samples.jpetstore.presentation.form.OrderForm"/>
<model key="orderId"
class="com.jdon.framework.samples.jpetstore.domain.Order">
<actionForm name="orderForm"/>
<handler>
<service ref="orderService">
<createMethod name="insertOrder"/>
</service>
</handler>
</model>
第六步:根据逐个实现界面功能,订单的第一个功能创建一个新的订单,在新订单页面NewOrderForm.jsp推出之前,这个页面的ActionForm已经被初始化,是根据购物车等Cart其他Model数据初始化合成的。
新订单页面初始化
根据Jdon框架中CRUD功能实现,初始化一个ActionForm有两种方法:一继承ModelHandler实现initForm方法;第二通过jdonframework.xml的initMethod方法配置。
这两个方案选择依据是根据用来初始化的数据来源什么地方。
订单表单初始化实际是来自购物车信息或用户账号信息,这两个都事先保存在HttpSession中,购物车信息是通过有态CartService实现的,所以这些数据来源是和request相关,那么我们选择第一个方案。
继承ModelHandler之前,我们可以考虑首先继承ModelHandler的子类XmlModelHandler,只要继承initForm一个方法即可,这样节省其他方法编写实现。
public class OrderHandler extends XmlModelHandler {
public ModelForm initForm(HttpServletRequest request) throws
Exception{
HttpSession session = request.getSession();
AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");
OrderForm orderForm = createOrderForm(accountForm);
CartService cartService = (CartService)WebAppUtil.getService("cartService", request);
orderForm.setTotalPrice(cartService.getSubTotal());
//below can read from the user's creditCard service;
orderForm.setCreditCard("999 9999 9999 9999");
orderForm.setExpiryDate("12/03");
orderForm.setCardType("Visa");
orderForm.setCourier("UPS");
orderForm.setLocale("CA");
orderForm.setStatus("P");
Iterator i = cartService.getAllCartItems().iterator();
while (i.hasNext()) {
CartItem cartItem = (CartItem) i.next();
orderForm.addLineItem(cartItem);
}
return orderForm;
}
private OrderForm createOrderForm(AccountForm account){
……
}
}
ModelHandler的initForm继承后,因为这使用了Jdon的CRUD功能实现,这里我们只使用到CRUD中的创建功能,因此,findModelBykey方法就无需实现,或者可以在jdonframework.xml中配置该方法实现。
考虑到在initForm执行后,需要推出一个NewOrderForm.jsp页面,这是一个新增性质的页面。所以在struts-config.xml
<action path="/shop/newOrderForm" type="com.jdon.strutsutil.ModelViewAction"
name="orderForm" scope="request" validate="false">
<forward name="create" path="/order/NewOrderForm.jsp"/>
</action>
订单确认流程
新的订单页面推出后,用户需要经过两个流程才能确认保存,这两个流程是填写送货地址以及再次完整确认。这两个流程实现的动作非常简单,就是将OrderForm中的shippingAddressRequired字段和confirm字段赋值,相当于简单的开关,这是一个很简单的动作,可以有两种方案:直接在jsp表单中将这两个值赋值;直接使用struts的Action实现。后者需要编码,而且不是非有这个必要,只有第一个方案行不通时才被迫实现。
第一步:填写送货地址
使用Jdon框架的推出纯Jsp功能的Action配置struts-config.xml:
<action path="/shop/shippingForm" type="com.jdon.strutsutil.ForwardAction"
name="orderForm" scope="session" validate="false">
<forward name="forward" path="/order/ShippingForm.jsp"/>
</action>
这是实现送货地址页面的填写,使用的还是OrderForm。更改前面流程NewOrderForm.jsp中的表单提交action值为本action path: shippingForm.shtml:
<html:form action="/shop/shippingForm.shtml" styleId="orderForm" method="post" >
……
</html:form>
在ShippingForm.jsp中增加将shippingAddressRequired赋值的字段:
<html:hidden name="orderForm" property="shippingAddressRequired" value="false"/>
第二步:确认订单
类似上述步骤,配置struts-config.xml:
<action path="/shop/confirmOrderForm" type="com.jdon.strutsutil. ForwardAction"
name="orderForm" scope="session" validate="false">
<forward name="forward" path="/order/ConfirmOrder.jsp"/>
</action>
将上一步ShippingForm.jsp的表单action改为本action的path: confirmOrderForm.shtml:
<html:form action="/shop/confirmOrderForm.shtml" styleId="orderBean" method="post" >
修改ConfirmOrder.jsp中提交的表单为最后一步,保存订单newOrder.shtml:
<html:link page="/shop/newOrder.shtml?confirmed=true"><img border="0" src="../images/button_continue.gif" /></html:link>
第三步:下面是创建数据保存功能实现:
<action path="/shop/newOrder" type="com.jdon.strutsutil.ModelSaveAction"
name="orderForm" scope="session"
validate="true" input="/order/NewOrderForm.jsp">
<forward name="success" path="/order/ViewOrder.jsp"/>
</action>
ModelSaveAction是委托ModelHandler实现的,这里有两种方式:配置方式:在jdonframework.xml中配置了方法插入;第二种是实现代码,这里原本可以使用配置方式实现,但是因为在功能上有要求:在订单保存后,需要清除购物车数据,因此只能使用代码实现方式,在ModelHandler中实现子类方法serviceAction:
public void serviceAction(EventModel em, HttpServletRequest request) throws java.lang.Exception {
try {
CartService cartService = (CartService) WebAppUtil.getService("cartService", request);
cartService.clear(); //清楚购物车数据
OrderService orderService = (OrderService) WebAppUtil.getEJBService("orderService", request);
switch (em.getActionType()) {
case Event.CREATE:
Order order = (Order) em.getModel();
orderService.insertOrder(order);
cartService.clear();
break;
case Event.EDIT:
break;
case Event.DELETE:
break;
}
} catch (Exception ex) {
throw new Exception(" serviceAction Error:" + ex);
}
}
用户订单列表
用户查询自己的订单列表功能很明显可以使用Jdon框架的批量查询事先。
在struts-config.xml中配置ModelListForm如下:
<form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>
建立继承ModelListAction子类OrderListAction:
public class OrderListAction extends ModelListAction {
public PageIterator getPageIterator(HttpServletRequest request, int start, int count) {
OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);
HttpSession session = request.getSession();
AccountForm accountForm = (AccountForm) session.getAttribute("accountForm");
if (accountForm == null) return new PageIterator();
return orderService.getOrdersByUsername(accountForm.getUsername(), start, count);
}
public Model findModelByKey(HttpServletRequest request, Object key) {
OrderService orderService = (OrderService) WebAppUtil.getService("orderService", request);
return orderService.getOrder((Integer)key);
}
}
修改OrderService, 将获得Order集合方法改为:
public class OrderService{
PageIterator getOrdersByUsername(String username, int start, int count)
….
}
根据Jdon批量查询要求,使用iBatis实现返回ID集合以及符合条件的总数。
最后编写ListOrders.jsp,两个语法:logic:iterator 和MultiPages
配置jdon框架启动
目前我们有四个struts-config.xml,前面每个模块一个配置:
/WEB-INF/struts-config.xml 主配置
/WEB-INF/struts-config-catalog.xml 商品相关配置
/WEB-INF/struts-config-security.xml 用户相关配置
/WEB-INF/struts-config-cart.xml 购物车相关配置
/WEB-INF/struts-config-order.xml 订单相关配置
本项目只有一个jdonframework.xml,当然我们也可以创建多个jdonframework.xml,然后在其struts-config.xml中配置。
<plug-in className="com.jdon.strutsutil.InitPlugIn">
<set-property property="modelmapping-config" value="jdonframework_iBATIS.xml" />
</plug-in>
修改iBatis的DAO配置
iBatis 4.0.5中原来的配置过于扩张(从持久层扩张到业务层),AccountDao每个实例获得都需要通过daoManager.getDao这样形式,而使用Jdon框架后,AccountDao等DAO实例获得无需特别语句,我们只要在AccountService直接引用AccountDao接口,至于AccountDao的具体实例,通过Ioc注射进入即可。
因此,在jdonframework.xml中有如下配置:
<pojoService name="accountDao"
class="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.AccountSqlMapDao"/>
<pojoService name="accountService"
class="com.jdon.framework.samples.jpetstore.service.bo.AccountServiceImp"/>
<pojoService name="productManager"
class="com.jdon.framework.samples.jpetstore.service.bo.ProductManagerImp"/>
而AccountServiceImp代码如下:
public class AccountServiceImp implements AccountService, Poolable {
private AccountDao accountDao;
private ProductManager productManager;
public AccountServiceImp(AccountDao accountDao,
ProductManager productManager){
this.accountDao = accountDao;
this.productManager = productManager;
}
AccountServiceImp需要两个构造方法实例,这两个中有一个是AccountDao。
按照iBatis原来的AccountDao子类AccountSqlMapDao有一个构造方法参数是DaoManager,但是我们无法生成自己的DaoManager实例,因为DaoManager是由dao.xml配置文件读取后生成的,这是一个动态实例;那只有更改AccountSqlMapDao构造方法了。
根源在于BaseSqlMapDao类,BaseSqlMapDao是一个类似JDBC模板类,每个Dao都继承它,现在我们修改BaseSqlMapDao如下:
public class BaseSqlMapDao extends DaoTemplate implements SqlMapExecutor{
…..
}
BaseSqlMapDao是XML配置和JDBC模板的结合体,在这个类中,这两者搭配在一起,在其中实现SqlMapExecutor各个子方法。
我们再创建一个DaoManagerFactory,专门根据配置文件创建DaoManager实例:
主要方法如下:
Reader reader = Resources.getResourceAsReader(daoResource);
daoManager = DaoManagerBuilder.buildDaoManager(reader);
其中daoResource是dao.xml配置文件,这个配置是在jdonframework.xml中配置:
<pojoService name="daoManagerFactory"
class="com.jdon.framework.samples.jpetstore.persistence.dao.DaoManagerFactory">
<constructor
value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/dao.xml"/>
</pojoService>
这样,我们可以通过改变jdonframework.xml配置改变dao.xml配置。
Dao.xml配置如下:
<daoConfig>
<context>
<transactionManager type="SQLMAP">
<property name="SqlMapConfigResource"
value="com/jdon/framework/samples/jpetstore/persistence/dao/sqlmapdao/sql/sql-map-config.xml"/>
</transactionManager>
<dao interface="com.ibatis.sqlmap.client.SqlMapExecutor"
implementation="com.jdon.framework.samples.jpetstore.persistence.dao.sqlmapdao.BaseSqlMapDao"/>
</context>
</daoConfig>
在dao.xml中,我们只配置一个JDBC模板,而不是将所有的如AccountDao配置其中,因为我们需要iBatis只是它的JDBC模板,实现持久层方便的持久化,仅此而已!
DaoManagerFactory为我们生产了DaoManager实例,那么如何赋值到BaseSqlMapDao中,我们设计一个创建BaseSqlMapDao工厂如下:
public class SqlMapDaoTemplateFactory {
private DaoManagerFactory daoManagerFactory;
public SqlMapDaoTemplateFactory(DaoManagerFactory daoManagerFactory) {
this.daoManagerFactory = daoManagerFactory;
}
public SqlMapExecutor getSqlMapDaoTemp(){
DaoManager daoManager = daoManagerFactory.getDaomanager();
return (SqlMapExecutor)daoManager.getDao(SqlMapExecutor.class);
}
}
通过getSqlMapDaoTemp方法,由DaoManager.getDao方法获得BaseSqlMapDao实例,BaseSqlMapDao的接口是SqlMapExecutor,这样我们通过DaoManager获得一个JDBC模板SqlMapExecutor的实例。
这样,在AccountDao各个子类AccountSqlMapDao中,我们只要通过SqlMapDaoTemplateFactory获得SqlMapExecutor实例,委托SqlMapExecutor实现JDBC操作,如下:
public class AccountSqlMapDao implements AccountDao {
//iBatis数据库操作模板
private SqlMapExecutor sqlMapDaoTemplate;
//构造方法
public AccountSqlMapDao(SqlMapDaoTemplateFactory sqlMapDaoTemplateFactory) {
sqlMapDaoTemplate = sqlMapDaoTemplateFactory.getSqlMapDaoTemp();
}
//查询数据库
public Account getAccount(String username) throws SQLException{
return (Account)sqlMapDaoTemplate.queryForObject("getAccountByUsername", username);
}
部署调试
当在JBoss或Tomcat控制台 或者日志文件中出现下面字样标识Jdon框架安装启动成功:
<======== Jdon Framework started successfully! =========>
Jdon框架启动成功后,以后出现的错误基本是粗心大意的问题,仔细分析会很快找到原因,相反,如果编程时仔细慢一点,则后面错误出现概率很小。
使用Jdon框架开发Jpetstore, 一次性调试通过率高,一般问题都是存在数据库访问是否正常,一旦正常,主要页面就出来了,其中常见问题是jsp页面和ActionForm的字段不对应,如jsp页面显示如下错误:
No getter method available for property creditCardTypes for bean under name orderForm
表示在OrderForm中没有字段creditCardTypes,或者有此字段,但是大小写错误等粗心问题。
如果jsp页面或后台log记录显示:
System error! please call system Admin.java.lang.Exception
一般这是由于前面出错导致,根据记录向前搜索,搜索到第一个出错记录:
2005-07-07 11:55:16,671 [http-8080-Processor25] DEBUG com.jdon.container.pico.PicoContainerWrapper - getComponentClass: name=orderService
2005-07-07 11:55:16,671 [http-8080-Processor25] ERROR com.jdon.aop.reflection.MethodConstructor - no this method name:insertOrder
第一个出错是在MethodConstructor报错,没有insertOrder方法,根据上面一行是orderService,那么检查orderService代码看看有无insertOrder:
public interface OrderService {
void insertOrder(Order order);
Order getOrder(int orderId);
List getOrdersByUsername(String username);
}
OrderService接口中是有insertOrder方法,那么为什么报错没有呢?仔细检查一下,是不是insertOrder的方法参数有问题,哦, 因为orderService的调用是通过jdonframework.xml下面配置进行的:
<model key="orderId"
class="com.jdon.framework.samples.jpetstore.domain.Order">
<actionForm name="orderForm"/>
<handler>
<service ref="orderService">
<createMethod name="insertOrder"/>
</service>
</handler>
</model>
而根据Jdon框架要求,使用配置实现ModelHandler,则要求OrderService的insertOrder方法参数必须是EventModel,更改OrderService的insertOrder方法如下:
public interface OrderService {
void insertOrder(EventModel em);
}
同时,修改OrderService的子类代码OrderServiceImp:
public void insertOrder(EventModel em) {
Order order = (Order)em.getModel();
try{
orderDao.insertOrder(order);
}catch(Exception daoe){
Debug.logError(" Dao error : " + daoe, module);
em.setErrors("db.error");
}
}
注意em.setErrors方法,该方法可向struts页面显示出错信息,db.error是在本项目的application.properties中配置的。
关于本次页面出错问题,还有更深缘由,因为我们在jdonframework.xml中中定义了createMethod,而根据前面已经有ModelHandler子类代码OrderHandler实现,所以,这里不用配置实现,jdonframework.xml的配置应该如下:
<model key="orderId"
class="com.jdon.framework.samples.jpetstore.domain.Order">
<actionForm name="orderForm"/>
<handler class="com.jdon.framework.samples.jpetstore.presentation.action.OrderHandler"/>
</model>
直接定义了hanlder的class是OrderHandler,在OrderHandler中的serviceAction我们使用代码调用了OrderService的insertOrder方法,如果使用这样代码调用,无需要求OrderService的insertOrder的参数是EventModel了。
总结
Jpetstore整个开发大部分基于Jdon框架开发,特别是表现层,很少直接接触使用struts原来功能,Jdon框架的表现层架构基本让程序员远离了struts的烦琐开发过程,又保证了struts的MVC实现。
Jpetstore中只有SignonAction这个类是直接继承struts的DispatchAction,这个功能如果使用基于J2EE容器的安全认证实现(见JdonNews),那么Jpetstore全部没有用到struts的Action,无需编写Action代码;ActionForm又都是Model的拷贝,Action和ActionForm是struts编码的两个主要部分,这两个部分被Jdon框架节省后,整个J2EE的Web层开发方便快速,而且容易得多。
这说明Jdon框架确实是一款快速开发J2EE工具,而且是非常轻量的。
纵观Jpetstore系统,主要有三个层的配置文件组成,持久层由iBatis的Product.xml等配置文件组成;服务层由jdon框架的jdonframework.xml组成;表现层由struts的struts-config.xml和jdonframework.xml组成;剩余代码基本是Model之类实现。