使用JAVABEAN高效处理JSP
类别: 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的应用设计提供了一个全面的解决办法。它改进了代码重用,确定了应用的体系,并且便于扩展。一个最大的好处是将表现和逻辑分开,你可以独立改变它们,而不会影响另一方。
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的应用设计提供了一个全面的解决办法。它改进了代码重用,确定了应用的体系,并且便于扩展。一个最大的好处是将表现和逻辑分开,你可以独立改变它们,而不会影响另一方。
- 上一篇: 在JSP中作HTTP认证
- 下一篇: 用标记库国际化JSP(2)
-= 资 源 教 程 =-
文 章 搜 索