使用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的應用設計提供了一個全面的解決辦法。它改進了代碼重用,確定了應用的體系,并且便于擴展。一個最大的好處是將表現和邏輯分開,你可以獨立改變它們,而不會影響另一方。
*************源程序下載**************
【責任編輯:方舟 頻道主編:趙家雄】 |