开发一个网上商店系统

  作者:板桥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();
    }
  }


  // 更新整个购物车,而不是单独一个cartItem更新
  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可视化开发工具一样,通过图形界面一次性完成一个数据对象操作的全过程。

 

 

首页