以IBatis.com的iBATIS-Jpetstore為例,我們使用Jdon框架對(duì)其重構(gòu)成為Jdon-JPetstore,本章開(kāi)發(fā)環(huán)境是Eclipse(本章案也適用其他開(kāi)發(fā)工具),部署運(yùn)行環(huán)境是JBoss。
Eclipse是一個(gè)非常不錯(cuò)的開(kāi)源開(kāi)發(fā)工具,使用Eclipse開(kāi)發(fā)和使用JBuilder將有完全不同的開(kāi)發(fā)方式。我們使用Eclipse基于Jdon框架開(kāi)發(fā)一個(gè)完全Web應(yīng)用,或者可以說(shuō),開(kāi)發(fā)一個(gè)輕量(lightweight)的J2EE應(yīng)用系統(tǒng)。
通過(guò)這個(gè)輕量系統(tǒng)開(kāi)發(fā),說(shuō)明Jdon框架對(duì)完全POJO架構(gòu)的支持,因?yàn)?/span>EJB分布式集群計(jì)算能力,隨著訪問(wèn)量提升,可能需要引入EJB架構(gòu),這時(shí)只要使用EJB session Bean包裝POJO服務(wù)則可以無(wú)縫升級(jí)到EJB。使用Jdon框架可實(shí)現(xiàn)方便簡(jiǎn)單地架構(gòu)升遷。
1.下載Eclipse:在http://www.eclipse.org 的下載點(diǎn)中選擇tds ISP 比較快。
2.安裝免費(fèi)插件:
編輯Jsp需要lomboz : http://www.objectlearn.com/projects/download.jsp
注意對(duì)應(yīng)的Eclipse版本。
編輯XML,使用Xmlbuddy: http://xmlbuddy.com/
基本上這兩個(gè)插件就夠了。
如果希望開(kāi)發(fā) Hibernate,插件:
http://www.binamics.com/hibernatesync
代碼折疊
http://www.coffee-bytes.com/eclipse/update-site/site.xml
3. 關(guān)鍵學(xué)習(xí)ant的編寫(xiě)build.xml,在build.xml將你的jsp javaclass打包成war或jar或ear就可以。都可以使用ant的jar打包,build.xml只要參考一個(gè)模板就可以:SimpleJdonFrameworkTest.rar 有一個(gè)現(xiàn)成的,可拷貝到其它項(xiàng)目后修改后就可用.
然后在這個(gè)模板上修改,參考 ant的命令參考:
http://ant.apache.org/manual/tasksoverview.html
網(wǎng)上有中文版的ant參考,在google搜索就能找到。
關(guān)鍵是學(xué)習(xí)ant的build.xml編輯,SimpleJdonFrameworkTest.rar 有一個(gè)現(xiàn)成的,可拷貝到其它項(xiàng)目后修改后就可用.
用ant編譯替代Eclipse的缺省編譯:選擇項(xiàng)目屬性-->Builders ---> new --> Ant Builder --->選擇本項(xiàng)目的build.xml workspace 選擇本項(xiàng)目
eclipse開(kāi)發(fā)就這些,非常簡(jiǎn)單,不象Jbuilder那樣智能化,導(dǎo)致項(xiàng)目目錄很大。eclipse只負(fù)責(zé)源碼開(kāi)發(fā),其它都由ant負(fù)責(zé)
Jdon-JPetstore除了保留iBATIS-JPetstore 4.0.5的域模型、持久層ibatis實(shí)現(xiàn)以及Jsp頁(yè)面外,其余部分因?yàn)槭褂昧?/span>Jdon框架而和其有所不同。
保留域模型和Jsp頁(yè)面主要是在不更改系統(tǒng)需求的前提下,重構(gòu)其架構(gòu)實(shí)現(xiàn)為Jdon框架,通過(guò)對(duì)比其原來(lái)的實(shí)現(xiàn)或Spring的JPetstore實(shí)現(xiàn),可以發(fā)現(xiàn)Jdon框架的使用特點(diǎn)。
在原來(lái)jpetstore iBatis包會(huì)延伸到表現(xiàn)層,例如它的分頁(yè)查詢(xún)PaginatedList,iBatis只是持久層框架,它的作用范圍應(yīng)該只限定在持久層,這是它的專(zhuān)業(yè)范圍,如果超過(guò)范圍,顯得 ….。所以,在Jdon-Jpetstore中將iBatis封裝在持久層(砍掉PaginatedList這只太長(zhǎng)的手),Jdon框架是一種中間層框架,聯(lián)系前后臺(tái)的工作應(yīng)該由Jdon這樣的中間層框架完成。
在iBatis 4.0.5版本中,它使用了一個(gè)利用方法映射Reflection的小框架,這樣,將原來(lái)需要在Action實(shí)現(xiàn)方法整入了ActionForm中實(shí)現(xiàn),ActionForm變成了一個(gè)復(fù)雜的對(duì)象:頁(yè)面表單抽象以及與后臺(tái)Service交互,在ActionForm中調(diào)用后臺(tái)服務(wù)是通過(guò)單態(tài)模式實(shí)現(xiàn),這是在一般J2EE開(kāi)發(fā)中忌諱的一點(diǎn),關(guān)于單態(tài)模式的討論可見(jiàn):http://www.jdon.com/jive/article.jsp?forum=91&thread=17578
Jdon框架使用了微容器替代單態(tài),消除了Jpetstore的單態(tài)隱患,而且也簡(jiǎn)化了ActionForm和服務(wù)層的交互動(dòng)作(通過(guò)配置實(shí)現(xiàn))。
首先,我們需要從域建模開(kāi)始,建立正確的領(lǐng)域模型,以用戶(hù)賬號(hào)為例,根據(jù)業(yè)務(wù)需求我們確立用戶(hù)賬號(hào)的域模型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是主鍵。
域模型建立好之后,就可以花開(kāi)兩朵各表一支,表現(xiàn)層和持久層可以同時(shí)開(kāi)發(fā),先談?wù)劤志脤雨P(guān)于用戶(hù)模型的CRUD功能實(shí)現(xiàn)。
主要是用戶(hù)的新增和修改,主要用于注冊(cè)新用戶(hù)和用戶(hù)資料修改。
public interface AccountDao {
Account getAccount(String username); //獲得一個(gè)Account
void insertAccount(Account account); //新增
void updateAccount(Account account); //修改
}
持久層可以使用多種技術(shù)實(shí)現(xiàn),例如Jdon框架的JdbcTemp代碼實(shí)現(xiàn)比較方便,如果你的sql語(yǔ)句可能經(jīng)常改動(dòng),使用iBatis的sql語(yǔ)句XML定義有一定好處,本例程使用Jpetstore原來(lái)的持久層實(shí)現(xiàn)iBatis。見(jiàn)源碼包中的Account.xml
這是在Domain Model建立后最重要的一步,是前臺(tái)表現(xiàn)層Struts開(kāi)發(fā)的起步,表單創(chuàng)建有以下注意點(diǎn):
表單類(lèi)必須繼承com.jdon.model.ModelForm
表單類(lèi)基本是Domain Model的影子,每一個(gè)Model對(duì)應(yīng)一個(gè)ModelForm實(shí)例,所謂對(duì)應(yīng):就是字段名稱(chēng)一致。ModelForm實(shí)例是由Model實(shí)例復(fù)制獲得的。
public class AccountForm extends ModelForm {
private String username;
private String password;
private String email;
private String firstName;
private String lastName;
……
}
當(dāng)然AccountForm可能有一些與顯示有關(guān)的字段,例如注冊(cè)時(shí)有英文和中文選擇,以及類(lèi)別的選擇,那么增加兩個(gè)字段在AccountForm中:
private List languages;
private List categories;
這兩個(gè)字段需要初始化值的,因?yàn)樵?/span>AccountForm對(duì)應(yīng)的Jsp的頁(yè)面中要顯示出來(lái),這樣用戶(hù)才可能進(jìn)行選擇。選擇后的值將放置在專(zhuān)門(mén)的字段中。
有兩種方式初始化這兩個(gè)字段:
1. 在AccountForm構(gòu)造方法中初始化,前提是:這些初始化值是常量,如:
public AccountForm() {
languages = new ArrayList();
languages.add("english");
languages .add("japanese");
}
2.如果初始化值是必須從數(shù)據(jù)庫(kù)中獲取,那么采取前面章節(jié)介紹的使用ModelHandler來(lái)實(shí)現(xiàn),這部分又涉及配置和代碼實(shí)現(xiàn),缺省時(shí)我們考慮通過(guò)jdonframework.xml配置實(shí)現(xiàn)。
第一步配置ActionForm:
上節(jié)編寫(xiě)了ModelForm代碼,ModelForm也就是struts的ActionForm,在struts-config.xml中配置ActionForm如下:
<form-bean name="accountFrom" type="com.jdon.framework.samples.jpetstore.presentation.form.AccountForm"/>
第二步配置Action:
這需要根據(jù)你的CRUD功能實(shí)現(xiàn)需求配置,例如本例中用戶(hù)注冊(cè)和用戶(hù)修改分開(kāi),這樣,配置兩套ModelViewAction和ModelSaveAction,具體配置見(jiàn)源碼包中的struts-config-security.xml,這里將struts-config.xml根據(jù)模塊劃分成相應(yīng)的模塊配置,實(shí)現(xiàn)多模塊開(kāi)發(fā),本模塊是用戶(hù)注冊(cè)登陸系統(tǒng),因此取名struts-config-security.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>
.其中有一個(gè)initMethod主要用于AccuntForm對(duì)象的初始化。其他都是增刪改查的常規(guī)實(shí)現(xiàn)。
在編輯頁(yè)面EditAccountForm.jsp中加入:
<html:hidden name="accountFrom" property="action" value="create" />
在新增頁(yè)面NewAccountForm.jsp加入:
<html:hidden name="accountFrom" property="action" value="edit" />
所有的字段都是直接來(lái)自accountFrom。
商品模塊功能完成,struts提供了多模塊開(kāi)發(fā),因此我們可以將這一模塊單獨(dú)保存在一個(gè)配置中:/WEB-INF/struts-config-security.xml,這樣以后擴(kuò)展修改起來(lái)方便。
在iBATIS-JPetstore中沒(méi)有單獨(dú)的CategoryForm,而是將三個(gè)Model:Category、Product、,Item合并在一個(gè)CatalogBean中,這樣做的缺點(diǎn)是拓展性不強(qiáng),將來(lái)這三個(gè)Model也許需要單獨(dú)的ActionForm。
由于我們使用Jdon框架的CRUD功能配置實(shí)現(xiàn),因此,不怕細(xì)分這三個(gè)Model帶來(lái)代碼復(fù)雜和瑣碎。
由于原來(lái)的Jpetstore“偷懶”,沒(méi)有實(shí)現(xiàn)Category Product等的CRUD功能,只實(shí)現(xiàn)它們的查詢(xún)功能,因此,我們使用Jdon框架的批量查詢(xún)來(lái)實(shí)現(xiàn)查詢(xún)。
商品查詢(xún)主要有兩種批量查詢(xún),根據(jù)其類(lèi)別ID:CategoryId查詢(xún)所有該商品目錄下所有的商品;根據(jù)關(guān)鍵字搜索符合條件的所有商品,下面以前一個(gè)功能為例子:
iBatis-jpetstore使用PaginatedList作為分頁(yè)的主要對(duì)象,該對(duì)象需要保存到HttpSession中,然后使用PaginatedList的NextPage等直接遍歷,這種方法只適合在小數(shù)據(jù)量合適,J2EE編程中不推薦向HttpSession放入大量數(shù)據(jù),不利于cluster。
根據(jù)Jdon批量查詢(xún)的持久層要求,批量查詢(xún)需要兩種SQL語(yǔ)句實(shí)現(xiàn):符合條件的ID集合和符合條件的總數(shù):以及單個(gè)Model查詢(xún)。
//獲得ID集合
List getProductIDsListByCategory(String categoryId, int pagessize);
//獲得總數(shù)
int getProductIDsListByCategoryCount(String categoryId);
//單個(gè)Model查詢(xún)
Product getProduct(String productId) ;
這里我們需要更改一下iBatis原來(lái)的Product.xml配置,原來(lái),它設(shè)計(jì)返回的是符合條件的所有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實(shí)現(xiàn),讀取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();
}
這樣,結(jié)合配置的iBatis DAO和Jdon框架批量查詢(xún),在ProductManagerImp中創(chuàng)建PageIterator,當(dāng)然這部分代碼也可以在ProductDao實(shí)現(xiàn),創(chuàng)建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;
根據(jù)批量查詢(xún)的編程步驟,在表現(xiàn)層主要是實(shí)現(xiàn)ModelListAction繼承、配置和Jsp編寫(xiě),下面分步說(shuō):
第一步,創(chuàng)建一個(gè)ModelListAction子類(lèi)ProductListAction,實(shí)現(xiàn)兩個(gè)方法: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);
}
由于我們實(shí)現(xiàn)的是查詢(xún)一個(gè)商品目錄下所有商品功能,因此,需要顯示商品目錄名稱(chēng),而前面操作的都是Product模型,所以在顯示頁(yè)面也要加入商品目錄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>
第三步,編寫(xiě)Category.jsp
從productListForm中取出我們要顯示兩個(gè)模型,一個(gè)是oneModel中的Category;另外一個(gè)是Product Model集合list,Jsp語(yǔ)法如下:
<bean:define id="category" name="productListForm" property="oneModel" />
<bean:define id="productList" name="productListForm" property="list" />
我們可以顯示商品目錄名稱(chēng)如下:
<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>
加上分頁(yè)標(biāo)簽庫(kù)如下:
<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>
至此,一個(gè)商品目錄下的所有商品批量查詢(xún)功能完成,由于是基于框架的模板化編程,直接上線運(yùn)行成功率高。
參考上面步驟,商品搜索也可以順利實(shí)現(xiàn),從后臺(tái)到前臺(tái)按照批量查詢(xún)這條線索分別涉及的類(lèi)有:
持久層實(shí)現(xiàn):ProductDao中的三個(gè)方法:
List searchProductIDsList(String keywords, int start, int pagessize); //ID集合
int searchProductIDsListCount(String keywords); //總數(shù)
Product getProduct(String productId) ; //單個(gè)Model
表現(xiàn)層:建立ProductSearchAction類(lèi),配置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>
與前面使用的都是同一個(gè)ActionForm:productListForm
編寫(xiě)SearchProducts .jsp,與Category.jsp類(lèi)似,相同的是ActionForm;不同的是action。
條目Item批量實(shí)現(xiàn)與Product批量查詢(xún)類(lèi)似:
持久層:ItemDao提供三個(gè)方法:
List getItemIDsListByProduct(String productId, int start, int pagessize);//ID集合
int getItemIDsListByProductCount(String productId);//總數(shù)
Item getItem(String itemId); //單個(gè)Model
表現(xiàn)層:創(chuàng)建一個(gè)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相比,非常類(lèi)似,不同的是Model名稱(chēng)不一樣,一個(gè)是Product一個(gè)是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的配置,非常類(lèi)似,其實(shí)itemListForm和productListForm是同一個(gè)ModelListForm類(lèi)型,可以合并起來(lái)統(tǒng)一命名為listForm,節(jié)省ActionForm的配置。
Product.jsp頁(yè)面與前面的Category.jsp SearchProdcuts.jsp類(lèi)似。
<bean:define id="product" name="itemListForm" property="oneModel" />
<bean:define id="itemList" name="itemListForm" property="list" />
分頁(yè)顯示:
<MultiPages:pager actionFormName="itemListForm" page="/shop/viewProduct.do"
paramId="productId" paramName="product" paramProperty="productId">
…..
單個(gè)顯示屬于CRUD中的一個(gè)查詢(xún)功能,我們需要建立Model對(duì)應(yīng)的ModelForm,將Item的字段拷貝到ItemForm中。配置這個(gè)ActionForm如下:
<form-bean name="itemForm"
type="com.jdon.framework.samples.jpetstore.presentation.form.ItemForm"/>
第二步:因?yàn)檫@個(gè)功能屬于CRUD一種,無(wú)需編程,但是需要配置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>
配置中只要一個(gè)方法getMethod就可以,因?yàn)橹挥玫?/span>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,現(xiàn)在開(kāi)始發(fā)現(xiàn)一個(gè)問(wèn)題,Item.jsp中不只是顯示Item信息,還有Product信息,而前面我們定義的是Item信息,如果使得Item.jsp顯示Product信息呢,這就從設(shè)計(jì)起源Domain Model上考慮,在Item的Model中有Product引用:
private Product product;
public Product getProduct() { return product; }
public void setProduct(Product product) { this.product = product; }
Item和Product的多對(duì)一關(guān)系其實(shí)應(yīng)該在域建模開(kāi)始就考慮到了。
那么,我們只要在持久層查詢(xún)Item時(shí),能夠?qū)⑵渲械?/span>Product字段查詢(xún)就可以。在持久層的iBatis的Product.xml實(shí)現(xiàn)有下列SQL語(yǔ)句:
<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>
這段語(yǔ)法實(shí)際在查詢(xún)Item時(shí),已經(jīng)將Product查詢(xún)出來(lái),這樣Item Model中已經(jīng)有Product數(shù)據(jù),因?yàn)?/span>ActionForm是Model映射,因此,前臺(tái)Jsp也可以顯示Product數(shù)據(jù)。
在Item.jsp中,進(jìn)行下面定義:
<bean:define id="product" name="itemForm " property="product" />
<bean:define id="item" name="itemForm " />
將itemForm中product屬性定義為product即可;這樣不必大幅度修改原來(lái)的Item.jsp了。
商品模塊功能完成,struts提供了多模塊開(kāi)發(fā),因此我們可以將這一模塊單獨(dú)保存在一個(gè)配置中:/WEB-INF/struts-config-catalog.xml,這樣以后擴(kuò)展修改起來(lái)方便。
購(gòu)物車(chē)屬于一種有狀態(tài)數(shù)據(jù),也就是說(shuō),購(gòu)物車(chē)的scope生命周期是用戶(hù),除非這個(gè)用戶(hù)離開(kāi),否則購(gòu)物車(chē)一直在內(nèi)存中存在。
現(xiàn)在有兩種解決方案:
第一,將購(gòu)物車(chē)狀態(tài)作為數(shù)據(jù)類(lèi),保存到ActionForm中,設(shè)置scope為session,這種形式下,對(duì)購(gòu)物車(chē)的數(shù)據(jù)操作如加入條目等實(shí)現(xiàn)不很方便,iBatis-jpetstore 4.0.5就采取這個(gè)方案,在數(shù)據(jù)類(lèi)Cart中存在大量數(shù)據(jù)操作方法,那么Cart這個(gè)類(lèi)到底屬于數(shù)據(jù)類(lèi)Model?還是屬于處理服務(wù)類(lèi)呢?
在我們J2EE編程中,通常使用兩種類(lèi)來(lái)實(shí)現(xiàn)功能,一種是數(shù)據(jù)類(lèi),也就是我們?cè)O(shè)計(jì)的Model;一種是服務(wù)類(lèi),如POJO服務(wù)或EJB服務(wù),服務(wù)屬于一種處理器,處理過(guò)程。使用這兩種分類(lèi)比較方便我們來(lái)解析業(yè)務(wù)需求,EJB中實(shí)體Bean和Session Bean也是屬于這兩種類(lèi)型。
iBatis-jpetstore 4.0.5則是將服務(wù)和數(shù)據(jù)類(lèi)混合在一個(gè)類(lèi)中,這也屬于一種設(shè)計(jì),但是我們認(rèn)為它破壞了解決問(wèn)題的規(guī)律性,而且造成數(shù)據(jù)和操作行為耦合性很強(qiáng),在設(shè)計(jì)模式中我們還使用橋模式來(lái)分離抽象和行為,因此這種做法可以說(shuō)是反模式的。那么我們采取數(shù)據(jù)類(lèi)和服務(wù)分離的方式方案來(lái)試試看:
第二.購(gòu)物車(chē)功能主要是對(duì)購(gòu)物車(chē)這個(gè)Model的CRUD,與通常的CRUD區(qū)別是,數(shù)據(jù)是保存到HttpSession,而不是持久化到數(shù)據(jù)庫(kù)中,是數(shù)據(jù)狀態(tài)保存不同而已。所以如果我們實(shí)現(xiàn)一個(gè)CartService,它提供add或update或delete等方法,只不過(guò)操作對(duì)象不是數(shù)據(jù)庫(kù),而是其屬性為購(gòu)物車(chē)Cart,然后將該CarService實(shí)例保存到HttpSession,實(shí)現(xiàn)每個(gè)用戶(hù)一個(gè)CartService實(shí)例,這個(gè)我們成為有狀態(tài)的POJO服務(wù)。
這種處理方式類(lèi)似EJB架構(gòu)處理,如果我們業(yè)務(wù)服務(wù)層使用EJB,那么使用有態(tài)會(huì)話Bean實(shí)現(xiàn)這個(gè)功能。
現(xiàn)在問(wèn)題是,Jdon框架目前好像沒(méi)有提供有狀態(tài)POJO服務(wù)實(shí)例的獲得,那么我們自己在WebAppUtil.getService獲得實(shí)例后,保存到HttpSession中,下次再到HttpSession中獲得,這種有狀態(tài)處理需要表現(xiàn)層更多代碼,這就不能使用Jdon框架的CRUD配置實(shí)現(xiàn)了,需要我們代碼實(shí)現(xiàn)ModelHandler子類(lèi)。
考慮到可能在其他應(yīng)用系統(tǒng)還有這種需求,那么能不能將有狀態(tài)的POJO服務(wù)提煉到Jdon框架中呢?關(guān)鍵使用什么方式加入框架,因?yàn)檫@是設(shè)計(jì)目標(biāo)服務(wù)實(shí)例的獲得,框架主要流程代碼又不能修改,怎么辦?
Jdon框架的AOP功能在這里顯示了強(qiáng)大靈活性,我們可以將有狀態(tài)的POJO服務(wù)實(shí)例獲得作為一個(gè)攔截器,攔截在原來(lái)POJO服務(wù)實(shí)例獲得之前。在Jdon框架設(shè)計(jì)中,目標(biāo)服務(wù)實(shí)例的獲得一般只有一次。
創(chuàng)建有狀態(tài)POJO服務(wù)攔截器com.jdon.aop.interceptor. StatefulInterceptor,再創(chuàng)建一個(gè)空接口:com.jdon.controller.service.StatefulPOJOService,需要實(shí)現(xiàn)有狀態(tài)實(shí)例的POJO類(lèi)只要繼承這個(gè)接口就可以。
配置aspect.xml,加入這個(gè)攔截器:
<interceptor name="statefulInterceptor" class="com.jdon.aop.interceptor.StatefulInterceptor"
pointcut="pojoServices" />
這里需要注意的是:你不能讓一個(gè)POJO服務(wù)類(lèi)同時(shí)繼承Poolable,然后又繼承Stateful,因?yàn)檫@是兩種不同的類(lèi)型,前者適合無(wú)狀態(tài)POJO;后者適合CartService這樣有狀態(tài)處理;這種選擇和EJB的有態(tài)/無(wú)態(tài)選擇是一樣的。
購(gòu)物車(chē)模塊主要圍繞域模型Cart展開(kāi),需要首先明確Cart是一個(gè)什么樣的業(yè)務(wù)模型,購(gòu)物車(chē)頁(yè)面是類(lèi)似商品條目批量查詢(xún)頁(yè)面,不過(guò)購(gòu)物車(chē)中顯示的不但是商品條目,還有數(shù)量,那么我們專(zhuān)門(mén)創(chuàng)建一個(gè)Model來(lái)指代它,取名為CartItem,CartItem是Item父集,多了一個(gè)數(shù)量。
這樣購(gòu)物車(chē)頁(yè)面就是CartItem的批量查詢(xún)頁(yè)面,然后還有CartItem的CRUD操作,所以購(gòu)物車(chē)功能主要是CartItem的CRUD和批量查詢(xún)功能。
iBatis 4.0.5原來(lái)設(shè)計(jì)了專(zhuān)門(mén)Cart Model,其實(shí)這個(gè)Cart主要是一個(gè)功能類(lèi),因?yàn)樗臄?shù)據(jù)項(xiàng)只有一個(gè)Map和List,這根本不能代表業(yè)務(wù)需求中的一個(gè)模型。雖然iBatis 4..0.5也可以自圓其說(shuō)實(shí)現(xiàn)了購(gòu)物車(chē)功能,但是這種實(shí)現(xiàn)是隨心所欲,無(wú)規(guī)律性可遵循,因而以后維護(hù)起來(lái)也是困難,維護(hù)人員理解困難,修改起來(lái)也沒(méi)有章程可循,甚至亂改一氣。
CartItem可以使用iBatis原來(lái)的CartItem,這樣也可保持Cart.jsp頁(yè)面修改量降低。刪除原來(lái)的Cart這個(gè)Model,建立對(duì)應(yīng)的CartService,實(shí)現(xiàn)原來(lái)的Cart一些功能。
public interface CartService {
CartItem getCartItem(String itemId);
void addCartItem(EventModel em);
void updateCartItem(EventModel em);
void deleteCartItem(EventModel em);
PageIterator getCartItems();
}
CartServiceImp是CartService子類(lèi),它是一個(gè)有狀態(tài)POJO服務(wù),代碼簡(jiǎn)要如下:
public class CartServiceImp implements CartService, Stateful{
private ProductManager productManager;
//將原來(lái)iBatis 中Cart類(lèi)中兩個(gè)屬性移植到CartServiceImp中
private final Map itemMap = Collections.synchronizedMap(new HashMap());
private List itemList = new ArrayList();
public CartServiceImp(ProductManager productManager) {
super();
this.productManager = productManager;
}
……
}
itemMap是裝載CartItem的一個(gè)Map,是類(lèi)屬性,由于CartServiceImp是有狀態(tài)的,每個(gè)用戶(hù)一個(gè)實(shí)例,那么也就是每個(gè)用戶(hù)有自己的itemMap列表,也就是購(gòu)物車(chē)。
CartServiceImp中的 getCartItemIDs是查詢(xún)購(gòu)物車(chē)當(dāng)前頁(yè)面的購(gòu)物條目,屬于批量分頁(yè)查詢(xún)實(shí)現(xiàn),這里有一個(gè)需要考量的地方,是getCartItems方法還是getCartItemIDs方法?也就是返回CartIem的實(shí)例集合還是CartItem的ItemId集合?按照前面標(biāo)準(zhǔn)的Jdon框架批量分頁(yè)查詢(xún)實(shí)現(xiàn),應(yīng)該返回CartItem的ItemId集合,然后由Jdon框架的ModelListAction根據(jù)ItemId首先從緩存中獲得CartItem實(shí)例,但是本例CartItem本身不是持久化在數(shù)據(jù)庫(kù),而也是內(nèi)存HttpSession中,所以ModelListAction這種流程似乎沒(méi)有必要。
如果將來(lái)業(yè)務(wù)需求變化,購(gòu)物車(chē)狀態(tài)不是保存在內(nèi)存而是數(shù)據(jù)庫(kù),這樣,用戶(hù)下次登陸時(shí),可以知道他上次購(gòu)物車(chē)?yán)锏纳唐窏l目,那么采取Jdon框架標(biāo)準(zhǔn)查詢(xún)方案還是有一定擴(kuò)展性的。
這里,我們就事論事,采取返回CartIem的實(shí)例集合,展示一下如何靈活應(yīng)用Jdon框架的批量查詢(xún)功能。下面是CartServiceImp 的getCartItems方法詳細(xì)代碼:
public PageIterator getCartItems(int start, int count) {
int offset = itemList.size() - start; //獲得未顯示的總個(gè)數(shù)
int pageCount = (count < offset)?count:offset;
List pageList = new ArrayList(pageCount); //當(dāng)前頁(yè)面記錄集合
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方法是從購(gòu)物車(chē)所有條目itemList中查詢(xún)獲得當(dāng)前頁(yè)面的條目,并創(chuàng)建一個(gè)PageIterator。
注意,現(xiàn)在這個(gè)PageIterator中keys屬性中裝載的不是數(shù)據(jù)ID集合,而是完整的CartItem集合,因?yàn)樯厦娲a中pageList中對(duì)象是從itemList中獲得,而itemList中裝載的都是CartItem。
由于PageIterator中封裝的是完整Model集合,而不是ID集合,所以現(xiàn)在表現(xiàn)層有兩種方案,繼承框架的ModelListAction;或重新自己實(shí)現(xiàn)一個(gè)Action,替代ModelListAction。
這里使用繼承框架的ModelListAction方案,巧妙地實(shí)現(xiàn)我們的目的,省卻編碼:
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; //因?yàn)?/span>key不是主鍵,而是完整的Model,直接返回
}
protected boolean isEnableCache(){
return false; //無(wú)需緩存,CartItem本身實(shí)際是在內(nèi)存中。
}
}
配置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>
上面是購(gòu)物車(chē)顯示實(shí)現(xiàn),只要調(diào)用/shop/viewCart.shtml就可以顯示購(gòu)物車(chē)了。
在Cart.jsp頁(yè)面插入下面標(biāo)簽:
<logic:iterate id="cartItem" name="listForm" property="list">
….
</logic:iterate>
分頁(yè)顯示標(biāo)簽如下:
<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>
前面完成了購(gòu)物車(chē)顯示功能,下面是設(shè)計(jì)購(gòu)物車(chē)的新增和刪除、修改功能。
參考Jdon框架的CRUD功能實(shí)現(xiàn),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>
在這個(gè)配置中,只有新增和刪除方法,修改方法沒(méi)有,因?yàn)橘?gòu)物車(chē)修改主要是其中商品條目的數(shù)量修改,它不是逐條修改,而是一次性批量修改,這里的Model是CartItem,這是購(gòu)物車(chē)?yán)锏囊粋€(gè)條目,因此如果這里寫(xiě)修改,也只是CartItem一個(gè)條目的修改,不符合我們要求。下面專(zhuān)門(mén)章節(jié)實(shí)現(xiàn)這個(gè)修改。
表現(xiàn)層主要是配置,沒(méi)有代碼,代碼都依靠cartService中的addCartItem和deleteCartItem實(shí)現(xiàn):例如:
public void addCartItem(EventModel em) {
CartItem cartItem = (CartItem) em.getModel();
String workingItemId = cartItem.getWorkingItemId();
……
}
注意addCartItem中從EventModel實(shí)例中獲取的Model是CartItem,這與我們?cè)?/span>jdonframework.xml中上述定義的Model類(lèi)型是統(tǒng)一的。
Struts-config.xml中定義是CRUD的標(biāo)準(zhǔn)定義,注意,這里只有ModelSaveAction無(wú)需ModelViewAction,因?yàn)閷⑸唐窏l目加入或刪除購(gòu)物車(chē)這個(gè)功能沒(méi)有專(zhuān)門(mén)的顯示頁(yè)面:
<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>
注意,調(diào)用刪除功能時(shí),需要附加action參數(shù):
/shop/removeItemFromCart.shtml?action=delete
而/shop/addItemToCart.shtml是新增屬性,缺省后面無(wú)需跟參數(shù)。
上面基本完成了購(gòu)物車(chē)主要功能;購(gòu)物車(chē)功能一個(gè)復(fù)雜性在于其顯示功能和修改功能合并在一起,修改功能是指修改購(gòu)物車(chē)?yán)锼猩唐窏l目的數(shù)量。
既然有修改功能,而且這個(gè)修改功能比較特殊,我們需要設(shè)計(jì)一個(gè)獨(dú)立的ActionForm,用來(lái)實(shí)現(xiàn)商品條目數(shù)量的批量修改。
首先設(shè)計(jì)一個(gè)ActionForm(ModelForm),該ModelForm主要用來(lái)實(shí)現(xiàn)購(gòu)物車(chē)條目數(shù)量的更改,取名為CartItemsForm,其內(nèi)容如下:
public class CartItemsForm extends ModelForm {
private String[] itemId;
private int[]quantity;
private BigDecimal totalCost;
…..
}
itemId和quantity設(shè)計(jì)成數(shù)組,這樣,Jsp頁(yè)面可以一次性提交多個(gè)itemId和quantity數(shù)值。
現(xiàn)在CartItemsForm已經(jīng)包含前臺(tái)jsp輸入的數(shù)據(jù),我們還是將其傳遞遞交到服務(wù)層實(shí)現(xiàn)處理。因此建立一個(gè)Model,內(nèi)容與CartItemsForm類(lèi)似,這里的Model名為CartItems,實(shí)際是一個(gè)傳輸對(duì)象。
public class CartItems extends Model{
private String[] itemId;
private int[] quantity;
private BigDecimal totalCost;
……
}
表現(xiàn)層在jdonframework.xml定義配置就無(wú)需編碼,配置如下:
<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,這兩個(gè)是專(zhuān)門(mén)為批量修改設(shè)立的。只有一個(gè)方法updateMethod。因?yàn)樵谶@個(gè)更新功能中,沒(méi)有根據(jù)主鍵從數(shù)據(jù)庫(kù)查詢(xún)Model的功能,因此,這里model的key可以為空值。
服務(wù)層CartServiceImp的updateCartItems方法實(shí)現(xiàn)購(gòu)物車(chē)條目數(shù)量更新:
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]);//逐條更新購(gòu)物車(chē)中的數(shù)量
}
} catch (Exception ex) {
logger.error(ex);
}
}
注意updateCartItems中從EventModel取出的是CartItems,和前面addCartItem方法中取出的是CartItem Model類(lèi)型不一樣,這是因?yàn)檫@里我們?cè)?/span>jdonframework.xml中定義與updateCartItems相對(duì)應(yīng)的Model是CartItems.
最后一步工作是Cat.jsp中加入CartItemsForm,能夠在購(gòu)物車(chē)顯示頁(yè)面有一個(gè)表單提交,客戶(hù)按提交按鈕,能夠立即實(shí)現(xiàn)當(dāng)前頁(yè)面購(gòu)物車(chē)數(shù)量的批量修改。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實(shí)際是ModelSaveAction時(shí),框架才知道操作性質(zhì)。
最后,還有一個(gè)功能需要完成,在購(gòu)物車(chē)顯示時(shí),需要顯示當(dāng)前購(gòu)物車(chē)的總價(jià)格,注意不是顯示當(dāng)前頁(yè)面的總價(jià)格,所以無(wú)法在Cart.jsp直接實(shí)現(xiàn),必須遍歷購(gòu)物車(chē)?yán)锼?/span>CartItem計(jì)算總數(shù)。
該功能是購(gòu)物車(chē)顯示時(shí)一起實(shí)現(xiàn),購(gòu)物車(chē)顯示是通過(guò)CartListAction實(shí)現(xiàn)的,這個(gè)CartListAction實(shí)際是生成一個(gè)ModelListForm,如果ModelListForm能夠增加一個(gè)getTotalPrice方法就可以,因此有兩種實(shí)現(xiàn)方式:繼承ModelListForm加入自己的getTotalPrice方法;第二種無(wú)需再實(shí)現(xiàn)自己的ModelListForm,ModelListForm可以攜帶一個(gè)Model,通過(guò)setOneModel即可,這個(gè)方法是在ModelListAction的子類(lèi)CartListAction可以override覆蓋實(shí)現(xiàn)的,在CartListAction加入下列方法:
protected Model setOneModel(HttpServletRequest request){
CartService cartService = (CartService)WebAppUtil.getService("cartService", request);
CartItems cartItems = new CartItems();
cartItems.setTotalCost(cartService.getSubTotal());
return cartItems;
}
我們使用空的CartItems作為攜帶價(jià)格總數(shù)的Model,然后在Cart.jsp中再取出來(lái)顯示:
<bean:define id="cartItems " name="listForm" property="oneModel" />
<b>Sub Total: <bean:write name="cartItems" property="subTotal" format="$#,##0.00" />
將當(dāng)前頁(yè)面listForm中屬性oneModel定義為cartItems,它實(shí)際是我們定義的CartItems,
下一行取出總價(jià)即可。
在顯示購(gòu)物車(chē)時(shí),需要一起顯示該用戶(hù)喜歡的商品列表,很顯然這是一個(gè)批量分頁(yè)查詢(xún)實(shí)現(xiàn),但是它有些特殊,它首先顯示的第一頁(yè)不是由URL調(diào)用的,而是嵌入在購(gòu)物車(chē)顯示中,那么只能在購(gòu)物車(chē)顯示頁(yè)面的ModellistForm中做文章。
在上節(jié)中,在CartListAction中setOneModel方法中,使用CartItems作為價(jià)格總數(shù)的載體,現(xiàn)在恐怕我們也要將之作為本功能實(shí)現(xiàn)載體。
還有一種實(shí)現(xiàn)載體,就是其他scope為session的ActionForm,AccountForm很適合做這樣的載體,而且和本功能意義非常吻合,所以在AccountForm/Account中增加一個(gè)myList字段,在myList字段中,放置的是該用戶(hù)喜歡的商品Product集合,注意不必放置Product的主鍵集合,因?yàn)槲覀冎灰@示用戶(hù)喜歡商品的第一頁(yè),這一頁(yè)是嵌入購(gòu)物車(chē)顯示頁(yè)面中,所以第一頁(yè)顯示的個(gè)數(shù)是由程序員可事先在程序中定義。
這樣在Account獲得時(shí),一起將myList集合值獲得。
我們還是從域模型開(kāi)始,Order是訂單模塊的核心實(shí)體,其內(nèi)容可以確定如下:
public class Order extends Model {
/* Private Fields */
private int orderId;
private String username;
private Date orderDate;
private String shipAddress1;
private String shipAddress2;
…..
}
第二步,建立與Model對(duì)應(yīng)的ModelForm,我們可以稱(chēng)之為邊界模型,代碼從Order拷貝過(guò)來(lái)即可。當(dāng)然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的業(yè)務(wù)服務(wù)接口,如下:
public interface OrderService {
void insertOrder(Order order);
Order getOrder(int orderId);
List getOrdersByUsername(String username);
}
第四步,實(shí)現(xiàn)OrderService的POJO子類(lèi):OrderServiceImp。
第五步,表現(xiàn)層實(shí)現(xiàn),本步驟可和第四步同時(shí)進(jìn)行。
OrderService中有訂單的插入創(chuàng)建功能,我們使用Jdon框架的CRUD中create配置實(shí)現(xiàn),配置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>
第六步:根據(jù)逐個(gè)實(shí)現(xiàn)界面功能,訂單的第一個(gè)功能創(chuàng)建一個(gè)新的訂單,在新訂單頁(yè)面NewOrderForm.jsp推出之前,這個(gè)頁(yè)面的ActionForm已經(jīng)被初始化,是根據(jù)購(gòu)物車(chē)等Cart其他Model數(shù)據(jù)初始化合成的。
根據(jù)Jdon框架中CRUD功能實(shí)現(xiàn),初始化一個(gè)ActionForm有兩種方法:一繼承ModelHandler實(shí)現(xiàn)initForm方法;第二通過(guò)jdonframework.xml的initMethod方法配置。
這兩個(gè)方案選擇依據(jù)是根據(jù)用來(lái)初始化的數(shù)據(jù)來(lái)源什么地方。
訂單表單初始化實(shí)際是來(lái)自購(gòu)物車(chē)信息或用戶(hù)賬號(hào)信息,這兩個(gè)都事先保存在HttpSession中,購(gòu)物車(chē)信息是通過(guò)有態(tài)CartService實(shí)現(xiàn)的,所以這些數(shù)據(jù)來(lái)源是和request相關(guān),那么我們選擇第一個(gè)方案。
繼承ModelHandler之前,我們可以考慮首先繼承ModelHandler的子類(lèi)XmlModelHandler,只要繼承initForm一個(gè)方法即可,這樣節(jié)省其他方法編寫(xiě)實(shí)現(xiàn)。
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繼承后,因?yàn)檫@使用了Jdon的CRUD功能實(shí)現(xiàn),這里我們只使用到CRUD中的創(chuàng)建功能,因此,findModelBykey方法就無(wú)需實(shí)現(xiàn),或者可以在jdonframework.xml中配置該方法實(shí)現(xiàn)。
考慮到在initForm執(zhí)行后,需要推出一個(gè)NewOrderForm.jsp頁(yè)面,這是一個(gè)新增性質(zhì)的頁(yè)面。所以在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>
新的訂單頁(yè)面推出后,用戶(hù)需要經(jīng)過(guò)兩個(gè)流程才能確認(rèn)保存,這兩個(gè)流程是填寫(xiě)送貨地址以及再次完整確認(rèn)。這兩個(gè)流程實(shí)現(xiàn)的動(dòng)作非常簡(jiǎn)單,就是將OrderForm中的shippingAddressRequired字段和confirm字段賦值,相當(dāng)于簡(jiǎn)單的開(kāi)關(guān),這是一個(gè)很簡(jiǎn)單的動(dòng)作,可以有兩種方案:直接在jsp表單中將這兩個(gè)值賦值;直接使用struts的Action實(shí)現(xiàn)。后者需要編碼,而且不是非有這個(gè)必要,只有第一個(gè)方案行不通時(shí)才被迫實(shí)現(xiàn)。
第一步:填寫(xiě)送貨地址
使用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>
這是實(shí)現(xiàn)送貨地址頁(yè)面的填寫(xiě),使用的還是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"/>
第二步:確認(rèn)訂單
類(lèi)似上述步驟,配置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>
第三步:下面是創(chuàng)建數(shù)據(jù)保存功能實(shí)現(xiàn):
<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實(shí)現(xiàn)的,這里有兩種方式:配置方式:在jdonframework.xml中配置了方法插入;第二種是實(shí)現(xiàn)代碼,這里原本可以使用配置方式實(shí)現(xiàn),但是因?yàn)樵诠δ苌嫌幸螅涸谟唵伪4婧?,需要清除?gòu)物車(chē)數(shù)據(jù),因此只能使用代碼實(shí)現(xiàn)方式,在ModelHandler中實(shí)現(xiàn)子類(lèi)方法serviceAction:
public void serviceAction(EventModel em, HttpServletRequest request) throws java.lang.Exception {
try {
CartService cartService = (CartService) WebAppUtil.getService("cartService", request);
cartService.clear(); //清楚購(gòu)物車(chē)數(shù)據(jù)
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);
}
}
用戶(hù)查詢(xún)自己的訂單列表功能很明顯可以使用Jdon框架的批量查詢(xún)事先。
在struts-config.xml中配置ModelListForm如下:
<form-bean name="listForm" type="com.jdon.strutsutil.ModelListForm"/>
建立繼承ModelListAction子類(lèi)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)
….
}
根據(jù)Jdon批量查詢(xún)要求,使用iBatis實(shí)現(xiàn)返回ID集合以及符合條件的總數(shù)。
最后編寫(xiě)ListOrders.jsp,兩個(gè)語(yǔ)法:logic:iterator 和MultiPages
目前我們有四個(gè)struts-config.xml,前面每個(gè)模塊一個(gè)配置:
/WEB-INF/struts-config.xml 主配置
/WEB-INF/struts-config-catalog.xml 商品相關(guān)配置
/WEB-INF/struts-config-security.xml 用戶(hù)相關(guān)配置
/WEB-INF/struts-config-cart.xml 購(gòu)物車(chē)相關(guān)配置
/WEB-INF/struts-config-order.xml 訂單相關(guān)配置
本項(xiàng)目只有一個(gè)jdonframework.xml,當(dāng)然我們也可以創(chuàng)建多個(gè)jdonframework.xml,然后在其struts-config.xml中配置。
<plug-in className="com.jdon.strutsutil.InitPlugIn">
<set-property property="modelmapping-config" value="jdonframework_iBATIS.xml" />
</plug-in>
iBatis 4.0.5中原來(lái)的配置過(guò)于擴(kuò)張(從持久層擴(kuò)張到業(yè)務(wù)層),AccountDao每個(gè)實(shí)例獲得都需要通過(guò)daoManager.getDao這樣形式,而使用Jdon框架后,AccountDao等DAO實(shí)例獲得無(wú)需特別語(yǔ)句,我們只要在AccountService直接引用AccountDao接口,至于AccountDao的具體實(shí)例,通過(guò)Ioc注射進(jìn)入即可。
因此,在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需要兩個(gè)構(gòu)造方法實(shí)例,這兩個(gè)中有一個(gè)是AccountDao。
按照iBatis原來(lái)的AccountDao子類(lèi)AccountSqlMapDao有一個(gè)構(gòu)造方法參數(shù)是DaoManager,但是我們無(wú)法生成自己的DaoManager實(shí)例,因?yàn)?/span>DaoManager是由dao.xml配置文件讀取后生成的,這是一個(gè)動(dòng)態(tài)實(shí)例;那只有更改AccountSqlMapDao構(gòu)造方法了。
根源在于BaseSqlMapDao類(lèi),BaseSqlMapDao是一個(gè)類(lèi)似JDBC模板類(lèi),每個(gè)Dao都繼承它,現(xiàn)在我們修改BaseSqlMapDao如下:
public class BaseSqlMapDao extends DaoTemplate implements SqlMapExecutor{
…..
}
BaseSqlMapDao是XML配置和JDBC模板的結(jié)合體,在這個(gè)類(lèi)中,這兩者搭配在一起,在其中實(shí)現(xiàn)SqlMapExecutor各個(gè)子方法。
我們?cè)賱?chuàng)建一個(gè)DaoManagerFactory,專(zhuān)門(mén)根據(jù)配置文件創(chuàng)建DaoManager實(shí)例:
主要方法如下:
Reader reader = Resources.getResourceAsReader(daoResource);
daoManager = DaoManagerBuilder.buildDaoManager(reader);
其中daoResource是dao.xml配置文件,這個(gè)配置是在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>
這樣,我們可以通過(guò)改變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中,我們只配置一個(gè)JDBC模板,而不是將所有的如AccountDao配置其中,因?yàn)槲覀冃枰?/span>iBatis只是它的JDBC模板,實(shí)現(xiàn)持久層方便的持久化,僅此而已!
DaoManagerFactory為我們生產(chǎn)了DaoManager實(shí)例,那么如何賦值到BaseSqlMapDao中,我們?cè)O(shè)計(jì)一個(gè)創(chuàng)建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);
}
}
通過(guò)getSqlMapDaoTemp方法,由DaoManager.getDao方法獲得BaseSqlMapDao實(shí)例,BaseSqlMapDao的接口是SqlMapExecutor,這樣我們通過(guò)DaoManager獲得一個(gè)JDBC模板SqlMapExecutor的實(shí)例。
這樣,在AccountDao各個(gè)子類(lèi)AccountSqlMapDao中,我們只要通過(guò)SqlMapDaoTemplateFactory獲得SqlMapExecutor實(shí)例,委托SqlMapExecutor實(shí)現(xiàn)JDBC操作,如下:
public class AccountSqlMapDao implements AccountDao {
//iBatis數(shù)據(jù)庫(kù)操作模板
private SqlMapExecutor sqlMapDaoTemplate;
//構(gòu)造方法
public AccountSqlMapDao(SqlMapDaoTemplateFactory sqlMapDaoTemplateFactory) {
sqlMapDaoTemplate = sqlMapDaoTemplateFactory.getSqlMapDaoTemp();
}
//查詢(xún)數(shù)據(jù)庫(kù)
public Account getAccount(String username) throws SQLException{
return (Account)sqlMapDaoTemplate.queryForObject("getAccountByUsername", username);
}
當(dāng)在JBoss或Tomcat控制臺(tái) 或者日志文件中出現(xiàn)下面字樣標(biāo)識(shí)Jdon框架安裝啟動(dòng)成功:
<======== Jdon Framework started successfully! =========>
Jdon框架啟動(dòng)成功后,以后出現(xiàn)的錯(cuò)誤基本是粗心大意的問(wèn)題,仔細(xì)分析會(huì)很快找到原因,相反,如果編程時(shí)仔細(xì)慢一點(diǎn),則后面錯(cuò)誤出現(xiàn)概率很小。
使用Jdon框架開(kāi)發(fā)Jpetstore, 一次性調(diào)試通過(guò)率高,一般問(wèn)題都是存在數(shù)據(jù)庫(kù)訪問(wèn)是否正常,一旦正常,主要頁(yè)面就出來(lái)了,其中常見(jiàn)問(wèn)題是jsp頁(yè)面和ActionForm的字段不對(duì)應(yīng),如jsp頁(yè)面顯示如下錯(cuò)誤:
No getter method available for property creditCardTypes for bean under name orderForm
表示在OrderForm中沒(méi)有字段creditCardTypes,或者有此字段,但是大小寫(xiě)錯(cuò)誤等粗心問(wèn)題。
如果jsp頁(yè)面或后臺(tái)log記錄顯示:
System error! please call system Admin.java.lang.Exception
一般這是由于前面出錯(cuò)導(dǎo)致,根據(jù)記錄向前搜索,搜索到第一個(gè)出錯(cuò)記錄:
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
第一個(gè)出錯(cuò)是在MethodConstructor報(bào)錯(cuò),沒(méi)有insertOrder方法,根據(jù)上面一行是orderService,那么檢查orderService代碼看看有無(wú)insertOrder:
public interface OrderService {
void insertOrder(Order order);
Order getOrder(int orderId);
List getOrdersByUsername(String username);
}
OrderService接口中是有insertOrder方法,那么為什么報(bào)錯(cuò)沒(méi)有呢?仔細(xì)檢查一下,是不是insertOrder的方法參數(shù)有問(wèn)題,哦, 因?yàn)?/span>orderService的調(diào)用是通過(guò)jdonframework.xml下面配置進(jìn)行的:
<model key="orderId"
class="com.jdon.framework.samples.jpetstore.domain.Order">
<actionForm name="orderForm"/>
<handler>
<service ref="orderService">
<createMethod name="insertOrder"/>
</service>
</handler>
</model>
而根據(jù)Jdon框架要求,使用配置實(shí)現(xiàn)ModelHandler,則要求OrderService的insertOrder方法參數(shù)必須是EventModel,更改OrderService的insertOrder方法如下:
public interface OrderService {
void insertOrder(EventModel em);
}
同時(shí),修改OrderService的子類(lèi)代碼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頁(yè)面顯示出錯(cuò)信息,db.error是在本項(xiàng)目的application.properties中配置的。
關(guān)于本次頁(yè)面出錯(cuò)問(wèn)題,還有更深緣由,因?yàn)槲覀冊(cè)?/span>jdonframework.xml中中定義了createMethod,而根據(jù)前面已經(jīng)有ModelHandler子類(lèi)代碼OrderHandler實(shí)現(xiàn),所以,這里不用配置實(shí)現(xiàn),jdonframework.xml的配置應(yīng)該如下:
<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我們使用代碼調(diào)用了OrderService的insertOrder方法,如果使用這樣代碼調(diào)用,無(wú)需要求OrderService的insertOrder的參數(shù)是EventModel了。
Jpetstore整個(gè)開(kāi)發(fā)大部分基于Jdon框架開(kāi)發(fā),特別是表現(xiàn)層,很少直接接觸使用struts原來(lái)功能,Jdon框架的表現(xiàn)層架構(gòu)基本讓程序員遠(yuǎn)離了struts的煩瑣開(kāi)發(fā)過(guò)程,又保證了struts的MVC實(shí)現(xiàn)。
Jpetstore中只有SignonAction這個(gè)類(lèi)是直接繼承struts的DispatchAction,這個(gè)功能如果使用基于J2EE容器的安全認(rèn)證實(shí)現(xiàn)(見(jiàn)JdonNews),那么Jpetstore全部沒(méi)有用到struts的Action,無(wú)需編寫(xiě)Action代碼;ActionForm又都是Model的拷貝,Action和ActionForm是struts編碼的兩個(gè)主要部分,這兩個(gè)部分被Jdon框架節(jié)省后,整個(gè)J2EE的Web層開(kāi)發(fā)方便快速,而且容易得多。
這說(shuō)明Jdon框架確實(shí)是一款快速開(kāi)發(fā)J2EE工具,而且是非常輕量的。
縱觀Jpetstore系統(tǒng),主要有三個(gè)層的配置文件組成,持久層由iBatis的Product.xml等配置文件組成;服務(wù)層由jdon框架的jdonframework.xml組成;表現(xiàn)層由struts的struts-config.xml和jdonframework.xml組成;剩余代碼基本是Model之類(lèi)實(shí)現(xiàn)。
聯(lián)系客服