Java EE项目应用开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.6 典型MVC框架Struts及其应用

随着Java Web开发技术的不断成熟,越来越多的开发人员开始使用Web应用框架,框架为Java Web应用提供了预备的软件架构和相关的软件包,它能够大大提高开发Java Web应用的速度和效率。在众多的Java Web开发框架中,最典型的、应用最为广泛的莫过于Struts框架。

Struts框架是最早发布的基于MVC设计模式的Java Web框架,它是由Apache软件组织提供的一项开源项目,尤其适用于开发大型可扩展的Java Web应用。Struts为Java Web应用提供了一个通用的框架,使得开发人员可以将精力集中在如何解决实际业务问题上。

下面简单介绍一下Struts的基本框架。

1.6.1 Struts框架

Struts框架以ActionServlet作为核心控制器,整个应用由客户端请求驱动。在Web应用启动时加载并初始化核心控制器ActionServlet,它将从配置文件struts-config.xml中读取配置信息,并将它们存放在各种配置对象中,如Action的映射信息存放在ActionMapping对象中。

当客户端向Web应用发送请求时,由Struts的核心控制器ActionServlet拦截请求,检索和用户请求匹配的ActionMapping实例,如果该实例不存在,就返回用户请求路径无效的信息。接着创建一个与请求对应的ActionForm Bean对象,并把客户提交的表单数据保存在其中,根据配置信息决定是否需要进行表单数据校验以及属性重新设置。然后根据ActionMapping实例包含的映射信息决定是否需要调用那个业务逻辑控制器(Action)处理用户请求,通过Action调用业务逻辑模型组件中方法,更新模型数据状态,并返回一个ActionForward对象,决定应用请求转发流程,呈现给用户JSP视图。

在Struts框架中,控制器就是它的核心,Struts控制器由两部分组成:核心控制器和业务逻辑控制器。其中核心控制器就是ActionServlet,由Struts框架提供;业务逻辑控制器就是用户自定义的Action,由应用开发者提供。

Struts是典型的MVC框架,如图1-40显示了实现MVC的Struts框架流程。

图1-40 Struts框架流程

● 视图:就是一组JSP文件,在这些JSP文件中没有业务逻辑,也没有模型信息,只有标签,这些标签可以是标准的JSP标签或客户自定义标签,也可以是Struts标签库中的标签。通常情况下,ActionFrom Bean也是属于视图模块,它被利用来进行视图和控制器之间表单数据的传递:一方面Struts框架把用户输入的表单数据保存在ActionForm Bean中,把它传递给控制器;另一方面控制器可以对ActionForm Bean中的数据进行修改,JSP文件使用Struts标签读取修改后的ActionForm Bean的信息,重新设置HTML表单。

● 模型:表示应用程序的状态和业务逻辑。主要由底层的业务逻辑组件充当,这些业务逻辑组件封装了底层数据库访问、业务逻辑方法实现。对于企业级Java EE应用来说,模型不仅仅是简单的JavaBean,可能是一个或多个EJB组件,也可能是一个WebService服务。Struts框架没有为实现Model组件提供任何支持。

● 控制器:由ActionServlet类和Action类实现。ActionServlet是Struts框架中的核心组件,继承了javax.servlet.http.HttpServlet类,是一个标准的Servlet,它在MVC模型中扮演中央控制器角色,该控制器负责拦截所有HTTP请求,然后根据用户请求决定是否需要调用业务逻辑控制器,如果需要调用业务逻辑控制器,则将请求转发给Action处理,否则直接转向请求的JSP页面。业务逻辑控制器负责处理用户请求,但它本身并不具有处理业务逻辑能力,而是调用Model来完成处理。

1.6.2 Struts框架应用:电子商务网站—购物车应用

为了帮助获得使用Struts开发Java Web应用的经验,讲解一个简单的Struts应用例子——电子商务网站中的购物车应用实现。

1.分析电子商务网站—购物车应用需求

在开发应用时,首先从分析需求入手,列举该应用的各种功能,以及限制条件。电子商务网站—购物车应用的需求比较简单,其包括如下需求:

● 客户浏览商品。

● 添加商品到购物车。

● 显示购物车中商品数量。

● 客户可能更新商品数量、删除购物车中的商品。

● 客户可以结账。

购物车应用处理流程如图1-41所示。

图1-41 购物车应用处理流程

2.数据分析设计

通过分析电子商务网站中购物车应用需求,可以抽取购物车应用中所涉及的实体对象包括产品信息(cart_product)、产品类别(cart_category)、商品信息(cart_item)、商品库存(cart_inventory)、商品供应商(cart_supplier)。产品信息与产品类别是一对多的关系,即某个产品类别下有多个产品;产品信息与商品信息时一对多的关系;商品信息与商品库存是一对一的关系;商品信息与商品供应商存在一对多关系。如图1-42所示是购物车应用中各数据实体关系图。

图1-42 购物车应用中各数据实体关系图

仍然采用电子商务网站—用户注册登录应用所构建数据,创建购物车应用相关数据表,创建数据表的sql语句如下。

● 商品供应商信息表:

        create table eb_c_supplier (
          supplierid char(10) not null,
          suppliername varchar(80),
          status char(2) not null,
          addr1 varchar(80),
          addr2 varchar(80),
          city varchar(80),
          state varchar(80),
          zip varchar(6),
          phone varchar(80),
          constraint pk_supplier primary key (supplierid)
        );

● 产品类别信息表:

        create table eb_c_category (
          cateid char(10) not null,
          name varchar(80),
          descn varchar(255),
          constraint pk_category primary key (cateid)
        );

● 产品信息表:

        create table eb_c_product (
          productid char(10) not null,
          cateid char(10) not null,
          name varchar(80) ,
          descn varchar(255),
          constraint pk_product primary key (productid),
              constraint fk_product_1 foreign key (cateid)
              references eb_c_category (cateid)
        );

● 商品信息表:

        create table eb_c_item (
          itemid char(10) not null,
          productid char(10) not null,
          listprice float,
          unitcost float,
          supplierid char(10) not null,
          status varchar(2),
          attr1 varchar(80),
          attr2 varchar(80),
          attr3 varchar(80),
          attr4 varchar(80),
          attr5 varchar(80),
          constraint pk_item primary key (itemid),
              constraint fk_item_1 foreign key (productid)
              references eb_c_product (productid),
              constraint fk_item_2 foreign key (supplierid)
              references eb_c_supplier (supplierid)
        );

● 商品库存表:

        create table eb_c_inventory (
          invenid char(10) not null,
          itemid char(10) not null,
          itemqty int not null,
          constraint pk_inventory primary key (invenid),
                    constraint fk_inven_1 foreign key (itemid)
                  references eb_c_item (itemid)
        );

3.运用Struts框架

将Struts框架运用于电子商务网站的购物车应用中。Struts框架可以方便迅速地把一个复杂的应用划分为模型、视图和控制器组件,而Struts的配置文件struts-config.xml则可以灵活地组装这些组件,从而简化开发过程。

在电子商务网站项目TopEBus中添加Struts框架支持:

● 加载jar包。

要想获得Struts框架支持,必须下载Struts框架jar包,可以到http://struts.apache.org/下载Struts,下载后直接解压缩到指定目录,然后找到lib目录,将其中的jar包添加TopEBus构建路径中即可。

实际上这里只需要几个jar包即可:antlr.jar,commons-beanutils.jar,commons-digester.jar, commons-fileupload.jar,commons-logging.jar,commons-validator.jar,jakarta-oro.jar,struts.jar。将这几个jar包直接复制到TopEBus项目下WebRoot\WEB-INF\lib目录下即可。

● 配置web.xml。

由于在项目中添加了Struts框架支持,所以需要在Web应用部署描述符web.xml文件中添加Struts配置,配置如【代码1-15】所示。

【代码1-15】 web.xml文件Struts配置片段。

        <!-- 配置struts -->
        <servlet>
            <servlet-name>struts</servlet-name>
            <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
            <init-param>
                <param-name>config</param-name>
                <param-value>/WEB-INF/struts-config.xml</param-value>
            </init-param>
            <load-on-startup>0</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>struts</servlet-name>
            <url-pattern>*.do</url-pattern>
        </servlet-mapping>

● 配置struts-config.xml。

Struts框架允许把应用划分为多个组件,从而提高开发效率。而Struts框架的配置文件struts-config.xml可以把这些组件组装起来,决定如何使用它们。这里仅仅只是添加struts-config.xml文件,暂时没有在里面添加组件,如【代码1-16】所示。

【代码1-16】 struts-config.xml代码片段。

        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation //DTD Struts
Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
        <struts-config>
        <!-- =========Form Bean Definitions=========================-->
                <form-beans> <!—这里配置视图组件ActionForm Bean --></form-beans>
                <!--=========Action Mapping Definitions=============-->
                <action-mappings><!—这里配置控制器组件action --></action-mappings>
                <!-- ========Message Resource Definitions==============-->
                <message-resources parameter=" " /><!—这里配置资源文件 -->
        </struts-config>

4.构建模型组件

在购物车应用中,主要包含一些对应数据表构建的域对象、专门为购物车构建的域对象和对应域对象操作的数据访问JavaBean对象。

数据表域对象包括有对应供应商信息的域对象EB_C_Supplier.java、对应产品信息的域对象EB_C_Product.java、对应产品种类信息的域对象EB_C_Category.java、对应商品项信息的域对象EB_C_Item.java、对应商品库存信息的域对象EB_C_Inventory.java等,因这些是最简单的POJO类,这里略去对应代码。

专门为购物车构建的域对象包括EB_C_CartItem和EB_C_Cart。

其中EB_C_CartItem域对象描述的是购物车中每一项商品(EB_C_Item),包括有该项商品的相关信息、数量以及总价格。此外,定义calculateTotal()方法用于计算该域对象内所有商品的总金额(单价*数量),当然,在设置EB_C_Item的数量时,需要计算总价格,这样才能保持数据一致。其详情如【代码1-17】所示。

【代码1-17】 EB_C_CartItem.java文件。

        public class EB_C_CartItem {
            private EB_C_Item item; // 私有字段
            private int quantity;
            private BigDecimal total;
            public EB_C_Item getItem() {return item;} // JavaBeans方法
            public int getQuantity() {return quantity;}
            public BigDecimal getTotal() {return total;}
            private void calculateTotal() {// 计算总价格
                if (item != null && item.getListprice() != 0.0f) {
                    total=new BigDecimal(item.getListprice()).multiply(new BigDecimal
(quantity));
                } else {total = null;}
            }
            public void setItem(EB_C_Item item) {// 设置Item,同时计算总价格
                this.item = item; calculateTotal();
            }
            public void setQuantity(int quantity) {// 设置数量,同时计算总价格
                this.quantity = quantity; calculateTotal();
            }
            public void incrementQuantity(){//增加Item数量,同时计算总价格
                quantity++; calculateTotal();
            }
        }

EB_C_Cart代表了购物车域对象,购物车用来保存客户所选择的商品,其中提供了添加商品、修改商品数量、删除购物车中商品、查看购物车中商品总金额等方法。在增加EB_C_Item到EB_C_CartItem中后,需要调用cartItem.incrementQuantity()方法使EB_C_Item数量加1,当然也可以使用cartItem.setQuantity(1)方法使cartItem的初始数量为1。计算购物车中所有物品总金额的方法为getSubTotal(),需要注意的是,BigDecimal是一个对象,所以不能直接调用:subTotal=subtotal+listprice*quantity之类的方法,BigDecimal为数据运算提供一系列方法,例如, add(BigDecimal val)表示加;subtract(BigDecimal val)表示减;multiply(BigDecimal val)表示乘;divide(BigDecimal val,int roundingMode)表示除。其详情如【代码1-18】所示。

【代码1-18】 EB_C_Cart.java文件。

        public class EB_C_Cart {
            /** 购物车中所有的商品信息都保存在Map中* */
            private final Map<String, EB_C_CartItem> itemMap = Collections
                .synchronizedMap(new HashMap<String, EB_C_CartItem>());
            public Iterator<EB_C_CartItem>getCartItems(){/** 获得所有cartItem**/
                return itemMap.values().iterator();
            }
            public int getNumberOfItems() {/** 获得EB_C_Item的总数* */
                return itemMap.size();
            }
            public boolean containsItemId(String itemId) {/**是否包含某个EB_C_Item* */
                return itemMap.containsKey(itemId);
            }
            public void addItem(EB_C_Item item) {/** 增加EB_C_Item* */
                EB_C_CartItem cartItem = itemMap.get(item.getItemid());
                if (cartItem == null) {
                    cartItem = new EB_C_CartItem();
                    cartItem.setItem(item);
                    cartItem.setQuantity(0);
                    itemMap.put(item.getItemid(), cartItem);
                }
                cartItem.incrementQuantity();
            }
            public EB_C_Item removeItemById(String itemId) {/** 删除EB_C_Item* */
                EB_C_CartItem cartItem = itemMap.remove(itemId);
                if (cartItem == null) {return null;
                } else {    return cartItem.getItem(); }
            }
            public void incrementQuantityByItemId(String itemId) {/** 增 加
EB_C_Item的数量 */
                EB_C_CartItem cartItem = itemMap.get(itemId);
                cartItem.incrementQuantity();
            }
            public void setQuantityByItemId(String itemId, int quantity) {/** 设
置EB_C_Item的数量* */
                EB_C_CartItem cartItem = itemMap.get(itemId);
                cartItem.setQuantity(quantity);
            }
            public BigDecimal getSubTotal() { /** 计算价格* */
                BigDecimal subTotal = new BigDecimal("0");
                Iterator<EB_C_CartItem> items = getCartItems();
                while (items.hasNext()) {
                    EB_C_CartItem cartItem = (EB_C_CartItem) items.next();
                    EB_C_Item item = cartItem.getItem();
                    BigDecimal listPrice = new BigDecimal(item.getListprice());
                    BigDecimal quantity = new BigDecimal(String.valueOf(cartItem.
getQuantity()));
                    subTotal = subTotal.add(listPrice.multiply(quantity));
                }
                return subTotal;
            }
        }

在购物车应用中,对应域对象操作的数据访问JavaBean对象包括有EB_C_ProductDAO对象、EB_C_CategoryDAO对象以及EB_C_ItemDAO对象等。

其中EB_C_ProductDAO实现对产品信息访问,其中定义两个接口方法:getProductByID(String productid),用于根据产品ID查找对应产品详细信息;getAllProduct(),用于查找所有产品。EB_C_ProductDAO.java源代码详见【代码1-19】。

【代码1-19】 EB_C_ProductDAO.java文件。

        public class EB_C_ProductDAO {
            //略去数据库操作属性定义
            public EB_C_Product getProductByID(String productid){ //根据ID查找产品信息
                EB_C_Product product=null;
                conn = new EBusDb().getCon();      //获取数据库连接对象
                //构建SQL语句:根据产品ID查找产品信息
                String sqlbyid="select * from eb_c_product where productid=?";
            try {
                    pstmt=conn.prepareStatement(sqlbyid); ////建立预处理对象
                pstmt.setString(1,productid); //给占位符赋值
                    rs=pstmt.executeQuery();   //执行查询
                    if(rs.next()){//迭代结果,是否存在符合条件记录
                          product=new EB_C_Product();
                          product.setProductid(rs.getString("productid"));
                          product.setName(rs.getString("name"));
                          product.setDescn(rs.getString("descn"));
                          product.setCateid(rs.getString("cateid"));
                      }
                  } catch (SQLException e) { e.printStackTrace();
                  } finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
                  return product;
              }
              public Collection<EB_C_Product> getAllProduct(){//查找所有的产品
                  Collection<EB_C_Product> coll=new ArrayList<EB_C_Product>();
                  conn = new EBusDb().getCon();
                  String sqlall="select * from eb_c_product";
                  try {
                      stmt=conn.createStatement();////建立预处理对象
                      rs=stmt.executeQuery(sqlall); //执行查询
                      while(rs.next()){//迭代结果
                          EB_C_Product product=new EB_C_Product();
                          product.setCateid(rs.getString("cateid"));
                          product.setName(rs.getString("name"));
                          product.setDescn(rs.getString("descn"));
                          product.setProductid(rs.getString("productid"));
                          coll.add(product);
                      }
                  } catch (SQLException e) { e.printStackTrace();
                  } finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
                  return coll;
              }
        }

EB_C_CategoryDAO实现对产品种类信息访问,其中定义两个接口方法:getCategoryByID (String cateid),用于根据产品种类ID查找对应产品种类详细信息;getAllCategory(),用于查找所有产品种类。EB_C_CategoryDAO.java源代码详见【代码1-20】。

【代码1-20】 EB_C_CategoryDAO.java文件。

        public class EB_C_CategoryDAO {
            //略去数据库操作属性定义
            public EB_C_Category getCategoryByID(String cateid){ //根据ID查找产品
种类信息
                EB_C_Category category=null;
                conn = new EBusDb().getCon();
                String sqlbyid="select * from eb_c_category where cateid=?";
                try {
                    pstmt=conn.prepareStatement(sqlbyid); ////建立预处理对象
                      pstmt.setString(1,cateid); //给占位符赋值
                    rs=pstmt.executeQuery();   //执行查询
                    if(rs.next()){//迭代结果,是否存在符合条件记录
                          category=new EB_C_Category();
                          category.setCateid(rs.getString("cateid"));
                          category.setName(rs.getString("name"));
                          category.setDescn(rs.getString("descn"));
                      }
                  } catch (SQLException e) { e.printStackTrace();
                  }finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
                  return category;
              }
              public Collection<EB_C_Category> getAllCategory(){//查找所有的产品种类
                  Collection<EB_C_Category> coll=new ArrayList<EB_C_Category>();
                  conn = new EBusDb().getCon();
                  String sqlall="select * from eb_c_category";
                  try {
                      stmt=conn.createStatement();   ////建立预处理对象
                      rs=stmt.executeQuery(sqlall); //执行查询
                      while(rs.next()){//迭代结果
                          EB_C_Category category=new EB_C_Category();
                          category.setCateid(rs.getString("cateid"));
                          category.setName(rs.getString("name"));
                          category.setDescn(rs.getString("descn"));
                          coll.add(category);
                      }
                  } catch (SQLException e) { e.printStackTrace();
                  }finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
                  return coll;
              }
        }

EB_C_ItemDAO实现对商品信息访问,其中定义两个接口方法:getItemByID(String itemid),用于根据商品ID查找对应商品详细信息;getItemByProductID(String productid),用于根据产品ID查找该产品对应的所有商品。EB_C_ItemDAO.java源代码详见【代码1-21】。

【代码1-21】 EB_C_ItemDAO.java文件。

        public class EB_C_ItemDAO {
            //略去数据库操作属性定义
            public EB_C_Item getItemByID(String itemid){ //根据ID查找商品信息
                EB_C_Item item=null;
                conn = new EBusDb().getCon();
                String sqlbyid="select * from eb_c_item where itemid=?";
                try {
                    pstmt=conn.prepareStatement(sqlbyid); ////建立预处理对象
                    pstmt.setString(1,itemid); //给占位符赋值
                    rs=pstmt.executeQuery();   //执行查询
                    if(rs.next()){//迭代结果,是否存在符合条件记录
                          item=new EB_C_Item();
                          item.setItemid(rs.getString("itemid"));
                          //略去部分代码,从数据库表字段获取值封装在item对象中
                    }
                } catch (SQLException e) { e.printStackTrace();
                }finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
            }
                return item;
            //根据产品ID查找所有商品信息
            public Collection<EB_C_Item> getItemByProductID(String productid){
                Collection<EB_C_Item> coll=new ArrayList<EB_C_Item>();
                onn = new EBusDb().getCon();
                String sqlallbyproductid="select * from eb_c_item where productid=?";
                try {
                    pstmt=conn.prepareStatement(sqlallbyproductid); ////建立预处理
对象
                    pstmt.setString(1,productid);      //给占位符赋值
                    rs=pstmt.executeQuery();   //执行查询
                    while(rs.next()){//迭代结果
                        EB_C_Item item=new EB_C_Item();
                        item.setItemid(rs.getString("itemid"));
                        //略去部分代码,从数据库表字段获取值封装在item对象中
                        coll.add(item);//将item对象添加到集合容器中
                    }
                } catch (SQLException e) { e.printStackTrace();
                }finally{ ebusdb.closedb(rs, stmt, pstmt, conn); }
                return coll;
            }
        }

5.创建视图组件

在struts应用中,通常会把FormBean对象作为视图组件,在本节购物车应用中,需要构建FormBean对象EB_C_CartForm,用来表示购物车,该对象包含了购物车域对象EB_C_Cart,而EB_C_Cart域对象又包含了EB_C_CartItem域对象。EB_C_CartForm、EB_C_Cart、EB_C_CartItem三者之间的关系如图1-43所示。

图1-43 EB_C_CartForm、EB_C_Cart、EB_C_CartItem关系图

EB_C_CartForm和EB_C_Cart是一种组合关系,EB_C_CartForm对象直接由EB_C_Cart对象组合而成;同样,EB_C_Cart对象与EB_C_CartItem对象也是一种组合关系,EB_C_Cart对象由多个EB_C_CartItem对象组合而成。

在整个在线购物的B2C电子商务网站实现中,由于有很多地方会构建FormBean对象,而这些对象中都需要进行表单校验,因此将基础的表单校验方法单独定义在一个基本的FormBean中,这样其他的FromBean可以继承它从而减少代码的重复。定义基本的FormBean为EB_BasicForm对象,其详情如【代码1-22】所示。

【代码1-22】 EB_BasicForm.java文件。

        public class EB_BasicForm extends ActionForm {
            public ActionErrors validate(ActionMapping mapping,HttpServletRequest
request) {
                ActionErrors actionErrors = null;
                ArrayList<String> errorList = new ArrayList<String>();
                doValidate(mapping, request, errorList);
                request.setAttribute("errors", errorList);
                if (!errorList.isEmpty()) {
                    actionErrors = new ActionErrors();
                    actionErrors.add(ActionErrors.GLOBAL_ERROR, new ActionError
("global.error"));
                }
                return actionErrors;
            }
            public void doValidate(ActionMapping mapping,HttpServletRequest request,
List<String> errors) {}
            protected void addErrorIfStringEmpty(List<String>errors,String message,
String value) {
                if (value == null || value.trim().length() < 1)errors.add(message);
            }
        }

EB_C_CartForm继承了EB_BasicForm,它有一个属性,就是EB_C_Cart。其详情如【代码1-23】所示。

【代码1-23】 EB_C_CartForm.java文件。

        public class EB_C_CartForm extends EB_BasicForm {
            private EB_C_Cart cart = new EB_C_Cart();
            private String theItemId; // 当前正在操作的ItemId
            //属性对应的getter、setter方法
            public void reset(ActionMapping mapping, HttpServletRequest request) {
                super.reset(mapping, request); theItemId = null;
            }
        }

将FormBean配置在Struts核心文件struts-config.xml中,配置代码如【代码1-24】所示。

【代码1-24】 struts-config.xml中的FormBean配置片段。

        <!-- 表单 Bean -->
        <form-beans>
            <form-bean name="basicForm" type="com.etop.topebus.view.forms.EB_
BasicForm"></form-bean>
            <form-bean name="cartForm" type="com.etop.topebus.view.forms.EB_C_
CartForm"> </form-bean>
        </form-beans>

购物车应用是基于Struts实现的,为了更好地表现Struts的应用,我们在页面文件中采用Struts标签,用以替代原来在JSP页面中出现的Java Script。为此,需要在使用Struts标签之前将Struts系列标签库引入到页面文件中。

我们首先需要将Struts标签库的描述文件放到WEB-INF目录下,这里包括struts-logic.tld、struts-html.tld、struts-bena.tld、struts-tiles.tld、struts-nested.tld共5个文件。然后修改web.xml配置文件,在其中加入这些tld文件的定位代码:

        <!-- Struts Tag Library Descriptors -->
        <taglib>
                <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
                <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
        </taglib>
        <taglib>
                <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>
                <taglib-location>/WEB-INF/struts-html.tld</taglib-location>
        </taglib>
        <taglib>
                <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
                <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
        </taglib>

为了通用性,我们将Struts标签库在公共的页头文件中导入,因此,需要修改header.jsp内容,在其中添加如下代码:

        <%@ taglib uri="../WEB-INF/struts-logic.tld" prefix="logic" %>
        <%@ taglib uri="../WEB-INF/struts-html.tld" prefix="html" %>
        <%@ taglib uri="../WEB-INF/struts-bean.tld" prefix="bean" %>

视图组件除了必要的FormBean组件以外,还包括一系列的JSP页面文件,这里我们构建购物车应用页面文件包括有eb_c_cart.jsp和eb_c_checkout.jsp。

eb_c_cart.jsp主要用来显示购物车的状态,并且提供了更新购物车状态的操作。页面中采用Struts的Logic标签库包含的逻辑运算标签如equal、notEqual运算来对EB_C_CartForm中的cart.numberOfItems进行判断,如果结果和0相当,那么就在浏览器中显示标记包含中的代码,否则,不显示这段代码。

在eb_c_cart.jsp中采用Struts的Logic标签库中包含的迭代器标签iterate,用来将购物车中所有的商品项信息显示出来:

        <logic:iterate id="cartItem" name="cartForm" property="cart.cartItems">

它相当于:

        <%
        Iterator<EB_C_CartItem> cartItems=cartForm.getCart().getCartItems();
        EB_C_CartItem cartItem;
        while(cartItems.hasNext()){cartItem=cartsItems.next();
        %>
        <td><%=cartItem.getItem().getProduct().getName() %></td>
        <td><%=cartItem.getItem().getAttr1() %></td>
        <%}%>

eb_c_cart.jsp页面文件详情如【代码1-25】所示。

【代码1-25】 eb_c_cart.jsp文件。

        <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
        <!-- 包含头部JSP -->
        <%@ include file="/include/header.jsp"%><br>
        <table border="0" width="1050" cellspacing="0" cellpadding="0" align=
"center">
        <tr><td valign="top" align="center"><h2 align="center">购物车</h2>
                <html:form action="/cart/updateItem.do" method="post">
                    <TABLE class=font1 cellSpacing=0 borderColorDark=#ffffff
                          cellPadding=2 width="99%" align=center bgColor=#ffffff
                          borderColorLight=#003686 border=1>
                          <tr bgcolor="#C4E669">
                              <td><b>项目 ID</b></td><td><b>产品 ID</b></td>
                              <td><b>说明</b></td><td><b>数量</b></td>
                              <td><b>价格</b></td><td>&nbsp;</td>
                          </tr>
                          <!-- 如果cart.numberOfItems是0,表示还没有往购物车中添加商品 -->
                          <logic:equal name="cartForm" property="cart.numberOfItems"
value="0">
                              <tr bgcolor="#fffdd7">
                                  <td colspan="6"><b>购物车中没有货物.</b></td>
                              </tr>
                          </logic:equal>
                          <!-- 利用struts迭代器标签显示商品信息 -->
                          <logic:iterate id="cartItem" name="cartForm" property=
"cart.cartItems">
                              <tr bgcolor="#fffdd7"><td><b>
                                  <html:link paramId="itemId" paramName="cartItem"
                                  paramProperty="item.itemid" page="/product/viewItem.
jsp">
                                        <bean:write name="cartItem" property="item.
itemid" />
                                  </html:link></b>    </td>
                                  <td><bean:write name="cartItem" property="item.
productid" /></td>
                                  <td>
                                        <!-- 输出商品的信息 -->
                                        <bean:write name="cartItem" property="item.
product.name" />
                                        <bean:write name="cartItem" property="item.
attr1" />
                                        <bean:write name="cartItem" property="item.
attr2" />
                                        <bean:write name="cartItem" property="item.
attr3" />
                                        <bean:write name="cartItem" property="item.
attr5" />
                                    </td>
                                    <td align="center">
                                        <!-- 更新商品数量的输入框 -->
                                        <input type="text" size="3" name="<bean:write
name="cartItem"
                                        property="item.itemid"/>" value="<bean:write
name="cartItem"
                                        property="quantity"/>" />
                                    </td>
                                    <td align="right">
                                        <bean:write name="cartItem" property="item.
listprice" />
                                    </td>
                                    <td>
                                        <html:link paramId="theItemId" paramName=
"cartItem"
                                            paramProperty="item.itemid" page="/cart/
removeItem.do">
                                            <img border="0" src="../images/button_
remove.gif" />
                                        </html:link>
                                    </td>
                                </tr>
                            </logic:iterate>
                            <tr bgcolor="#fffdd7">
                                <td colspan="5" align="right">
                                    <b>小计: <bean:write name="cartForm" property="cart.
subTotal" /></b>
                                    <br /><input type="image" border="0" src="../images/
button_update
                                    _cart.gif" name="update" />
                                </td> <td>&nbsp;</td>
                            </tr>
                      </table>
                  </html:form>
                  <!-- 如果cart.numberOfItems是0,那么不显示结账链接 -->
                  <logic:notEqual name="cartForm" property="cart.numberOfItems"
                      value="0"> <br />
                      <center>
                            <html:link page="/cart/eb_c_checkout.jsp">
                                <img border="0" src="../images/button_checkout.gif"
/>
                            </html:link>
                        </center>
                    </logic:notEqual>
                </td>
          </tr>
          </table><br>
          <!-- 包含尾部JSP -->
          <%@ include file="/include/footer.jsp"%>

eb_c_checkout.jsp表示对购物车中的购物信息进行统计展示,并提供返回购物车和进行结账操作的入口,其页面如【代码1-26】所示。

【代码1-26】 eb_c_checkout.jsp文件。

        <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
        <!-- 包含头部JSP -->
        <%@ include file="/include/header.jsp"%><br>
        <table border="0" width="1050" cellspacing="0" cellpadding="0" align=
"center">
        <tr>
            <td valign="top" align="center">
                <h2 align="center">
                    <html:link page="/cart/eb_c_cart.jsp">&lt;&lt; 购物车</html:
link> ::购物统计
                </h2>
                <TABLE class=font1 cellSpacing=0 borderColorDark=#ffffff
                    cellPadding=2 width="99%" align=center bgColor=#ffffff
                    borderColorLight=#003686 border=1>
                    <tr bgcolor="#C4E669">
                          <td><b>项目 ID</b></td><td><b>产品 ID</b></td>
                          <td><b> 介 绍 </b></td><td><b> 数 量 </b></td><td><b> 价 格
</b></td>
                    </tr>
                    <logic:iterate id="cartItem" name="cartForm"
                          property="cart.cartItems">
                          <tr bgcolor="#fffdd7">
                              <td>
                                  <b><html:link paramId="itemid"paramName="cartItem"
                                            paramProperty="item.itemid" page="/product/
viewItem.jsp">
                                            <bean:write name="cartItem" property=
"item.itemid" />
                                        </html:link></b>
                              </td>
                              <td>
                                  <bean:write name="cartItem" property="item.
productid" />
                              </td>
                              <td>
                                  <bean:write name="cartItem" property="item.product.
name" />
                                  <bean:write name="cartItem"property="item.attr1"/>
                                  <bean:write name="cartItem"property="item.attr2"/>
                                  <bean:write name="cartItem"property="item.attr3"/>
                                  <bean:write name="cartItem"property="item.attr5"/>
                              </td>
                              <td align="center">
                                  <bean:write name="cartItem" property="quantity" />
                              </td>
                              <td align="right">
                                  <bean:write name="cartItem" property="item.
listprice" />
                              </td>
                          </tr>
                      </logic:iterate>
                      <tr bgcolor="#fffdd7">
                          <td colspan="5" align="right">
                              <b>小计: <bean:write name="cartForm" property="cart.
subTotal" /></b><br />
                          </td>
                      </tr>
                  </table><br />
                  <center>
                      <!—购物车统计后,可以进行结账处理,此处为结账入口 -->
                      <html:link page="/order/createOrderForm.do">
                          <img border="0" src="../images/button_continue.gif" />
                      </html:link>
                  </center>
            </td>
            <td valign="top" width="20%" align="right">    &nbsp;</td>
        </tr>
        </table><br>
        <!-- 包含尾部JSP -->
        <%@ include file="/include/footer.jsp"%>

6.创建控制器组件

购物车应用中,包括向购物车中添加商品,更新购物车中某项商品的数量以及从购物车中删除某项商品,因此我们在实现过程中定义多个控制器组件来实现上述功能,包括AddItemAction组件用以处理添加商品项、RemoveItemAction组件用于处理删除商品项以及UpdateItemAction组件用以更新商品项的数量。

AddItemAction核心方法execute,它操作处理客户请求的过程,如果EB_C_Cart中已经存在有商品项EB_C_Item,那么就增加这个EB_C_Item的数量;否则,在EB_C_Cart中添加这个商品项EB_C_Item。AddItemAction.java文件源代码如【代码1-27】所示。

【代码1-27】 AddItemAction.java文件。

        public class AddItemAction extends Action {
            public ActionForward execute(ActionMapping mapping, ActionForm form,
                HttpServletRequest   request,   HttpServletResponse   response)
    throws Exception {
                EB_C_CartForm cartForm = (EB_C_CartForm) form;
                EB_C_Cart cart = cartForm.getCart();
                String theItemId = cartForm.getTheItemId();
            <!—如果购物车中已经存在该商品项,则直接添加该商品项的数量;否则,添加这个商品项 -->
                if (cart.containsItemId(theItemId)) {
                    cart.incrementQuantityByItemId(theItemId);
                } else {
                    EB_C_ItemDAO dao = new EB_C_ItemDAO();
                    EB_C_Item item = dao.getItemByID(theItemId);
                    cartForm.getCart().addItem(item);
                }
                <!—添加商品项处理成功,页面转发到“success”所代表的逻辑视图 -->
                return mapping.findForward("success");
            }
        }

由于向购物车中添加商品项或增加商品项的数量成功后,页面会被转发到success所代表的逻辑视图,而逻辑视图则在struts-config.xml文件中配置,其对应有物理视图页面cart/eb_c_cart.jsp。该控制器组件在struts-config.xml文件中配置片段为:

        <action name="cartForm" path="/cart/addItem" scope="session"
                type="com.etop.topebus.control.action.AddItemAction">
                <forward name="success" path="/cart/eb_c_cart.jsp"></forward>
        </action>

其中,name属性对应FormBean组件EB_C_CartForm配置逻辑名;path属性对应页面请求路径,即页面form表单中action属性所对应值;type属性对应控制器组件的完整限定名称类;scope属性表示所配置的控制器作用范围。

UpdateItemAction组件更新购物车中商品数量,UpdateItemAction.java文件源代码如【代码1-28】所示。

【代码1-28】 UpdateItemAction.java文件。

        public class UpdateItemAction extends Action {
            public ActionForward execute(ActionMapping mapping, ActionForm form,
                HttpServletRequest request, HttpServletResponse response) throws
Exception {
                EB_C_CartForm cartForm = (EB_C_CartForm) form;
                Iterator<EB_C_CartItem>cartItems=cartForm.getCart().getCartItems();
                while (cartItems.hasNext()) {
                    EB_C_CartItem cartItem = cartItems.next();
                    String itemId = cartItem.getItem().getItemid();
                    try {
                          int quantity = Integer.parseInt(request.getParameter(itemid));
                          cartForm.getCart().setQuantityByItemId(itemId, quantity);
                          if (quantity < 1)       cartItems.remove();
                      } catch (Exception e) {}
                  }
                  return mapping.findForward("success");
              }
        }

相应的,该控制器组件在struts-config.xml文件中配置片段为:

        <action name="cartForm" path="/cart/updateItem" scope="session"
                type="com.etop.topebus.control.action.UpdateItemAction">
                <forward name="success" path="/cart/eb_c_cart.jsp"></forward>
        </action>

RemoveItemAction组件用于删除购物车中商品,RemoveItemAction.java文件源代码如【代码1-29】所示。

【代码1-29】 RemoveItemAction.java文件。

        public class RemoveItemAction extends Action {
            public ActionForward execute(ActionMapping mapping, ActionForm form,
                HttpServletRequest request, HttpServletResponse response) throws
Exception {
                EB_C_CartForm cartForm = (EB_C_CartForm) form;
                EB_C_Item item = cartForm.getCart().removeItemById(cartForm.
getTheItemId());
                if (item == null) {
                    request.setAttribute("message","Attempted to remove null
CartItem from Cart.");
                    return mapping.findForward("error");
                } else {
                    return mapping.findForward("success");
                }
            }
        }

相应的,该控制器组件在struts-config.xml文件中配置片段为:

        <action name="cartForm" path="/cart/removeItem" scope="session"
                type="com.etop.topebus.control.action.RemoveItemAction">
                <forward name="success" path="/cart/eb_c_cart.jsp"></forward>
        </action>

在RemoveItemAction组件中,当从购物车中删除商品发生问题的时候,请求将被转发到一个叫error的逻辑视图中,该逻辑视图同样在struts-config.xml文件中配置,其物理视图对应整个应用的全局异常页面。配置信息如下:

        <!-- 全局转发 -->
        <global-forwards>
            <forward name="error" path="/include/error.jsp"></forward>
        </global-forwards>

7.部署运行购物车应用

至此,已经将在线购物的B2C电子商务网站中的购物车应用完全实现。结合注册登录应用,重新部署一下项目TopEBus即可完成请求。