電子商務模型的JSP、JavaBean實現
引言
現在,開發和管理一個電子商務系統需要高效率的開發和利用網絡資源,特別是如果你想讓你的顧客在網上購買你的產品或是取得你提供的服務的話,更要注意這一點。構建一個這樣的商務網站來實現你商業上的目的并不是一件非常簡單的工作,在開發電子商務網站的時候,我們就要充分的利用搞技術含量的技術,我們可以利用到最先進的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來構建一個簡單網絡應用程序是可行,但是一個有效的應用程序是這三種技術共同作用的結果。
圖2解釋了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_;
}
}