top
Loading...
使用JavaBean高效處理JSP

摘要:JavaServer Pages Model II的一個基本思想就是將表現(HTML)和處理邏輯分開。這篇文章介紹的是一個高效、可重用的設計方法,將動態的內容、處理和驗證由JavaServer Page中分離出來,放到一個相應的JavaBean中。它使用Template Method的設計方法,可提高代碼在整個Web應用中的重用性。此外,它還介紹了在一個HTTP Session中bean間如何通信的簡單技巧。

JavaServer Pages(JSP)技術提供了許多的特性,可讓你簡易和快速地開發Web應用。不過,如果你無計劃和結構地使用這些技術,你的JSP代碼將會是各種HTML標記、JSP標記和Java代碼的混合,很難于看懂、調試和維護。

這里的目標是將全部的JSP處理代碼封裝到JavaBean中。這種方法的好處是HTML編程者和美工可以做表現的開發(使用HTML編輯器),而Java編程者可以集中開發編程邏輯。此外,這種方法可便于你為同一個Web應用提供不同的外觀。

我將介紹的架構使用Template Method方法,可集中實現整個應用的共有設計部分和實現每個JSP的共有處理。就這個例子,共有的處理包括有頁面狀態管理、共有的頁面處理、共有的錯誤處理,以及在頁面間共享信息的技術。所有這些僅是定義一次,你可在頁面設計時再處理特定的細節。

我將以一個簡單的"投票"應用為例,介紹如何使用這個架構。你應有基本的JSP和Java知識,并且最好懂得一些UML知識。

總的靜態結構

這個部分將介紹該架構核心部分的概觀,以及投票應用的例子。圖一顯示了該架構的一個UML框圖:



**************圖一*******************
(UML類框圖)

該架構的中心部分由兩個共有的JSP包含文件和兩個類組成,下面將會談到。它們的作用是完成共有的處理。

includeheader.jsp:這個JSP文件必須被靜態包含在每個JSP文件的開頭。

includefooter.jsp:這個JSP文件必須被靜態包含在每個JSP文件的末尾。

AbstractJSPBean:這是一個抽象類,你應該將它作為所有JSP JavaBean類的一個超類使用。它是該架構的核心類。

SharedSessionBean:在一個HTTP session中,為所有的JSP JavaBean對象提供關聯。

JSP網頁只是用作表現的。每個JSP頁面都應該使用一個相應的JavaBean來處理特定頁面的邏輯。每個JSP頁面都應該靜態包含includeheader.jsp和includefooter.jsp。每個JavaBean都必須擴展AbstractJSPBean,該抽象類包含有模板方法,可完成共有的處理。

這個投票的應用包含有以下的JSP和相應的JavaBean:

login.jsp, LoginJSPBean:投票者認證和登錄

vote.jsp, VoteJSPBean: 執行投票

confirmation.jsp, ConfirmationJSPBean: 顯示確認和投票的結果

我將不會詳細討論數據庫和商業邏輯的部分((Voter, Candidate和VoteDB),不過它們對于這個例子是必需的。

以上我們已經對整體有了一個概觀,接著我們將討論例子中的每個JSP頁面。

JSP例子

每個頁面都必須使用指定的結構,以符合整個架構。

列表1。login.jsp

<%@ page import = "lbm.jsputil.*" %>
<jsp:useBean id="_loginJSPBean" class="lbm.examples.LoginJSPBean"
scope="session"/>
<jsp:setProperty name="_loginJSPBean" property="*"/>
<% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %>
<%@ include file="includeheader.jsp" %>

<html>
<head><title>Vote Login</title></head>
<body bgcolor="white">

<font size=4>
Please enter your Voter ID and Password

</font>

<font size="3" color="Red">
<jsp:getProperty name="_loginJSPBean" property="errorMsg"/>

</font>

<font size=3>
<form method=post>
Voter ID <input type=text name=voterId value=<jsp:getProperty
name="_loginJSPBean" property="voterId"/>>

Password <input type=password name=password value=<jsp:getProperty
name="_loginJSPBean" property="password"/>>


<input type=submit value="Login">
</form>
</font>
</body>
</html>

<%@ include file="includefooter.jsp" %>

該JSP頁面的架構如下:由幾個JSP語句開始。接著的HTML代碼將沒有多少JSP指令、語句和腳本等。除了幾個必要的指令,它們負責由bean中得到動態的內容。最后,頁面使用了一個JSP include指令。

我們討論其中一些重要的JSP語句:

<jsp:useBean id="_loginJSPBean" class="lbm.examples.LoginJSPBean" scope="session"/>
<jsp:setProperty name="_loginJSPBean" property="*"/>

以上的代碼在JSP和相應的bean間建立了一個連接。第二個語句顯式傳送全部的form字段(存儲為HTTP request參數)到bean中匹配的屬性中。代碼中使用了bean的setter方法。

<% AbstractJSPBean _abstractJSPBean = _loginJSPBean; %>
<%@ include file="includeheader.jsp" %>

第一個語句讓includeheader.jsp可執行共有的處理。第二個語句將includeheader.jsp靜態包含進來。要注意到loginJSPBean和_abstractJSPBean現在指向同樣的對象,只是帶有不同的接口。

列表2:includeheader.jsp

<%-- Set the SharedSessionBean --%>
<jsp:useBean id="_sharedSessionBean" class="lbm.jsputil.SharedSessionBean" scope="session"/>
<% _abstractJSPBean.setSharedSessionBean(_sharedSessionBean); %>

<%-- Set implicit Servlet objects --%>
<% _abstractJSPBean.setRequest(request); %>
<% _abstractJSPBean.setResponse(response); %>
<% _abstractJSPBean.setServlet(this); %>

<%-- Perform the processing associated with the JSP --%>
<% _abstractJSPBean.process(); %>

<%-- If getSkipPageOutput equals false, do not output the JSP page --%>
<% if (! _abstractJSPBean.getSkipPageOutput()) { %>


includeheader.jsp是模板的核心元素之一。所有的JSP頁面都使用這個共有的元素。

列表2的前兩個語句令不同頁面但在同一HTTP session中的JSP bean之間互相進行通信。基本上,每個JSP將有兩個與它關聯的JavaBean:一個指定的JSP JavaBean(例如,LoginJSPBean)和共有的SharedSessionBean。SharedSessionBean作為一個共有的元素使用,用來連接所有的頁面;我將在后面繼續討論它。

includeheader.jsp中接著的三個語句與固有的Servlet對象有關。

<% _abstractJSPBean.setRequest(request); %>
<% _abstractJSPBean.setResponse(response); %>
<% _abstractJSPBean.setServlet(this); %>

JSP規范提供訪問Java Servlet規范中的固有對象,例如頁面處理中常用到的request、response和servlet對象。因此它們被傳送到JSP bean。

<% _abstractJSPBean.process(); %>

最后,通過上面的語句來觸發相關JSP頁面的處理。你看到我們調用的是抽象JSP bean上的方法,而不是實類LoginJSPBean上的。為什么?我將在以下的部分解釋。

運用Template Method設計方法

AbstractJSPBean是Template Method設計的主體。每個實際的JSP JavaBean都必須繼承這個類。

列表 3. AbstractJSPBean.java

package lbm.jsputil;

import java.util.*;
import javax.servlet.http.*;
import javax.servlet.*;

public abstract class AbstractJSPBean {

/* constants used for _state */
public static final int NEW = 0;
public static final int FIRSTPASS = 1;
public static final int PROC = 2;
public static final int ERR = -1;

private int _state; // current state
private String _errorMsg; // current message that is being appended during validation
private boolean _skipPageOutput; // should the page output be skipped

private SharedSessionBean _sharedSessionBean; // used for associating the JSP Bean with the HTTP Session

/* standard Servlet objects that need to be setup for each JSP Bean */
protected HttpServletRequest _request;
protected HttpServletResponse _response;
protected Servlet _servlet;

public AbstractJSPBean () {
setState(NEW);
}

protected abstract void beanProcess() throws java.io.IOException;

protected abstract void beanFirstPassProcess() throws java.io.IOException;

protected abstract void beanFooterProcess() throws java.io.IOException;

protected abstract String getJSPCode();

public void process() throws java.io.IOException {
setSkipPageOutput(false); // by default do not skip page output. Specific bean process
// methods can override it.
if (getState() == NEW) {
setState(FIRSTPASS);
beanFirstPassProcess();
} else {
resetErrorMsg();
setState(PROC);
beanProcess();
}

// validation that all common fields have been properly set by the application
// this is actually checking that the code has been written properly
String l_err = "";
if (_sharedSessionBean == null) l_err = l_err + "; SharedSessionBean must be set";
if (_request == null) l_err = l_err + "; Request must be set";
if (_response == null) l_err = l_err + "; Response must be set";
if (_servlet == null) l_err = l_err + "; Servlet must be set";
if ( ! l_err.equals("")) throw new IllegalStateException(l_err);
}

public void footerProcess() throws java.io.IOException {
beanFooterProcess();
}

protected void addErrorMsg (String addErrorMsg) {
if (_errorMsg == null) _errorMsg = addErrorMsg;
else _errorMsg = _errorMsg + " <br>" + addErrorMsg;

setState(ERR);
}
protected void resetErrorMsg () {
_errorMsg = null;
}
public String getErrorMsg () {
if (_errorMsg == null) return "";
else return _errorMsg;
}

protected void setState (int newState) {
_state = newState;
}
public int getState () {
return _state;
}

public void setSharedSessionBean (SharedSessionBean newSharedSessionBean) {
if (_sharedSessionBean == null) {
_sharedSessionBean = newSharedSessionBean;
_sharedSessionBean.putJSPBean(getJSPCode(), this);
} else {
if (_sharedSessionBean != newSharedSessionBean) {
throw new IllegalStateException("SharedSessionBean is not set properly. SharedSessionBean must be the same for all PageBeans within the session");
}
}
}
public SharedSessionBean getSharedSessionBean () {
return _sharedSessionBean;
}

public void setSkipPageOutput (boolean newSipPageOutput) {
_skipPageOutput = newSipPageOutput;
}
public boolean getSkipPageOutput () {
return _skipPageOutput;
}

protected void redirect (String redirectURL) throws java.io.IOException {
// skip the page output since we are redirecting
setSkipPageOutput(true);
_response.sendRedirect(redirectURL);
}

public void setRequest (HttpServletRequest newRequest) {
_request = newRequest;
}
public void setResponse (HttpServletResponse newResponse) {
_response = newResponse;
}
public void setServlet (Servlet newServlet) {
_servlet = newServlet;
}
}

AbstractJSPBean包含有以下的抽象方法:beanFirstPassProcess(), beanProcess(), and beanFooterProcess()。這些方法被稱為primitive方法。你必須在實際的JSP JavaBean子類中實現它們。每個都在JSP處理的一個特定階段中執行。

beanFirstPassProcess()--在頁面被首次調用時進行的處理,它發生在頁面開始輸出之前。它適合用來初始化動態的內容和驗證對頁面的訪問。可參見VoteJSPBean中該方法的實現,該Bean中用它來驗證頁面的訪問,并且進行應用的流程控制。

beanProcess()--發生在第二和后來的頁面調用期間的處理,在頁面輸出開始之前。你可以用它來作HTML form驗證和數據庫更新。在LoginJSPBean類中,該方法被用作HTML form處理,在VoteJSPBean類中,用來保存信息到數據庫中。

beanFooterProcess()--在頁面輸出完成后進行的處理。你可以使用它來令session無效。在ConfirmationJSPBean類中,當投票完成后,通過實現該方法令session無效,并且顯示確認的頁面。

接著我們將看一下process()方法:

public void process() throws java.io.IOException {
setSkipPageOutput(false); // by default do not skip page output. Specific bean process
// methods can override it.
if (getState() == NEW) {
setState(FIRSTPASS);
beanFirstPassProcess();
} else {
resetErrorMsg();
setState(PROC);
beanProcess();
}
....


process()首先檢查JSP的狀態;然后,根據狀態,它調用相應的primitive方法。它還設置了JSP相應的狀態。

process()和footerProcess()方法被稱為template方法。它們由JSP中真正調用(在includeheader.jsp和includefooter.jsp中)。實體的bean不應該覆蓋它們。template方法包含有共有的框架算法。一個典型模板方法的框架算法執行一個共有的處理,并且調用primitive(抽象)方法(beanFirstPassProcess()、beanProcess()和beanFooterProcess()),這些方法的實現在每個實際的JSP JavaBean中都是不同的。框架算法也可以稱為AbstractJSPBean中實現的實體方法。以上的規則是Template Method設計方法的基本點。

這種方法的好處是:

1、通過在模板方法中分解出共有的處理,你可以做到代碼重用

2、你可以對整個應用進行共有的設計和處理

除了處理邏輯外,AbstractJSPBean還包含有以下具體的方法來幫助子類(實際的JSP JavaBean)來實現它們的處理任務,你不應該覆蓋這些實際的方法。

1、與用戶錯誤管理相關的方法(addErrorMsg(), resetErrorMsg(), and getErrorMsg())

2、與頁面狀態管理相關的方法(setState(), getState())

3、管理與SharedSessionBean關聯的方法


4、控制JSP頁面的HTML部分是否輸出的方法(setSkipPageOutput(), getSkipPageOutput())

5、重定向的方法

6、訪問Servlet對象的方法: request, response和servlet

form處理、動態內容和bean通信

列表4展示了一個具體的JSP JavaBean--LoginJSPBean,用來實現特定的頁面處理

列表4。LoginJSPBean


package lbm.examples;

import lbm.jsputil.*;
import java.util.*;

public class LoginJSPBean extends AbstractJSPBean {

public static final String PAGE_CODE = "login";

private String _voterId;
private String _password;
private Voter _voter = null;

public LoginJSPBean() {
}

public void setVoterId (String newVoterId) {
_voterId = newVoterId;
}

public String getVoterId() {
if (_voterId == null) return "";
else return _voterId;
}

public void setPassword (String newPassword) {
_password = newPassword;
}

public String getPassword() {
if (_password == null) return "";
else return _password;
}

public Voter getVoter () {
return _voter;
}

protected void beanProcess () throws java.io.IOException {
if (_voterId == null || _voterId.equals("")) {
addErrorMsg("Voter must be entered");
}

if (_password == null || _password.equals("")) {
addErrorMsg("Password must be entered");
}

if (getState() != ERR) {
file://If all the fields are entered, try to login the voter
Voter voter = VoteDB.login(_voterId, _password);
if (voter == null) {
addErrorMsg("Unable to authenticate the Voter. Please try again.");
}
else {
_voter = voter;

if (_voter.getVotedForCandidate() != null) {
// if the voter has already voted, send the voter to the last page
redirect("confirmation.jsp");
}
else {
// go to the Vote page
redirect("vote.jsp");
}
}
}
}

protected void beanFirstPassProcess() throws java.io.IOException {
}

protected void beanFooterProcess() throws java.io.IOException {
}

protected String getJSPCode() {
return PAGE_CODE;
}
}


觀察LoginJSPBean類中的set和get方法。就象上面提及的,它們用作動態的匹配,并且用來在form字段(request參數)和bean屬性間傳送值。

列表4中的beanProcess()方法,展示了form處理的一些基本點。這個方法發生在頁面輸出前,在第二和全部后來的頁面調用期間執行。這意味著它將僅在用戶按下登錄按鈕并且提交form后執行。

你首先要驗證voteId和password的輸入,產生的錯誤將通過addErrorMsg方法記錄下來。這個方法設置AbstractJSPBean類的errorMsg屬性。該屬性可被JSP用來顯示用戶的錯誤:

<jsp:getProperty name="_loginJSPBean" property="errorMsg"/>

如果數據的輸入成功通過,beanProcess()方法將會調用數據庫來驗證用戶。最后,它通過調用AbstractJSPBean類中實現的redirect()方法,將請求重定向到相應的頁面。

以下我們將討論VoteJSPBean類中的一些方法。它們將可以解釋該架構的一些其它方面,例如JSP JavaBean之間的通信和應用的流程控制。

列表5。VoteJSPBean類中的beanFirstPassProcess()


protected void beanFirstPassProcess() throws java.io.IOException {
// get the Voter from Login page
_voter = null;

LoginJSPBean loginJSPBean =
(LoginJSPBean) getSharedSessionBean().getJSPBean(LoginJSPBean.PAGE_CODE);

if (loginJSPBean != null) {
_voter = loginJSPBean.getVoter();
}

if (_voter == null) {
// voter is not logged in yet. Send it to Login page
setState(NEW);
redirect("login.jsp");
}
}


以上的方法使用了AbstractJSPBean類中_sharedSessionBean對象。SharedSessionBean類通過使用一個簡單的方法,讓所有的JSP JavaBean對象在一個HTTP session中進行通信。它保存有一個session內的全部JSP JavaBean中的一個Map。Map是Java Collections框架的一個接口,它是Java 1.2推出的。對熟悉Java 1.1的人來說,它與Hashtable非常類似。一個JSP JavaBean的主鍵是它的PAGE_CODE,它作為一個常數存儲在每個JSP JavaBean類中。

在這個例子中,beanFirstPassProcess()方法首先定位到LoginJSPBean對象。接著,它由LoginJSPBean對象中得到Voter對象,并且存儲一個到它的引用,以便以后使用。如果Voter為null,這意味著用戶沒有首先登錄就進入Voter頁面,因此它重定向到登錄頁面。這是一個應用流程控制的簡單例子。你可以設計更復雜的方法,例如使用一個智能的調度程序,不過這些討論已經超出了本文的范圍。

列表6。VoteJSPBean類的getCandidateList()方法


public String getCandidateList () {
StringBuffer candidateList = new StringBuffer();
Candidate candidate;

Iterator candidates = VoteDB.getCandidates();
while (candidates.hasNext()) {
candidate = (Candidate) candidates.next();

candidateList.append("<input type=radio name="candidateName" value="");
candidateList.append(candidate.getName());
candidateList.append(""> ");
candidateList.append(candidate.getName());
candidateList.append("<br>");
}

return candidateList.toString();
}


以上的getCandidateList()方法被vote.jsp調用,通過以下的方法:

<jsp:getProperty name="_voteJSPBean" property="candidateList"/>

根據由數據庫得到的內容不同,該方法提供不同的動態HTML內容輸出。它需要開發JavaBean的Java編程者懂得一些HTML知識。

你也可以使用一個利用HTML的獨立庫來格式化HTML,它可以接受一個預定義的輸入。例如Iterator,然后以預定義的格式產生HTML輸出。另一個方法是使用標簽庫。

最后的要點:框架

通過將表現和邏輯分離開來,該架構可讓你獨立地修改表現(JSP)和邏輯(bean)。這意味著你可以修改bean中的邏輯而無需改動JSP,只要你保持bean的屬性不變就行了。相反也是成立的,你可以將JSP代碼交給HTML開發者和美工來改變站點的外觀,而不會影響其中的Java代碼。

你可以稍微修改該框架的核心元素來滿足你應用的特別需要。你可以加入新的或者修改現有的方法,或者修改模板的方法。重要的一點是,在你的整個應用中,全部的JSP和JSP JavaBean都應該使用這個架構。

在剛開始時,這個架構看來比較復雜,特別是對于一個只有三頁的例子應用來說。不過,如果你開始寫你的應用,你將會發現當應用變復雜時,代碼量的增長卻沒有預期的大。

這個架構并沒有談到Web應用中通常用到的多層設計。它主要集中在JSP的表現層。要建立真正的三層或者多層的系統,JSP JavaBeamn將需要調用Enterprise JavaBean或者一些其它的商業邏輯實現。

例子還介紹了讓應用跟蹤HTTP session的架構。不過,如果你不想依賴session的話,該架構仍然可以工作得很好。應用的頁面和流程應該是分別設計的。你可能將不需要SharedSessionBean。你的頁面只是用作表現,而獨立的頁面將僅用作處理和驗證,而不會有任何的HTML輸出。為此,你將主要使用beanFirstPassProcess() 方法。

最后的要點:例子

我使用Tomcat3.1來測試這個例子,Tomcat3.1符合JSP1.1和Servlet2.2規范。這篇文章將不會討論如何在Tomcat中配置JSP應用的詳細情況。

在測試該例子時,你可以在VoteDB類的源代碼中得到Voter ID,以便可以進行登錄測試(密碼和ID是一樣的)。

你必須在瀏覽器中允許cookies,否則不能成功運行例子。如果你需要在禁止cookies時仍然可以運行這個應用,你必須重新寫URL(使用the javax.servlet.http.HttpServletResponse類的encodeURL()方法)。你需要重寫全部應用中的URL,包括你的JSP中的鏈接,form標記中的action,以及在JSP JavaBean中用來重定向URL的HTTP請求。

結論

這篇文章介紹的架構為JSP的應用設計提供了一個全面的解決辦法。它改進了代碼重用,確定了應用的體系,并且便于擴展。一個最大的好處是將表現和邏輯分開,你可以獨立改變它們,而不會影響另一方。

*************源程序下載**************




【責任編輯:方舟 頻道主編:趙家雄

 
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗