引言 现在,开发和管理一个电子商务系统需要高效率的开发和利用网络资源,特别是如果你想让你的顾客在网上购买你的产品或是取得你提供的服务的话,更要注意这一点。构建一个这样的商务网站来实现你商业上的目的并不是一件非常简单的工作,在开发电子商务网站的时候,我们就要充分的利用搞技术含量的技术,我们可以利用到最先进的Java 技术:Java Server Pages(JSP),Java Servlets 和JavaBeans(甚至是EJB),它们各自都有自己的不同的优点,因此了解在构建一个电子商务网站时如何合理的利用它们各自的优势,并且把它们联合起来以完成你想达到的效果是非常重要的。 当然,我们可以只使用 JSP来构建电子商务系统,比如一个简单的购物车系统,但是如果你要想完成一个有效的的应用程序,并使它用于商业上,则需要综合以上我所说的三种技术相互补充的力量。让我们来看怎么把它们联合起来以完成最好的效果吧!我们都知道,JSP是Sun公司倡导的用来替代微软ASP的纯Java替代品,JSP技术发展壮大了Java Servlet技术,事实上, JSP引擎在运行JSP时也把JSP页面翻译成 Servlets;而不用我多说,大家一定都知道Servlets在网络编程世界是非常流行的,因为它在理论上和实践上都是可以完全取代 CGI脚本的,Servlets能产生动态的网页,这是通过把静态的HTML与数据库查询或事务性服务提供的内容混合起来实现的。JSP则是通过在HTML页面中内嵌Java代码这条途径来实现生成动态的网页的目的的,把Java代码插入到HTML页的这种能力极大的增加了基于Java Servlet网络体系结构的灵活性。 为了产生 HTML , servlet 必须用println()输出格式化的HTML字符串,如: out.println("<html>"); out.println("<br><br>购物车系统"); out.println("</html>"); 从上面的代码段中可以看出,servlet用println()输出HTML页面,也就是说,当编写一个 Java Servlet时,开发者必须充当程序员和网页设计师两个身份。而JSP则是在HTML中嵌入简单的Java代码,使普通的HTML网页设计师也能写出优秀的动态网页,这样就使网站的设计沿着两条平行的轨道,即Java程序设计和HTML页面设计共同进行下去,从而加快网站开发的速度和开发的质量。JSP也支持业务逻辑组件和现有的组件之间的宽松连接,从而做到可重用性。 下面,我想通过一个简单的购物车程序来说明一下 JSP,Servlet和Bean在网络体系结构中是怎样相互作用的,并且借这个例子解释编写一个实际可用的电子商务应用程序应该注意的一些问题。 简单购物车的实现方案 我们的购物车方案实际上是一种简化了的在线商店的模型:顾客选择商品,添加到他们的购物车中,然后通过一系列形式最终购买这些商品。上图中就显示了我们的应用程序体系结构是如何把 JSP、servlets 和 JavaBeans有机的结合起来的,从图上更可以看出,只使用 JSP来构建一个简单网络应用程序是可行,但是一个有效的应用程序是这三种技术共同作用的结果。 解释了model-view-controller( MVC )模式,它把应用程序划分成独立的数据管理(Model),表现形式(View)和控制组件(Controller),成为最先进的图形用户接口的基础,这些划分模块支持独立开发并且可以重复使用组件。我们也能把 MVC 模式应用于我们的网络应用程序中:JSP最适合充当实现网络应用程序的对外表现的部分;而JavaBeans封装了提供给Web网站的服务信息内容并且简化了数据在体系结构组件之间的传输;Servlet则正好充当控制者和协调用户请求和应用程序信息、更新程序数据等功能。 好,前面我们已经有了“CustomerServlet”大致的设计方案,现在让我们来看看编写我们应用程序上的一些细节问题。 代码1 CustomerServlet.java package shoppingcart; import javax.servlet.*; import javax.servlet.http.*; import java.io.IOException; public class CustomerServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } // 处理顾客请求 public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { // 取得请求的Session对象 HttpSession session = request.getSession(true); BasketBean basket = null; file://如果没有购物车则创建一个新的如果已存在,则更新它 basket = (BasketBean)session.getAttribute(BasketBean.BASKET); if(basket == null) { // 新的顾客,创建一个购物车。 basket = new BasketBean(); session.setAttribute(BasketBean.BASKET, basket); } else { // 已存在的顾客,保存篮中的内容。 basket.savePurchases(request); } // 取得当前的工作流程。 RequestDispatcher rd = null; String nextPage = request.getParameter(BasketBean.PAGE); if (nextPage == null || nextPage.equals(BasketBean.UPDATE)) { // 从目录中查找选择 rd = getServletConfig().getServletContext().getRequestDispatcher("Inventory.jsp"); } else if (nextPage.equals(BasketBean.PURCHASE)) { // 提供购买信息 rd = getServletConfig().getServletContext() .getRequestDispatcher("Purchase.jsp"); } else if (nextPage.equals(BasketBean.RECEIPT)) { file:// 提供购买信息 rd = getServletConfig().getServletContext().getRequestDispatcher("Receipt.jsp"); } if (rd != null) { rd.forward(request, response); } } } 上面的程序段显示了CustomerServlet类中的doGet()和doPost()方法。CustomerServlet类做了两件事情来控制我们应用程序的工作流程: 一、通过 BasketBean 类实现,保持购物车组件的状态,; 二、它通过一系列的JSP页面向顾客发送请求。 一旦我们的购物车与某一个特定的顾客session相联系,顾客的BasketBean对象的实例就会存储在 HttpSession对象中。一个以普通ID进入CustomerServlet工作流程的客户,他会产生很多动作,servlet引擎提供了HttpSession对象来组织并存储这一系列的相互作用(Session对象为存储和取回任何使用唯一的键/值对的Java对象提供了方便的方法)。在CustomerServlet类中 ,我们首先通过HttpSession session = request.getSession(true)从 servlet 引擎中取得Session对象,大家都可以看到,我们传递了true值,意思是我们告诉Servlet引擎,如果一个session对象已经不存在了,就新建一个;然后我们查看Session中是否有我们的购物车,如果我们找不到购物车,我们就知道这个购物Session刚刚开始,我们必须新建一辆购物车,并且把它保存在Session对象中,如果我们能够在Session中找到购物车,那我们就知道顾客正处在一个购物期间,那时就必须存储购物车当前的状态。在查看了购物车的状态之后,我们把顾客的请求发送到相应的JSP页中去,请求本身包含一个状态参数(BasketBean.PAGE)告诉CustomerServlet把请求发送到哪里,我们的控制器取回这个参数,然后使用一个RequestDispatcher对象把请求提交给下一个JSP页。 代码段2:BasketBean类 package shoppingcart; import javax.servlet.http.HttpServletRequest; import java.util.Hashtable; import java.util.Enumeration; public class BasketBean { final static public String BASKET = "Basket" ; final static public String PAGE = "Page" ; /* 工作流程的状态 */ final static public String UPDATE = "Update" ; final static public String PURCHASE = "Purchase" ; final static public String RECEIPT = "Receipt" ; /* 当前购物篮中有那些商品。 主键是商品号 ,值为 Product对象 */ private Hashtable products_ = new Hashtable(); public BasketBean() { } /* 计算篮中的商品的总价值。 */ public double getTotal() { double totalPrice = 0.0 ; Enumeration e = products_.elements(); while(e.hasMoreElements()) { Product product = (Product)e.nextElement(); totalPrice += product.getPieces() * product.getPrice(); } return totalPrice; } /* 取得篮中某个商品的个数。 */ public double getPieces(Product p_in_inv) { int SKU = p_in_inv.getSKU(); Product p = (Product)products_.get( Integer.toString(SKU)); if(p == null) return 0.0 ; else return p.getPieces(); } /* 用当前的选择更换篮中的内容。 */ public void savePurchases(HttpServletRequest request) { Product[] products = InventoryBean.getCatalogue(); String[] lbValues = request.getParameterValues("pieces" ); if (lbValues != null) { products_.clear(); for (int i = 0 ; i < lbValues.length; i++) { double lbs = Double.parseDouble(lbValues[i]); if(lbs > 0 ) { Product p = null; p = (Product)products[i].clone(); p.setPieces(lbs); products_.put(Integer.toString(p.getSKU()), p); } } } } file://利用一个函数实现统一的显示商品价格的方式 public static String getStringifiedValue(double value) { String subval = "0.00" ; if (value > 0.0 ) { subval = Double.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; } /* 清空篮内的东西 */ public void clear() { products_.clear(); } } 代码段2中 BasketBean类实现购物车应用程序中一个简单的数据管理模型,它提供了一个方法,用于取得一个顾客正在购买的货物的信息,还提供了一个方法用来更新购物车的内容,我们使用一张哈希表来维护顾客请求的商品列表。InventoryBean对象管理商品的目录,我们是使用一个数组来实现商品的目录的。每个Product类的实例存储四个属性:商品名字,商品标识号,单价和购买的数量,只要顾客购买了东西,Product类的实例就会改变。 JSP显示页面 我们的购物车方案中设定了3个JSP 页面: Inventory.jsp , Purchase.jsp和Receipt.jsp (代码见下)。应用程序发送Inventory.jsp页面给新来的顾客,顾客通过不断的更新Inventory.jsp,来挑选商品;顾客选择好想要购买的商品以后,应用程序就会把顾客的购买请求发送到Purchase.jsp页,进行处理;最后,顾客证实确实购买这些商品,应用程序把顾客请求发送到Receipt.jsp. 我和说明的方便,我想把JSP的基本内容再简要的向大家介绍一下。JSP页面是使用特定的JSP标记与标准的 HTML混合,除了固定的模板数据以外,JSP页还能包括指令,脚本单元和动作,我们购物车系统也说明了以上三点,现在我想就这三个问题简单的谈一谈。 在JSP页面中,我们可以使用JSP指令将一些与页面有关的信息传递到JSP引擎,指令的主要作用就是用来与JSP引擎之间进行沟通的,JSP中的指令是有语法规范的:<%@ directive %> page指令 page指令定义了一系列与JSP页面相关的属性,并用它们与JSP引擎进行通信。例如, 在Inventory.jsp中使用的一条page指令:<%@ page buffer="5kb" language="java" import="shoppingcart.*" errorPage="Error.jsp" %> 这条指令告诉JSP引擎,输出缓冲区大小是5k,在溢出之前输出缓冲区的输出流,它也向JSP引擎说明,当前页使用的脚本语言是Java语言,并请求引擎导入shoppingcart包中的所有Java类,最后,它还指示如果有任何无法处理的错误,就重定向到Error.jsp页面中去。因为在JSP1.1中,只能使用 Java作为脚本语言,所以我们可以省略page指令中关于脚本语言的那部分说明。 include指令用来指定JSP文件被编译的时候,需要插入的文本或者代码,被包含的文件要能够被JSP引擎控制和访问。Inventory.jsp文件也使用了include指令: <%@ include file="header.html" %> <%@ include file="footer.html" %> 第一个指令为我们的页面插入了一个标准的页眉;第二个指令则插入一个标准的注脚。我们可以使用这些指令为我们的JSP页面创造一致的外观。 JSP脚本元素为你提供了把Java代码插入由当前的JSP页面产生的Servlet功能。在JSP中,有三种脚本语言元素---声明、小脚本和表达式。这些元素的语法形式是: <%! declaration; %> <% scriptlet %> <%= expression %> Inventory.jsp中三种元素都使用了。 下面的JSP片段用来声明局部变量保存当前购物篮(BasketBean的实例)和产品目录。 <%! BasketBean basket; Product[] catalogue; %> 从上面我们可以看出,JSP 声明必须以一个分号结束,并且这个声明的范围是整个JSP页。 声明这些局部变量以后,Inventory.jsp 使用一段小脚本从session对象中取回购物车对象(BasketBean)和商名目录,如下。 <% basket =(BasketBean) session.getAttribute( BasketBean.BASKET); catalogue = InventoryBean.getCatalogue(); %> 所以我们可以看出,JSP 声明和 JSP小脚本只是放在特定的JSP标记之间的Java代码,当JSP引擎把一个JSP程序翻译成一个servlet时,它就把这些Java代码内嵌到新的Servlet代码中去。 前面我们说过,我们从一个session对象中取得购物车对象,这个session对象是一个内部物体。JSP引擎提供一些内部隐含对象,这些对象可以直接被引用,我们可以不事先声明,也不需要专门的代码来创建他们的实例。它们在小脚本和表达式中总是可以使用的,而不需要预先声明。JSP 1.1 规范中列出内部隐含对象完整的集合。在程序中,另一个象这样的对象就是 HttpServletRequest对象(request),它在CustomerServlet类中作为一个参数被传递到 doPost()方法中,这就意味着Inventory.jsp 能够通过调用request.getSession(true).getAttribute(BasketBean.BASKET)来取回购物车的信息了。 在JSP中表达式和小脚本为JSP动态生成网页提供一个强有力的工具。从下面的Inventory.jsp程序中我们可以看出,JSP代码段循环访问商品目录并且动态地为每个产品产生HTML表格,我们使用小脚本编写循环,然后在每一行中都混合使用HTML和JSP表达式。 (注:JSP引擎把小脚本标记之间的Java 代码直接插入引擎内部产生的 servlet 代码。JSP引擎对待JSP表达式也是不同的。它先把JSP表达式变换成字符串,然后在内部产生的servlet中把它们包入一个out.println()调用中。) 代码段3 Inventory.jsp <!-- Inventory.jsp - 显示商品目录并且获取用户购买的物品及其数量单价等信息 --> <html> <%-- 页面的头部,使用另一个HTML页面,通过include指令来实现 --%> <%@ include file="header.html" %> <!-- 显示标题 --> <BR> <CENTER> <TITLE> 中国科学技术大学百货商店</TITLE> <FONT SIZE="+1"> <B> 欢迎选购我们的商品</B> </FONT> <BODY BGCOLOR="#FFFFF"> <!-- Page指令 --> <%@ page import="shoppingcart.*" errorPage="error.jsp" %> <%-- 创建表单设定页面布局 --%> <BR><BR> <FORM METHOD="post" ACTION="/shoppingcart/CustomerServlet"> <TABLE WIDTH=450 CELLSPACING="0" CELLPADDING="0" BORDER="1"> <%-- 创建表头 --%> <TR> <TD WIDTH=5% BGCOLOR="#ECA613"> <B>商品序列号</B></TD> <TD BGCOLOR="#ECA613"> <B>商品描述</B></TD> <TD WIDTH=5% ALIGN=center BGCOLOR="#ECA613" > <B>商品数量</B></TD> <TD WIDTH=25% BGCOLOR="#ECA613"> <B>单价</B> </TD> </TR> <%-- 声明一个购物篮,和一个Product商名目录。 --%> <%! BasketBean basket; Product[] catalogue; %> <%-- 从HttpSession对象中取回当前的购物篮,然后从Inventory对象中取回商品列表 --%> <% basket = (BasketBean)session.getAttribute(BasketBean.BASKET); catalogue = InventoryBean.getCatalogue(); %> <%-- 循环显示出Product中商品列表中的每一个商品 --%> <% for(int i = 0; i < catalogue.length; i++) { Product product = catalogue[i]; %> <TR> <TD> <%= product.getSKU() %> </TD> <TD> <%= product.getName() %> </TD> <TD> <INPUT TYPE=text SIZE=6 MAXLENGTH=6 NAME="pieces" VALUE=<%= basket.getPieces(product)%>> </TD> <TD ALIGN=right> <%= product.getPrice() %> </TD> </TR> <% } %> <%-- 表中显示总共化去多少钱 --%> <TR> <TD COLSPAN=4 align=right BGCOLOR="#ECA613"><B> 总共人民币<%= basket.getStringifiedValue(basket.getTotal()) %>元 </B></TD> </TR> </TABLE> <%-- 发送购买请求 --%> <BR> <TABLE WIDTH=450 CELLSPACING="0" CELLPADDING="0" BORDER="0"> <TR> <TD ALIGN=left><INPUT TYPE=submit NAME=<%= BasketBean.PAGE %> VALUE=<%= BasketBean.UPDATE %>></TD> <TD ALIGN=left><INPUT TYPE=reset VALUE="取消"></TD> <TD ALIGN=right><INPUT TYPE=submit NAME=<%= BasketBean.PAGE %> VALUE=<%= BasketBean.PURCHASE %>></TD> </TR> </TABLE> </FORM> </CENTER> <BR><BR> <%@ include file="footer.html" %> </body> </html> Receipt.jsp程序中,我们使用了JSP动作,来处理顾客发送来请求的参数值。因此我也想简要的介绍一下JSP中的动作元素。除了指令和脚本元素外,JSP动作也是 JSP页面不可缺少的一部分,一个动作元素有两种基本的语法形式: <prefix:tag attr_list /> <prefix:tag attr_list> <body> </prefix:tag> 当动作有语句体时,我们必须使用后一种表式方法。动作背后基本的概念就是与特定的JSP标签联系的“标签处理器”。这些处理器是基于标签的一些代码段,用来执行某些操作。JSP引擎提供了一些标准的动作,所有的这些动作都要以“ jsp ”为前缀。例如,我们的电子商店使用一个助手Bean来简化请求参数分析,我们就要使用<jsp:useBean>元素声明这样一个Bean:<jsp:useBean id="receiptBean" scope="request" class="shoppingcart.ReceiptBean" /> JSP声明了一个对象变量,取名为receiptBean,作为 ReceiptBean 的一个实例,在当前请求完成时就会被释放。使用Bean的主要优点就是它分析并且返回 HTML请求参数的简洁性。 在声明完Bean以后,我们就可以使用<jsp:setProperty>元素从HTML请求中获取参数,来设置它的属性值。我们可以显式的指出属性和HTML 参数的名字,来设置属性值。例如,下面是Receipt.jsp中用来设置我们的ReceiptBean实例的属性的一些语句:<jsp:setProperty name="receipt_bean" property="name" param="name" /> 如果我们的属性名和相应的参数名相同,我们可以指示用一个JSP元素来设置所有的属性:<jsp:setProperty name="receipt_bean" property="*" /> 这个单独的元素告诉引擎,使用 Java映像来匹配所有的JSP参数和JavaBean属性名,然后使用HTML请求信息中的值来设置JavaBean的属性值。同样,我们使用<jsp:getProperty>元素从助手Bean中返回属性值。例如,下面是我们返回属性的语句:<jsp:getProperty name="receipt_bean" property="name" /> 在ReceiptBean类的代码中,每个在Receipt.jsp中被分析的参数,在我们程序Bean中都有一个相关联的用来设置了取得的方法:例如,<jsp:setProperty name="receipt_bean" property="name" param="name" />有一个相关联的设置方法:void setName(String phone);同样, <jsp:getProperty name="receipt_bean" property="name" />也有一个相关联的取得方法:String getName(); 代码段4:Receipt.jsp <%@ page import="shoppingcart.*" errorPage="error.jsp" %> <html> <TITLE> 中国科学技术大学百货商店收银台</title> <BODY BGCOLOR="#FFFFF"> <BR><BR> <%@ include file="header.html" %> <CENTER> <BR> <FONT SIZE="+1"> <B> 顾客信息</B> </FONT> <BR><BR> <FORM METHOD="post" ACTION="/shoppingcart/CustomerServlet"> <TABLE WIDTH=450 CELLSPACING="0" CELLPADDING="0" BORDER="1"> <TR> <TD WIDTH=10% BGCOLOR="#ECA613"> <B>姓名:</B></TD> <TD WIDTH=90%> <INPUT TYPE=text SIZE=50 NAME="name" ></TD> </TR> <TR> <TD WIDTH=10% BGCOLOR="#ECA613"> <B>EMail: </B></TD> <TD WIDTH=90%> <INPUT TYPE=text SIZE=50 NAME="email"></TD> </TR> <TR> <TD WIDTH=10% BGCOLOR="#ECA613" > <B>地址</B></TD> <TD WIDTH=90%> <INPUT TYPE=text SIZE=50 NAME="address"></TD> </TR> <TR> <TD WIDTH=10% BGCOLOR="#ECA613"> <B>电话</B> </TD> <TD WIDTH=90%> <INPUT TYPE=text SIZE=50 NAME="phone"></TD> </TR> </TABLE> <% BasketBean basket = (BasketBean)session.getAttribute(BasketBean.BASKET); %> <B> 总共价格人民币<%= basket.getStringifiedValue(basket.getTotal()) %>元 </B> <BR> <BR> <TABLE WIDTH=450 CELLSPACING="0" CELLPADDING="0" BORDER="0"> <TR> <TD ALIGN=left><INPUT TYPE=reset VALUE="Reset"></TD> <TD ALIGN=right><INPUT TYPE=submit NAME=<%= BasketBean.PAGE %> VALUE=<%= BasketBean.RECEIPT %> ></TD> </TR> </TABLE> </FORM> </CENTER> <BR><BR> <%@ include file="footer.html" %> </body> </html> 代码段5:Confirm.jsp <!-- Confirm.jsp - 确认顾客购买商品,并且显示账单 --> <html> <!-- 页眉 --> <%@ include file="header.html" %> <BODY BGCOLOR="#FFFFF"> <CENTER> <TITLE> Grocery Joe´s</TITLE> <FONT SIZE="+1"> <B>Receipt</B> </FONT> <!-- 使用Page指令导入页面 --> <%@ page import="shoppingcart.*" errorPage="error.jsp" %> <!-- 声明ReceiptBean的实例,用从request流中的数据设置属性 --> <jsp:useBean id="receipt_bean" scope="request" class="shoppingcart.ReceiptBean" /> <%-- <jsp:setProperty name="receipt_bean" property="name" param="name" /> <jsp:setProperty name="receipt_bean" property="email" param="email" /> <jsp:setProperty name="receipt_bean" property="street1" param="address" /> <jsp:setProperty name="receipt_bean" property="phone" param="phone" /> --%> <jsp:setProperty name="receipt_bean" property="*" /> </jsp:useBean> <!-- 创建一个表,输出顾客信息,并列出账单。 --> <BR><BR> <TABLE WIDTH=450 CELLSPACING="0" CELLPADDING="0" BORDER="0"> <TR> <TD WIDTH=100%> <B>姓名: <jsp:getProperty name="receipt_bean" property="name"/></B></TD> </TR> <TR> <TD WIDTH=100%> <B>EMail: <jsp:getProperty name="receipt_bean" property="email"/></B></TD> </TR> <TR> <TD WIDTH=100% > <B>地址: <jsp:getProperty name="receipt_bean" property="address"/></B></TD> </TR> <TR> <TD WIDTH=100%> <B>电话: <jsp:getProperty name="receipt_bean" property="phone"/></B></TD> </TR> </TABLE> <!-- 显示出共花去多少钱 --> <% BasketBean basket = (BasketBean)session.getAttribute(BasketBean.BASKET); %> <B> 总共价格人民币<%= basket.getStringifiedValue(basket.getTotal()) %>元 </B> <% basket.clear(); %> <BR> <BR> </CENTER> <BR><BR> <%@ include file="footer.html" %> </body> </html> 代码六:Error.jsp <HTML> <HEAD> <TITLE> 错误处理页面 </TITLE> </HEAD> <%@ page isErrorPage="true" %> <BODY BGCOLOR=Black TEXT="#FFFFFF" LINK="#FFFFFF" VLINK="#FFFFFF"> <H1> 发生了一个错误... </H1> <BR><BR><BR> 是: <BR> <%= exception.toString() %> <BR> </BODY> </HTML> 现实中的应用程序模型 前面我们介绍的这个程序,只是一个简单的应用程序,仅供大家参考。然而,一个真实的应用程序其实和我们介绍的这个简单应用程序一样遵循着 MVC模式 ;让我们来看一看如何修改这个程序的某方面,来创造出一个更有现实意义的电子商务应用程序。我们的电子商店应用程序通过使用 BasketBean 类来实现了它的模型(Model),这存在着一些问题:它没能定义一个标准的接口,这样的缺陷限制了它的可维护性,可扩展性和作为一个应用程序的可伸缩性。 应用程序应该为访问应用程序模型定义一个标准的接口,接口实际上是建立一个约定,允许不同的实现应当按照要求被“插入(plugged-in)”,“可插入”的实现形式可以用桥模式来说明,桥模式的目的就是从功能的任何特定实现形式中分离出抽象的功能。例如,我们的存货清单数据最初是作为静态的信息被内嵌到Java 代码中的(见附录一InventoryBean.java),为了获得程序的灵活性,我们可以把数据从代码中提取出来,并在文件系统上存储它;而随着数据体积的不断膨胀,一个最直接的想法就是把数据转移到一个关系型数据库( RDBMS )中存储,如果我们的 BasketBean 实现一个标准的接口,那么我们就能重新实现这个接口来使用一个文件系统或一个关系型数据库,而不用重写 CustomerServlet类。因此,实际的应用程序要求从代码中分离数据,数据是经常变化的,但是代码应该尽可能少的变化,为能够把我们的应用程序移植到一个商务平台上,最小的要求就是把它分割成的数据存取层和数据管理层。两层结构允许在不影响代码的情况下增加数据。 有时候,数据的可伸缩性和数据交易的要求迫使我们涉及数据管理体系的第三级结构,CORBA 或企业版JavaBean(EJB)提供的数据管理服务接口现在已经被普便使用了,如果我们的BasketBean实现了一个标准的接口,那么我们就能把它作为一种分布式的服务来重新实现它。 宽松组件连接 JSP应用程序能遵从 MVC 模式的原因之一就是这个模式支持清楚地定义model,view和controller组件所扮演的角色,我们应该保持这些部件之间尽可能的宽松的联合。 然而,我们并没有使 CustomerServlet类宽松联合,请看下面的语句 file://取得当前的工作流程的状态。 RequestDispatcher rd = null; String state = request.getParameter( BasketBean.STATE); if (state == null || state.equals(BasketBean.UPDATE)) { // 从商品目录中选择 rd = getServletConfig().getServletContext().getRequestDispatcher( "/jsp/Inventory.jsp"); } 控制器(controller)和表现形式(view)组件之间的紧密连接,使程序中如果改变一个组件就会改变相应其他的组件。在我们的例子中,如果我们想把附加的JSP页面添加到购物车工作流程中,我们也必须把附加的条件添加到 CustomerServlet中,这样怎能谈得上高可维护性和高伸缩性呢?如果我们能去掉CustomerServlet类和它的JSP页面之间的紧密连接,我们的应用程序就会有更多的可维护性和可伸缩性。要使这种紧密连接减小到最少程度的一个方法就是为每一个JSP页面都创造一个助手Bean,我们在 CustomerServlet 中安装这些助手Bean来管理所有的相关联的 JSP 页到来的HTML请求。 象这样封装每一个对象中的请求的模式是一种命令模式(command pattern)。 就象桥模式(bridge pattern)一样,实现一个命令模式的关键是声明每个请求处理器必须实现的通用的接口。在我们的例子中,这个接口的最简单的形式可能是一个单独的方法让我们传递请求参数和BasketBean对象?例如redirect()。因为接口的每一种具体的实现形式支持这个方法, CustomerServlet 就能在任何给定的处理机制上,在不知道实现形式的任何特定信息的情况下调用接口,我们可以为每一个JSP页设定助手Bean,并且根据需要使它复杂化,例如,它能够验证request发来的数据参数,不管是简单的判断输入的是否为空值还是更复杂的任务象验证信用卡信息等等。 一个JSP页面只有一个输入,但是它却有多个输出,这取决于提交按钮的数量,每个输出都能与一个不同的JSP页相联系。例如,Inventory.jsp 有两个输出,一个指向Purchase.jsp,另一个指回自己,我们能使用一个隐藏标签把每个助手Bean和输出联系起来。在 Inventory.jsp 中,我们可以使用 <TD ALIGN=left> <INPUT TYPE=hidden NAME=<%= BasketBean.UPDATE %> VALUE="shoppingcart.UpdateHandler"> <INPUT TYPE=submit NAME=<%= BasketBean.PAGE %> VALUE=<%= BasketBean.UPDATE %>> </TD> 代替下面的行: <TD ALIGN=left> <INPUT TYPE=submit NAME=<%= BasketBean.PAGE %> VALUE=<%= BasketBean.UPDATE %>> </TD> JavaBean“shoppingcart.UpdateHandler”将被我们的CustomerServlet类实例化,它包括一个 CustomerServlet 将调用的重定向方法。UpdateHandler 会知道怎么验证参数,更新模型,并且把请求提交给相应的JSP页,因此这是一条完善CustomerServlet的编程的非常好的途径。 小结 这样一来,JSP就能极大的扩展了 servlet 技术,由于支持 Java 脚本,JSP和Servlet就使网页设计师能够随心所欲的开发出高品质的动态网页程序。但不管怎么说,JSP也是不会代替 servlets的,因为servlets,JSP和JavaBeans在网络体系结构中起着互相补足的作用。通过遵从MVC模式, JSP应用程序能够独立地扩大或提高用于后台控制的servlet,JSP页面和现实的应用程序模型。应用程序模型能被扩展到一个两层或三层的方案,并且增加助手Bean,这都能管理JSP工作流程和支持应用程序组件之间的宽松联接。 附录一、 InventoryBean.java类(下面我将不会对代码进行过多的解释) package shoppingcart; public class InventoryBean { /* 商品名称 */ private static final String[] names = { "牙膏", "肥皂", "毛巾", "口香糖", "牙刷", "练习本", "钢笔"}; /* 商品序列号。 */ private static final int[] skus = { 1, 2, 3, 4, 5, 6, 7 }; /* 商品每件的价格。 */ private static final double[] prices = { 2.50, 3.90, 5.25, 1.00, 3.50, 0.40, 11.80}; private Product[] catalogue_ = null; private static InventoryBean inventory_ = new InventoryBean(); private InventoryBean() { catalogue_ = new Product[skus.length]; for (int i = 0; i < skus.length; i++) { catalogue_[i] = createProduct(skus[i]); } } public static Product[] getCatalogue() { return inventory_.catalogue_; } private Product createProduct(int sku) { return new Product(sku, names[sku-1], prices[sku-1]); } } 附录二、Product类 package shoppingcart; public class Product implements Cloneable { private int sku_ = 0; private String name_ = null; private double price_ = 0; private int Pieces_ = 0; public String toString() { return "商品序列号: " + sku_ + ", 商品名: " + name_ + ", 单价: " + price_ + ", 件数: " + pieces_; } public Product(int sku, String name, double price) { sku_ = sku; name_ = name; price_ = price; } public int getSKU() { return sku_; } public String getName() { return name_; } public double getPrice() { return price_; } public double getPieces() { return pounds_; } public void setPieces(int pieces) { pieces_ = pieces; } public Object clone() { Object dup = null; try { up = super.clone(); } catch(CloneNotSupportedException ignore) { } return dup; } } 附录三、ReceiptBean类 package shoppingcart; import javax.servlet.*; import javax.servlet.http.*; public class ReceiptBean { private String name_ = null; private String email_ = null; private String address_ = null; private String phone_ = null; public ReceiptBean() { } public void setName(String name) { name_ = name; } public String getName() { return name_; } public void setEmail(String email) { email_ = email; } public String getEmail() { return email_; } public void setAddress(String address) { address_ = address; } public String getAddress() { return address_; } public void setPhone(String phone) { phone_ = phone; } public String getPhone() { return phone_; } } |