开发一个网上商店系统
作者:板桥banq
上页
8.5.3 页导航条实现
以上讨论的是在Strut框架下的实现,ModelListForm实现了页面上数据列表显示。除此之外,还要在JSP页面中实现页导航条显示,如下:
[前页] 1 2 3 4 5 6 [后页]
该导航条使用taglib来实现,有专门的taglib开源项目,这里只是简单地实现一下。实现原理参考Jive论坛的页导航条功能,Jive采取在JSP中写入Java代码的方式实现的,该方式不利于重用和封装,改进方法就是将其转换成JSP taglib来实现。
标签库taglib分4种:
PagerTag:为其他taglib初始化环境。
PrevTag: 实现“前页”的链接,如<a href=“/xxx.do?start=10&count=20”>前页</a>。
NextTag: 实现“后页”的链接,如<a href=“/xxx.do?start=30&count=20”>后页</a>。
IndexTag:实现“1 2 3 4 5”的链接,如<a href=“/xxx.do?start=30&count=20”>3</a>。
PagerTag代码的主要方法doStartTag()如下:
public int doStartTag() throws JspException {
// Generate the URL to be encoded
ModuleConfig config = (ModuleConfig) pageContext.getRequest()
.getAttribute(org.apache.struts.Globals.MODULE_KEY);
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest();
StringBuffer url = new StringBuffer(request.getContextPath());
url.append(config.getPrefix());
url.append(pageUrl);
if (pageUrl.indexOf("?") < 0)
url.append("?");
else
url.append("&");
url.append("count=").append(count);
//获得被ModelListAction保存的ModelListForm实例
ModelListForm form = null;
try {
form = (ModelListForm) pageContext.getRequest().getAttribute(actionFormName);
} catch (ClassCastException e) {
Debug.logError("PagerTag not found ");
throw new JspException(" PagerTag not found " + actionFormName);
}
int start = form.getStart();
int allCount = form.getAllCount();
String nextPage = "";
if (form.getHasNextPage())
nextPage = NEXTPAGE;
//暂时保存,供其他标签使用
pageContext.setAttribute(URLNAME, url.toString());
pageContext.setAttribute(START, Integer.toString(start));
pageContext.setAttribute(COUNT, count);
pageContext.setAttribute(ALLCOUNT, Integer.toString(allCount));
pageContext.setAttribute(NEXTPAGE, nextPage);
// Evaluate the body of this tag
return (EVAL_BODY_INCLUDE);
}
PrevTag代码的主要方法doStartTag()如下:
public int doStartTag() throws JspException {
String startStrs = (String) pageContext.getAttribute(PagerTag.START);
int start = Integer.parseInt(startStrs);
String url = (String) pageContext.getAttribute(PagerTag.URLNAME);
String countStrs = (String) pageContext.getAttribute(PagerTag.COUNT);
int count = Integer.parseInt(countStrs);
StringBuffer buf = new StringBuffer(100);
// Print out a left arrow if necessary
if (start > 0) {
disp = true;
buf.append("<a href=\"");
buf.append(url);
buf.append("&start=");
buf.append( (start - count));
buf.append("\" >");
} else
buf.append("");
HttpServletResponse response =
(HttpServletResponse) pageContext.getResponse();
JspWriter writer = pageContext.getOut();
try {
if (disp)
writer.print(response.encodeURL(buf.toString()));
} catch (IOException e) {
throw new JspException("PrevTag error");
}
return (EVAL_BODY_INCLUDE);
}
其他两个标签库与PrevTag类似,有兴趣者可参考Jive代码进一步自行转换实现,因为篇幅限制,这里代码省略。
要在JSP中使用上述标签库,还需要创建一个TLD描述文件,声明需要输入的一些参数变量,格式内容如下:
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>Application Tag Library</shortname>
<uri>http://jakarta.apache.org/taglibs/</uri>
<info></info>
<tag>
<name>pager</name>
<tagclass>com.jdon.strutsutil.taglib.PagerTag</tagclass>
<info> </info>
<attribute>
<name>actionFormName</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>pageUrl</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>count</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
<tag>
<name>prev</name>
<tagclass>com.jdon.strutsutil.taglib.PrevTag</tagclass>
<info> </info>
</tag>
<tag>
<name>next</name>
<tagclass>com.jdon.strutsutil.taglib.NextTag</tagclass>
<info> </info>
</tag>
<tag>
<name>index</name>
<tagclass>com.jdon.strutsutil.taglib.IndexTag</tagclass>
<info>
</info>
</tag>
</taglib>
该TLD文件说明可以在JSP中使用。
/productList.jsp是商店客户浏览商店商品时用的页面,由于商品可能很多,需要多页显示,因此需要使用上述标签库,具体使用如下:
<%@ taglib uri="/WEB-INF/MultiPages.tld" prefix="MultiPages" %>
…
<body bgcolor="#ffffff">
<html:errors/>
<table border="0" cellpadding="2" cellspacing="1" align="center" bgcolor="#D9D9D9">
<TR > <td colspan = "2">
<MultiPages:pager
actionFormName="listForm" pageUrl="/productListAction.do" count="5">
<MultiPages:prev>[ 前页 ]</MultiPages:prev>
<MultiPages:index />
<MultiPages:next>[ 后页 ]</MultiPages:next>
</MultiPages:pager>
</td>
</TR>
<!-- product列表 -->
<logic:iterate
id="product" name="listForm" property="list" type="com.jdon.estore.model.Product">
<TR > <td bgcolor="#ffffff">
<table width="300" border="0" cellspacing="0" cellpadding="0">
<tr><td rowspan="2">
<a href="<%=request.getContextPath()%>/imageShowAction.do?Id=<bean:write name="product" property="productId"/>">
<img src="<%=request.getContextPath()%>/imageShowAction.do?Id=<bean:write name="product" property="productId"/>"
height="120" height="120" border="0" name="ico" />
</a> </td>
<td><bean:write name="product" property="name"/></td>
</tr> <tr>
<td><bean:write name="product" property="description"/></td>
</tr></table>
</td>
</TR>
<TR >
<td bgcolor="#ffffff"><br><br>
</td>
</TR>
</logic:iterate>
<!-- product列表 -->
</table>
</body>
上述页面是显示一个个商品数据,商店客户选择合适的商品加入购物车购买,其中有一段使用了标签库实现页导航条显示。
8.6 购物车功能的实现
商店客户浏览商品时,可以将愿意购买的商品品种放入购物车,购物车功能使用由状态Session Bean实现,首先根据功能需求进行基本对象建模。
经过分析,购物车有两个基本业务对象:ShoppingCart和CartItem。ShoppingCart代表整个购物车,CartItem则是车中每个购买的品种。CartItem和商品Item有一点区别,CartItem包含Item一些属性,但增加了一个购买数量属性。
CartItem的主要属性代码如下:
public class CartItem implements Model {
private String itemId; //品种Id
private String productId; //商品Id
private String name; //名称
private int qty; //购买数量
private float listprice; //购买单价
…
}
ShoppingCart主要属性代码如下:
public class ShoppingCart implements Model {
private String totalCost; //总价格
private Collection items = new ArrayList(); // 购物车中所有CartItem集合
private String[] itemId;
private int[] qty;
…
}
在ShoppingCart中有两个数组型变量,这是用于购物车更新数量,商店客户对购物车中的多个商品品种进行购买数量的修改。那么,这些商品品种的Id和数量数据将被包含在这两个数组中,提交给购物车EJB ShoppingCart处理。
8.6.1 有状态Session Bean
EJB ShoppingCart是一个有状态Session Bean。商店客户在浏览多个商品页面,不断地挑选商品,那么之前的购物状态一定要保存,使用有状态Session Bean保存购物车状态是一个好办法。
注意,有状态Session Bean的引用需要保存在HttpSession中,以便同一个客户再次访问时能获取到同一个有状态Session Bean。所幸的是,因为这里使用了“EJB方法调用框架”,该机制已经被框架实现了,因此就无需再考虑这些细节了。
EJB ShoppingCart接口方法如下:
public interface ShoppingCartLocal extends javax.ejb.EJBLocalObject {
//加入一个CartItem
public void addCartItem(EventModel em) throws Exception;
//更新整个购物车
public void updateCart (EventModel em) throws Exception;
//删除一个CartItem
public void deleteCartItem(EventModel em);
public CartItem getCartItem(String itemId);
public ShoppingCart getShoppingCart();
public Integer getCount();
public void empty();
}
ShoppingCartBean的具体实现方法如下:
public class ShoppingCartBean implements SessionBean {
//该hashMap在内存中维持购物车数据
//Key是itemId, Value是CartItem
private Map cart;
…
public void addCartItem(EventModel em) throws Exception {
CartItem citem = (CartItem) em.getModel();
try {
CatalogEJBLocal catalogEJB = catalogEJBHome.create();
Item item = catalogEJB.getItem(citem.getItemId());
logger.debug(" qty =" + citem.getQty());
if (citem.getQty() < 1)
citem.setQty(1); //因为是加入购物车,如果前台没有提供数量,默认为1
setCartItem(citem, item, citem.getQty());
cart.put(citem.getItemId(), citem); //保存到Map中
} catch (Exception ex) {
logger.error(ex);
throw new Exception();
}
}
public void updateCart(EventModel em) throws Exception {
ShoppingCart shoppingCart = (ShoppingCart) em.getModel();
try {
String[] itemIds = shoppingCart.getItemId();
int[] qtys = shoppingCart.getQty();
int length = itemIds.length;
for (int i = 0; i < length; i++) {
logger.debug(" itemid = " + itemIds[i] + " qty=" + qtys[i]);
updateCartItem(itemIds[i], qtys[i]); //逐个更新
}
} catch (Exception ex) {
logger.error(ex);
throw new Exception();
}
}
//去除一个CartItem
public void deleteCartItem(EventModel em) {
CartItem citem = (CartItem) em.getModel();
cart.remove(citem.getItemId());
}
//获得整个购物车
public ShoppingCart getShoppingCart() {
ShoppingCart shoppingCart = new ShoppingCart();
shoppingCart.setItems(getCartItems());
//购物总金额
shoppingCart.setTotalCost(getStringifiedValue(getSubTotal()));
return shoppingCart;
}
//遍历购物车中所有CartItem,计算总金额
private float getSubTotal() {
Collection citems = getCartItems();
float ret = 0.0f;
for (Iterator it = citems.iterator(); it.hasNext(); ) {
CartItem i = (CartItem) it.next();
ret += (i.getListprice() * i.getQty());
}
return ret;
}
//总金额显示格式计算,显示到小数点后两位
private String getStringifiedValue(float value) {
String subval = "0.00";
if (value > 0.0) {
subval = Float.toString(value);
int decimal_len = subval.length() - (subval.lastIndexOf('.') + 1);
if (decimal_len > 1)
subval = subval.substring(0, subval.lastIndexOf('.') + 3);
else
subval += "0";
}
return subval;
}
8.6.2 Web功能实现
购物车的业务核心都是在EJB中实现,Web层只是将客户输入数据进行整理后,打包成EventMode传送给EJB。
ActionForm的两个子类CartForm和CartItemForm代码类似基本数据对象ShoppingCart和CartItem。
购物车主要功能界面是显示界面,需要从EJB中获得整个购物车情况,然后在浏览器中显示出来。代码如下:
public class CartAction extends Action {
private final static ServiceServerFactory sf = ServiceServerFactory.
getInstance();
public ActionForward execute(ActionMapping actionMapping,
ActionForm actionForm,
HttpServletRequest request,
HttpServletResponse response) throws
Exception {
//通过 EJB方法调用框架调用ShoppingCart EJB
ShoppingCartLocal shoppingCartLocal = (ShoppingCartLocal) sf.getService(
FrameworkServices.ShoppingCartEJB, request);
ShoppingCart cart = shoppingCartLocal.getShoppingCart();
CartForm cartForm = new CartForm();
PropertyUtils.copyProperties(cartForm, cart); //复制数据
//保存到request的attribute中,以便JSP读取
FormBeanUtil.save(cartForm, actionMapping, request);
return actionMapping.findForward("display");
}
}
Struts-config.xml配置如下:
<action attribute="cartForm"
type="com.jdon.estore.web.cart.CartAction"
validate="false" scope="request"
path="/cart/cartAction">
<forward name="display" path="/cart/cart.jsp" />
</action>
具体显示是通过cart.jsp实现。cart.jsp主要代码如下:
<html:form action="/cart/cartAction.do" method="POST">
<input type="hidden" name="action" value="update">
<logic:iterate id="citem" name="cartForm" property="items"
type="com.jdon.estore.model.CartItem">
<!-- 显示每个CartItem数据 -->
<input type="hidden" name="itemId"
value="<bean:write name="citem" property="itemId"/>">
<a href="<%=request.getContextPath()%>/cart/saveCartAction.do?action=remove&itemId=
<bean:write name="citem" property="itemId"/>">
删除</a>
<!-- 选购数量 -->
<input size="3" type="text" name="qty" maxlength="3" value="<bean:write name="citem" property="qty"/>" >
<!-- 选购单价 -->
¥<bean:write name="citem" property="listprice"/>
</logic:iterate>
<input type="submit" value="更新">
<b>合计:</b><bean:write name="cartForm" property="totalCost"/>
上面实现了购物车的显示。除此之外,购物车还有增加CartItem、删除CartItem和更新功能。前两个功能因为是针对CartItem进行的操作,因此需要建立专门的Action,建立CartHandler用于实现CartItem新增和删除。
public class CartHandler implements ModelHandler {
private final static String module = CartHandler.class.getName();
private final static ServiceServerFactory sf = ServiceServerFactory.
getInstance();
public void serviceAction(EventModel em, HttpServletRequest request) throws
java.lang.Exception {
ShoppingCartLocal shoppingCartLocal = (ShoppingCartLocal) sf.getService(
FrameworkServices.ShoppingCartEJB, request);
try {
switch (em.getActionType()) {
case Event.ADD:
shoppingCartLocal.addCartItem(em);
break;
case Event.REMOVE:
shoppingCartLocal.deleteCartItem(em);
break;
}
} catch (Exception ex) {
throw new Exception("System operation Error:" + ex);
}
}
}
CartHandler是使用了增、删、改、查框架的一个部分,struts_config.xml配置如下:
<action name="cartItemForm"
type="com.jdon.strutsutil.ModelSaveAction"
scope="request"
path="/cart/saveCartAction">
<forward name="success" path="/cart/cartAction.do" />
<forward name="failure" path="/cart/cartAction.do" />
</action>
配置显示,购物车加入或删除CartItem后,返回的cartAction也就是显示整个购物车内容。
关于更新整个购物车,因为购物车中有数个CartItem数据,因此,cart.jsp提交的参数是数个itemId和qty,这通过CartForm的下面两个数组接受:
private String[] itemId;
private int[] qty;
修改cartAction的方法execute代码,加入更新功能,代码如下:
String action = request.getParameter("action");
if ( (action != null) && (action.equalsIgnoreCase("update"))) {
ShoppingCart cart = new ShoppingCart();
CartForm cartForm = (CartForm) actionForm;
PropertyUtils.copyProperties(cart, cartForm);
//包装成EventModel
EventModel em = new EventModel();
em.setModel(cart);
shoppingCartLocal.updateCart(em); //调用EJB更新
}
//获取显示整个购物车内容
ShoppingCart cart = shoppingCartLocal.getShoppingCart();
CartForm cartForm = new CartForm();
PropertyUtils.copyProperties(cartForm, cart);
FormBeanUtil.save(cartForm, actionMapping, request);
return actionMapping.findForward("display");
8.7 小结
由于篇幅限制,不可能罗列出此网上商店系统的所有功能实现,但前面系列章节通过框架概念已经基本确定整个系统的架构,订单数据也是增、删、改、查等功能。由于使用了专用框架,这些功能实现可以非常方便而且有效地快速完成,开发者能将更多精力投入客户定制和客户感受等更加细致的工作中。
框架技术和设计模式是J2EE中很重要的软件重用技术,框架技术通过重用提高了软件的生产效率,J2EE项目是通过框架、模式的设计运用达到控制软件质量的目的。虽然这是很多喜欢自由编程的程序员所不喜欢的,但是它带来了稳定的软件质量和可重用的组件,为规模生产创造了条件。
一个软件系统一般由两大部分组成:针对本应用的新设计和可重用的软件组件或框架。如果后者占据越大,无疑需要实现的新设计或实现工作量就越小,生产效率越高,成本越低。因此,可根据自己项目领域特点,在J2EE框架下架构设计自己领域的应用框架,提高在该领域软件开发生产的效率,节约成本。
J2EE正在由“复杂”变得“简单”,通过JSF(JavaServer Faces,JSR-127)实现Web层的简易开发。通过EJB3.0简化EJB配置。同时,O/R Mapping产品的日趋成熟,JDO 2.0标准的制定,这些都促进J2EE的持久层技术开发的不断简化。
在新的技术标准支持下,数据操作通用框架的使用也将变得更加简单,可以通过定义一个XML文件完成开发,或者像JBuilder的EJB可视化开发工具一样,通过图形界面一次性完成一个数据对象操作的全过程。
首页