J2EE应用系统JPetstore以IBatis.com的iBATIS-Jpetstore为例,我们使用Jdon框架对其重构成为Jdon-JPetstore,本章开发环境是Eclipse(本章案也适用其他开发工具),部署运行环境是JBoss。 Eclipse是一个非常不错的开源开发工具,使用Eclipse开发和使用JBuilder将有完全不同的开发方式。我们使用Eclipse基于Jdon框架开发一个完全Web应用,或者可以说,开发一个轻量(lightweight)的J2EE应用系统。 通过这个轻量系统开发,说明Jdon框架对完全POJO架构的支持,因为EJB分布式集群计算能力,随着访问量提升,可能需要引入EJB架构,这时只要使用EJB session Bean包装POJO服务则可以无缝升级到EJB。使用Jdon框架可实现方便简单地架构升迁。 Eclipse安装简要说明1.下载Eclipse:在http://www. 的下载点中选择tds ISP 比较快。 2.安装免费插件: 编辑Jsp需要lomboz : http://www./projects/download.jsp 注意对应的Eclipse版本。 编辑XML,使用Xmlbuddy: http:/// 基本上这两个插件就够了。
如果希望开发 Hibernate,插件: http://www./hibernatesync
代码折叠 http://www./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./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./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之类实现。 |
|
来自: coolboybobo > 《jpetstore》