作者:Andrei Cioroianu
轉(zhuǎn)自:Oracle Technology Network http://www.oracle.com/technology/global/cn/pub/articles/andrei_reuse.html
了解如何利用 JSP 標(biāo)記文件、JSF 和 Oracle ADF Faces 重用 Web 內(nèi)容和 Java 代碼。
2005 年 10 月發(fā)表
代碼重用是提高開(kāi)發(fā)人員生產(chǎn)效率和應(yīng)用程序可維護(hù)性的一種非常好的方式。您應(yīng)當(dāng)總是尋找設(shè)計(jì)良好的框架和可自定義的組件,而不是從頭重來(lái)。應(yīng)用程序特有的代碼也可以在模塊甚至相關(guān)項(xiàng)目間重用。后一種可重用性可使您快速修改,整體利用新特性,并減少測(cè)試和調(diào)試的時(shí)間。
雖然這些聽(tīng)起來(lái)像是針對(duì)程序員的不錯(cuò)建議,但 Web 開(kāi)發(fā)人員也應(yīng)當(dāng)注意這些事情。許多 Web 開(kāi)發(fā)人員已經(jīng)在使用諸如 Java Server Faces (JSF)、Oracle ADF Faces 和 Apache MyFaces 之類的框架,這些框架提供了許多內(nèi)置組件并支持創(chuàng)建其他可重用組件。然而,很多時(shí)候是將許多 HTML 和 JSP 標(biāo)記從一個(gè) Web 頁(yè)面復(fù)制粘貼到其他頁(yè)面中,這意味著當(dāng) Web 內(nèi)容改變時(shí)將不得不修改這些頁(yè)面中的重復(fù)標(biāo)記。此外,如果沒(méi)有更新某些頁(yè)面,那么應(yīng)用程序的外觀將會(huì)不一致。如果跨頁(yè)面重用 UI 組件就不會(huì)發(fā)生這種情況了,這是因?yàn)榘l(fā)生變化時(shí)只需在一個(gè)地方進(jìn)行編輯就可以了。
在本文中,我將提供一些在基于 JSF 和 ADF Faces 的 Web 應(yīng)用程序中重用 UI 組件的最佳實(shí)踐。您將了解到如何創(chuàng)建定義 Web 頁(yè)面布局的模板,以及如何重用表單、菜單和按鈕欄。您還將了解到如何轉(zhuǎn)換現(xiàn)有的 JSP 頁(yè)面以使它們更易于維護(hù),以及如何將由 JSF 和 Oracle ADF Faces 提供的組件與 JSTL 和現(xiàn)代 JSP 特性(例如標(biāo)記文件)一起使用。
Java Web 可重用特性
自從第一個(gè)版本起,JSP 就已經(jīng)提供了一些鼓勵(lì)可重用的基本機(jī)制,例如 JavaBeans 支持、基于 Servlets API RequestDispatcher 的 <%@include%> 指令和 <jsp:include> 標(biāo)記。JSTL 增加了 <c:import> 標(biāo)記,它使您能夠包含某個(gè)資源的內(nèi)容,該資源可以位于同一個(gè)應(yīng)用程序、服務(wù)器中,甚至也可以在遠(yuǎn)程服務(wù)器上。Struts Tiles 圍繞著這種內(nèi)容包含特性構(gòu)建了一個(gè)完整的框架。JSF 也支持這一特性,允許您構(gòu)建使用 <f:subview> 標(biāo)記的子表單。JSP 2.0 增加了一個(gè)稱為“隱式包含”的新特性。這些特性使用 <include-prelude> 和 <include-coda> 在 web.xml 文件中聲明。正如您所能看到的,雖然頁(yè)面/片斷包含種類各異,但每一種都有其自己的用途和上下文。
對(duì)自定義標(biāo)記的支持從 JSP 1.1 就有了,它為構(gòu)建標(biāo)記庫(kù)提供了一個(gè) API。JSP 1.2 對(duì)該 API 進(jìn)行了增強(qiáng),但很多人認(rèn)為它太復(fù)雜了。因此,JSP 2.0 定義了一個(gè)具有相同功能的全新 API。這個(gè)為標(biāo)記庫(kù)提供的新 API 稱為簡(jiǎn)單標(biāo)記 API,舊 API 現(xiàn)在稱為標(biāo)準(zhǔn)標(biāo)記 API。許多 Web 框架(如 Struts、JSF 和 JSTL)仍使用標(biāo)準(zhǔn)標(biāo)記 API,以便可以與 JSP 1.2 以及 JSP 2.0 一起使用。簡(jiǎn)單標(biāo)記 API 是另一種 JSP 2.0 特性 — 標(biāo)記文件 — 的基礎(chǔ),該特性使您能夠使用 JSP 語(yǔ)法構(gòu)建標(biāo)記庫(kù)。除了簡(jiǎn)單標(biāo)記和標(biāo)記文件之外,JSP 2.0 規(guī)范還定義了 EL 函數(shù),后者使您能夠使用 EL 語(yǔ)法從 JSP 頁(yè)面中調(diào)用靜態(tài) Java 方法。
JSF 標(biāo)準(zhǔn)將組件定義為它的可重用單元。這些組件比自定義標(biāo)記更強(qiáng)大,但也更難設(shè)計(jì)和實(shí)施。因?yàn)橛袔讉€(gè)公司和開(kāi)放源代碼機(jī)構(gòu)正在制作可供使用的 JSF 組件庫(kù),所以您可能不需要構(gòu)建自己的 JSF 組件。本文的示例使用了 Oracle ADF Faces,它是基于 JSF 標(biāo)準(zhǔn)的最先進(jìn)的框架。
創(chuàng)建頁(yè)面模板。典型 Web 應(yīng)用程序的所有頁(yè)面共享一個(gè)公共布局,該布局可以定義在一個(gè)地方,如 JSP 標(biāo)記文件中。該模板可以生成標(biāo)題和正文標(biāo)記、應(yīng)用程序的菜單以及在所有頁(yè)面中出現(xiàn)的其他部分。此外,它可以包含用于加載資源綁定、設(shè)置 JSP 變量等的設(shè)置標(biāo)記。在應(yīng)用程序的每個(gè) Web 頁(yè)面中重復(fù)該標(biāo)記是沒(méi)有意義的。在這一部分中,您將了解如何使用 Oracle JDeveloper 10g (10.1.3)(撰寫(xiě)此文時(shí)為早期試用版)基于 JSF 和 Oracle ADF Faces 構(gòu)建自定義模板。
JDeveloper 提供了一個(gè)創(chuàng)建 JSF 頁(yè)面模板的向?qū)А?File 菜單中選擇 New 項(xiàng),打開(kāi) New Gallery 窗口。然后,轉(zhuǎn)至 Web Tier 中的 JSF 類別,在右側(cè)面板中選擇 JSF JSP Template 并單擊 OK:
單擊 Next 跳過(guò) Welcome 頁(yè)面,然后選擇您使用的 J2EE 版本,并再次單擊 Next:
為模板提供一個(gè)文件名:
選擇組件綁定樣式:
指定是否要使用錯(cuò)誤頁(yè)面:
選擇要使用的標(biāo)記庫(kù):
提供頁(yè)面標(biāo)題和其他頁(yè)面屬性:
單擊 Finish。JDeveloper 將創(chuàng)建該模板并在可視化編輯器中將其打開(kāi)。您可以使用 Component Palette 將 JSF 和 Oracle ADF Faces 組件添加到該模板中。然后,您可以在 New Gallery 窗口中從 Template 中選擇 JSF JSP,基于您剛創(chuàng)建的模板創(chuàng)建 JSF 頁(yè)面。這是從模板構(gòu)建頁(yè)面的一種非常簡(jiǎn)單的方法。另一種方法是將該共用的 JSF 標(biāo)記移到一個(gè)可重用的標(biāo)記文件中。以下段落使用了第二種方法。
創(chuàng)建標(biāo)記文件。從 File 菜單中選擇 New 項(xiàng),打開(kāi) New Gallery 窗口。然后,轉(zhuǎn)至 Web Tier 中的 JSP 類別,在右側(cè)面板中選擇 JSP Tag File 并單擊 OK:
JDeveloper 將打開(kāi)一個(gè)創(chuàng)建 JSP 標(biāo)記文件的向?qū)Т翱凇螕?Next 跳過(guò) Welcome 頁(yè)面,在 File Name 域中輸入 pageTemplate.tag 并單擊 Next:
現(xiàn)在您就可以定義模板標(biāo)記的屬性了。假定您正在構(gòu)建一個(gè)基于 Web 的向?qū)ВM總€(gè)頁(yè)面都有一個(gè)步驟 ID 和一個(gè)標(biāo)題。標(biāo)記文件需要該信息來(lái)為每個(gè)頁(yè)面自定義模板標(biāo)記。單擊 Add 按鈕,輸入 step 屬性名,并將 Required 設(shè)為 true。對(duì)另一個(gè)名稱為 title 的屬性執(zhí)行同樣的操作:
單擊 Next 和 Finish。JDeveloper 將在 WEB-INF 目錄的 tags 子目錄下創(chuàng)建 pageTemplate.tag 文件。用 <%@attribute%> 指令定義這兩個(gè)標(biāo)記屬性:
<%@ attribute name="step" required="true" %>
<%@ attribute name="title" required="true" %>
在創(chuàng)建標(biāo)記文件之后,JDeveloper 將打開(kāi)它進(jìn)行編輯。以下段落介紹了本文通篇用到的示例應(yīng)用程序的模板代碼。
在標(biāo)記文件中使用 JSF 和 Oracle ADF Faces。無(wú)論您是否想在普通頁(yè)面的標(biāo)記文件內(nèi)使用這些框架,您都必須用 <%@taglib%> 指令來(lái)對(duì)其進(jìn)行聲明。在 Component Palette 中選擇 JSP,然后單擊 Taglib。您需要在一個(gè)對(duì)話框中輸入要使用的標(biāo)記庫(kù)的 URI 和前綴:
您還可以使用 Component Palette 來(lái)將任何標(biāo)記拖放到 JSP 頁(yè)面上,如果它的 <%@taglib%> 指令不在頁(yè)面中,那么 JDeveloper 將自動(dòng)添加它。pageTemplate.tag 示例使用了四個(gè)標(biāo)記庫(kù):JSTL Core、JSF Core、Oracle ADF Faces Core 和 Oracle ADF Faces HTML:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="af"
uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="afh"
uri="http://xmlns.oracle.com/adf/faces/html" %>
在同時(shí)使用 JSF 和 JSTL 時(shí),或者在標(biāo)記文件中使用 JSF 時(shí)有一件非常重要的事情您必須始終牢記:JSF 框架不支持頁(yè)面范圍,后者是 JSTL 標(biāo)記的默認(rèn) JSP 范圍。因此,您應(yīng)當(dāng)顯式指定與 JSTL、JSF 和 Oracle ADF Faces 的標(biāo)記結(jié)合使用的 JSP 變量的請(qǐng)求范圍。此外,標(biāo)記文件的所有屬性都可以通過(guò)保存在頁(yè)面范圍內(nèi)的 JSP 變量來(lái)訪問(wèn)。您必須復(fù)制請(qǐng)求范圍內(nèi)的屬性,以便它們可以和 JSF 和 Oracle ADF Faces 標(biāo)記一起使用:
<c:set var="pageTitle" scope="request" value="${pageScope.title}"/>
您還可以將屬性的值存儲(chǔ)在由 JSF 管理的 Bean 內(nèi)。假定您有一個(gè)名稱為 WizardBean 的類,該類在 faces-config.xml 中被配置為受管 Bean:
<faces-config>
...
<managed-bean>
<managed-bean-name>wizard</managed-bean-name>
<managed-bean-class>webreuse.WizardBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
...
</faces-config>
示例 Bean 有一個(gè) currentStep 屬性,您可以在該屬性中存儲(chǔ) step 屬性的值。該操作可以利用 JSTL 的 <c:set> 標(biāo)記來(lái)完成,但您必須確保該 Bean 存在于會(huì)話范圍中(如 faces-config.xml 文件中所聲明的那樣)。只有在 JSF EL 表達(dá)式中首次引用受管 Bean 實(shí)例時(shí),JSF 才會(huì)創(chuàng)建該實(shí)例。如果在訪問(wèn)受管 Bean 的 JSF 或 Oracle ADF Faces 標(biāo)記之前執(zhí)行 JSTL 標(biāo)記,那么,您應(yīng)當(dāng)使用 <jsp:useBean>以確保該 Bean 實(shí)例存在于會(huì)話范圍中。如此,您就可以安全地使用 <c:set> 標(biāo)記了:
<jsp:useBean class="webreuse.WizardBean"
id="wizard" scope="session"/>
<c:set target="${wizard}" property="currentStep"
value="${pageScope.step}"/>
以上代碼可以手動(dòng)輸入,或者可以使用 JDeveloper 的 Component Palette。選擇 JSP,然后單擊 UseBean,添加 <jsp:useBean> 標(biāo)記。JDeveloper 將打開(kāi)一個(gè)對(duì)話框,您必須在其中提供該標(biāo)記的屬性:
最后,您可以在 pageTemplate.tag 文件中添加模板代碼:
<f:view>
<afh:html>
<afh:head title="#{pageTitle}"/>
<afh:body>
<af:panelPage title="#{pageTitle}">
<jsp:doBody/>
</af:panelPage>
</afh:body>
</afh:html>
</f:view>
所有的 JSF 和 Oracle ADF Faces 組件都必須置于 <f:view> 內(nèi)部。<afh:html>、<afh:head> 和 <afh:body> 組件將生成具有相同名稱的 HTML 標(biāo)記。<af:panelPage> 組件顯示頁(yè)面標(biāo)題。然后,<jsp:doBody> 將調(diào)用您放在使用模板標(biāo)記的 JSP 頁(yè)面中的 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內(nèi)容。這種 JSP 頁(yè)面將類似于:
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<tags:pageTemplate step="..." title="...">
... JSP content executed by doBody ...
</tags:pageTemplate>
當(dāng)執(zhí)行該頁(yè)面時(shí),標(biāo)記文件將生成 <html>、<head> 和 <body> 標(biāo)記以及頁(yè)面標(biāo)題和其他標(biāo)題標(biāo)記。然后,模板標(biāo)記文件將調(diào)用包在 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內(nèi)容。在此之后,標(biāo)記文件可能會(huì)生成一個(gè)頁(yè)腳,并用 </body> 和 </html> 來(lái)完成頁(yè)面。下一部分將在基于 Web 的向?qū)У捻?yè)面中使用模板標(biāo)記。
重用表單、菜單和其他 UI 組件
利用 JSF 和 Oracle ADF Faces 組件構(gòu)建的 UI 面板可以使用 "subviews" 或 JSP 標(biāo)記文件在多個(gè)頁(yè)面中重用。前一種解決方案與舊的 JSP 1.2 版本兼容,但標(biāo)記文件更靈活。這一部分將介紹如何基于 JSF 和 Oracle ADF Faces 將一個(gè) Web 表單分成多個(gè)頁(yè)面和標(biāo)記文件。當(dāng)用戶第一次訪問(wèn)應(yīng)用程序時(shí),他將使用包含后退和前進(jìn)按鈕的向?qū)浇缑鎭?lái)逐步瀏覽這些頁(yè)面。在此以后,他可能想更新最初提供的信息。在這種情況下,用戶需要使用基于菜單的界面直接訪問(wèn)應(yīng)用程序的頁(yè)面。
可以在上述的兩種情況下使用同一種 Web 表單。此外,所有的表單可以組合在一個(gè)確認(rèn)頁(yè)面中,該頁(yè)面可以讓用戶以只讀模式查看信息。當(dāng)用戶必須修改其中一個(gè)表單時(shí),可以編輯單個(gè)文件。每一個(gè)修改都將顯示在向?qū)эL(fēng)格的界面(用于從用戶那獲取信息)、確認(rèn)頁(yè)面(用于查看信息)和基于菜單的界面(由用戶用于更新信息)中。如果不重用表單,您將必須在三個(gè)不同的地方進(jìn)行相同的修改。
開(kāi)發(fā) Backing Bean。您在前一部分中已經(jīng)發(fā)現(xiàn),示例應(yīng)用程序使用了一個(gè)名稱為 WizardBean 的 Backing Bean。pageTemplate.tag 文件設(shè)置了 currentStep 屬性,該屬性保存用戶在瀏覽器中看到的當(dāng)前表單的步驟 ID。示例應(yīng)用程序在確認(rèn)頁(yè)面(第四步)之前使用了三個(gè)表單,確認(rèn)頁(yè)面的 ID 由 MAX_STEP 常量來(lái)定義。將該常量公開(kāi)為名為 maxStep 的 bean 屬性,以便可以在 Web 頁(yè)面中使用 JSF EL 來(lái)訪問(wèn)它:
package webreuse;
public class WizardBean implements java.io.Serializable {
public final static int MAX_STEP = 4;
private int currentStep;
private String connName, connType;
private String userName, password, role;
private String driver, hostName, sid;
private int jdbcPort;
public int getMaxStep() {
return MAX_STEP;
}
public int getCurrentStep() {
return currentStep;
}
public void setCurrentStep(int currentStep) {
this.currentStep = currentStep;
}
...
}
除 currentStep 和 maxStep 之外,WizardBean 類還有幾個(gè)其他的屬性可用于保存用戶提供的向?qū)?shù):connName、connType、userName、password、role、driver、hostName、sid 和 jdbcPort。該示例應(yīng)用程序與用戶數(shù)據(jù)無(wú)關(guān),但在實(shí)際情況中,向?qū)⒂盟鼇?lái)配置數(shù)據(jù)庫(kù)連接。WizardBean 還實(shí)施了幾個(gè)在用戶單擊向?qū)У陌粹o時(shí)將執(zhí)行 JSF 操作。這些方法稍后將在本部分中進(jìn)行介紹。
要?jiǎng)?chuàng)建您自己的 Bean,您可以在 File 菜單中選擇 New 來(lái)打開(kāi) New Gallery 窗口。然后,轉(zhuǎn)至 General 中的 Simple Files 類別,在右側(cè)面板中選擇 Java Class,并單擊 OK:
提供類名和程序包名稱。然后單擊 OK,創(chuàng)建該類:
在聲明一個(gè)字段之后(例如 private int currentStep),右鍵單擊其名稱并選擇 Generate Accessors。單擊 OK,生成 get 和 set 方法:
創(chuàng)建可重用表單。將向?qū)У闹鞅韱尉帉?xiě)為可重用標(biāo)記文件。第一個(gè)表單 (form1.tag) 包含一個(gè)文本域和一個(gè)下拉列表:
<!-- form1.tag -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:inputText id="connName" required="true" columns="40"
label="Connection Name:" value="#{wizard.connName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
<af:selectOneChoice id="connType" required="true"
label="Connection Type:" value="#{wizard.connType}"
readOnly="#{wizard.currentStep == wizard.maxStep}">
<af:selectItem label="Oracle (JDBC)" value="Oracle_JDBC"/>
</af:selectOneChoice>
第二個(gè)表單 (form2.tag) 包含三個(gè)文本域:
<!-- form2.tag -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:inputText id="userName" required="true" columns="40"
label="User Name:" value="#{wizard.userName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
<af:inputText id="password" required="true" columns="40"
label="Password:" value="#{wizard.password}" secret="true"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
<af:inputText id="role" required="false" columns="40"
label="Role:" value="#{wizard.role}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
第三個(gè)表單 (form3.tag) 與其他兩個(gè)表單類似:
<!-- form3.tag -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:selectOneChoice id="driver" required="true"
label="Driver:" value="#{wizard.driver}"
readOnly="#{wizard.currentStep == wizard.maxStep}">
<af:selectItem label="thin" value="thin"/>
<af:selectItem label="oci8" value="oci8"/>
</af:selectOneChoice>
<af:inputText id="hostName" required="true" columns="40"
label="Host Name:" value="#{wizard.hostName}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
<af:inputText id="sid" required="true" columns="40"
label="SID:" value="#{wizard.sid}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
<af:inputText id="jdbcPort" required="true" columns="40"
label="JDBC Port:" value="#{wizard.jdbcPort}"
readOnly="#{wizard.currentStep == wizard.maxStep}"/>
正如您可能已經(jīng)注意到的那樣,三個(gè)標(biāo)記文件中沒(méi)有一個(gè)包含了用于排列 Oracle ADF Faces 組件的標(biāo)記。不含任何布局標(biāo)記使得您可以獨(dú)立地使用表單標(biāo)記,或在確認(rèn)頁(yè)面中組合它們。Oracle ADF Faces 提供了一個(gè)名稱為 <af:panelForm> 的強(qiáng)大組件,它將自動(dòng)執(zhí)行布局。除了主要的組件之外,表單通常包含有其他的標(biāo)記,例如 <h:messages globalOnly="true"/> 和 <af:objectLegend name="required"/>。所有這些標(biāo)記都可以集中在一個(gè)名為 formTemplate.tag 的標(biāo)記文件中:
<!-- formTemplate.tag -->
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<h:panelGrid columns="1" border="0" cellspacing="5">
<h:messages globalOnly="true"/>
<af:objectLegend name="required"/>
<af:panelForm>
<jsp:doBody/>
</af:panelForm>
</h:panelGrid>
使用 pageTemplate.tag 和 formTemplate.tag 的 JSF 頁(yè)面將類似于:
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<tags:pageTemplate step="..." title="...">
<af:form id="...">
...
<tags:formTemplate>
<tags:form123/>
</tags:formTemplate>
...
</af:form>
</tags:pageTemplate>
在前面的代碼段中,<tags:form123/> 代表向?qū)У娜齻€(gè)主表單中的任意一個(gè)(<tags:form1/>、<tags:form2/> 或 <tags:form3/>)。除了這些表單的組件之外,<af:form> 可能包含其他的 JSF 和 Oracle ADF Faces 組件(例如按鈕)。下一段介紹了使用 <tags:pageTemplate>、<tags:formTemplate>、<tags:form1>、<tags:form2> 和 <tags:form3> 的應(yīng)用程序頁(yè)面。這些具體的例子充分說(shuō)明了利用可重用標(biāo)記文件構(gòu)建 JSF 用戶界面的實(shí)際好處。
向?qū)эL(fēng)格的界面。基于 Web 的向?qū)У捻?yè)面將包含標(biāo)記為 Back、Next 和 Finish 的按鈕。可以在一個(gè)名為 stepButtons.tag 的標(biāo)記文件中定義這些按鈕:
<!-- stepButtons.tag -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:panelButtonBar>
<af:singleStepButtonBar
selectedStep="#{wizard.currentStep}"
maxStep="#{wizard.maxStep}"
previousAction="#{wizard.previousAction}"
nextAction="#{wizard.nextAction}"/>
<c:if test="${wizard.currentStep == wizard.maxStep}">
<af:commandButton text="Finish"
action="#{wizard.finishAction}"/>
</c:if>
</af:panelButtonBar>
WizardBean 類包含當(dāng)用戶單擊按鈕時(shí)將執(zhí)行的操作方法:
package webreuse;
public class WizardBean implements java.io.Serializable {
...
public String previousAction() {
if (currentStep <= 1)
return null;
else {
currentStep--;
return "step" + currentStep;
}
}
public String nextAction() {
if (currentStep >= getMaxStep())
return null;
else {
currentStep++;
return "step" + currentStep;
}
}
public String finishAction() {
currentStep = 0;
return "finished";
}
...
}
操作方法返回的結(jié)果將在 faces-config.xml 文件的導(dǎo)航規(guī)則中使用:
<faces-config>
...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>step1</from-outcome>
<to-view-id>/step1.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>step4</from-outcome>
<to-view-id>/confirm.jsp</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>finished</from-outcome>
<to-view-id>/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
</faces-config>
除了所有可見(jiàn)的組件之外,向?qū)ы?yè)面還將包含一個(gè)與 WizardBean 的 currentStep 屬性綁定的隱藏字段。您已經(jīng)看到了 pageTemplate.tag 將在每一次執(zhí)行頁(yè)面時(shí)設(shè)置該屬性。然而,用戶可能單擊瀏覽器的后退按鈕。作為該操作的結(jié)果,在瀏覽器中看到的當(dāng)前步驟將與 currentStep 屬性的值不符,因?yàn)闉g覽器將從其緩存中檢索到頁(yè)面,而不是請(qǐng)求執(zhí)行 JSF 頁(yè)面。
如果每一次用戶單擊按鈕時(shí) currentStep 值都與表單數(shù)據(jù)一起提交,則不會(huì)導(dǎo)致任何問(wèn)題。hiddenData.tag 文件將 currentStep 作為一個(gè)隱藏字段包含在內(nèi)。此外,如果 currentStep 等于 maxStep(這意味著標(biāo)記在確認(rèn)頁(yè)面中使用),那么該標(biāo)記文件將為所有 Bean 屬性生成隱藏字段:
<!-- hiddenData.tag -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<h:inputHidden id="currentStep" value="#{wizard.currentStep}"/>
<c:if test="${wizard.currentStep == wizard.maxStep}">
<h:inputHidden id="h_connName" value="#{wizard.connName}"/>
<h:inputHidden id="h_connType" value="#{wizard.connType}"/>
<h:inputHidden id="h_userName" value="#{wizard.userName}"/>
<h:inputHidden id="h_password" value="#{wizard.password}"/>
<h:inputHidden id="h_role" value="#{wizard.role}"/>
<h:inputHidden id="h_driver" value="#{wizard.driver}"/>
<h:inputHidden id="h_hostName" value="#{wizard.hostName}"/>
<h:inputHidden id="h_sid" value="#{wizard.sid}"/>
<h:inputHidden id="h_jdbcPort" value="#{wizard.jdbcPort}"/>
</c:if>
所有的向?qū)Р糠侄伎梢栽?JSF 頁(yè)面中組裝,如 step1.jsp:
<!-- step1.jsp -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<tags:pageTemplate step="1"
title="Create Database Connection - Step 1">
<af:form id="form1">
<tags:formTemplate>
<tags:form1/>
</tags:formTemplate>
<tags:stepButtons/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
step2.jsp 和 step3.jsp 頁(yè)面與 step1.jsp 非常類似。作為練習(xí),您可以嘗試為這些頁(yè)面構(gòu)建一個(gè)模板,從而將這些頁(yè)面都減少為四行代碼。confirm.jsp 頁(yè)面將一起顯示所有三個(gè)表單,但組件在只讀模式下工作,從而占用的屏幕空間更少并且無(wú)需用戶交互:
<!-- confirm.jsp -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<tags:pageTemplate step="4" title="Confirm Connection Parameters">
<af:form id="form4">
<tags:formTemplate>
<tags:form1/>
<tags:form2/>
<tags:form3/>
</tags:formTemplate>
<tags:stepButtons/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
基于菜單的界面。假定用戶逐步瀏覽向?qū)У捻?yè)面,提供所有需要的信息。如果用戶需要在以后修改某些地方,那么他應(yīng)當(dāng)不需要再次瀏覽所有的向?qū)ы?yè)面。相反,用戶界面將讓用戶直接轉(zhuǎn)至必須修改信息的表單。menuTabs.tag 文件使用相同名稱的 Oracle ADF Faces 組件來(lái)構(gòu)建菜單:
<!-- menuTabs.tag -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:menuTabs>
<af:commandMenuItem text="Name and Type" action="tab1"
selected="#{wizard.currentStep == 1}"/>
<af:commandMenuItem text="Authentication" action="tab2"
selected="#{wizard.currentStep == 2}"/>
<af:commandMenuItem text="Connection" action="tab3"
selected="#{wizard.currentStep == 3}"/>
</af:menuTabs>
菜單的導(dǎo)航規(guī)則在 faces-config.xml 中定義:
<faces-config>
...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>tab1</from-outcome>
<to-view-id>/tab1.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>tab3</from-outcome>
<to-view-id>/tab3.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
</faces-config>
當(dāng)用戶單擊菜單的標(biāo)簽時(shí),表單數(shù)據(jù)將被提交給 Web 服務(wù)器,在該服務(wù)器上 JSF 框架將更新 Backing Bean。如果用戶想更新表單而不改變當(dāng)前的標(biāo)簽,那么需要使用提交按鈕。submitButton.tag 文件提供了提交按鈕:
<!-- submitButton.tag -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<af:commandButton id="command" text="Submit" action="submitAction"/>
tab1.jsp、tab2.jsp 和 tab3.jsp 文件將把菜單附加到向?qū)У谋韱紊稀O旅媸?tab1.jsp:
<!-- tab1.jsp -->
<%@ taglib prefix="af" uri="http://xmlns.oracle.com/adf/faces" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<tags:pageTemplate step="1" title="Edit Database Connection">
<af:form id="form1">
<tags:menuTabs/>
<tags:formTemplate>
<tags:form1/>
</tags:formTemplate>
<tags:submitButton/>
<tags:hiddenData/>
</af:form>
</tags:pageTemplate>
使顯示邏輯可重用
如果您必須使用 JSF 和 Oracle ADF Faces 從頭開(kāi)始構(gòu)建新的頁(yè)面,那么一切都沒(méi)什么問(wèn)題。但是,對(duì)于包含了以 Java 代碼形式存在的顯示邏輯的舊 JSP 頁(yè)面,該如何處理呢?維護(hù)這些頁(yè)面困難重重,并且,若不將顯示代碼從 JSP 頁(yè)面中分離出來(lái),則無(wú)法對(duì)其進(jìn)行重用。此外,Java 代碼和 HTML 標(biāo)記不應(yīng)混合在同一個(gè)頁(yè)面中,因?yàn)檫@將使 Java 開(kāi)發(fā)人員和 Web 設(shè)計(jì)人員無(wú)法輕松地展開(kāi)并行工作。
JSF 和 Oracle ADF Faces 組件解決了多數(shù)情況下的此種問(wèn)題,因?yàn)?Web 頁(yè)面將使用這兩個(gè)框架提供的標(biāo)記來(lái)構(gòu)建,同時(shí)將 Java 代碼放到了 Backing Bean 中。JSTL 和其他的標(biāo)記庫(kù)也非常有用,但有時(shí)您必須只能使用 Java 代碼來(lái)動(dòng)態(tài)生成內(nèi)容。
一種好的解決方案是使用 JSP 2.0 提供的 Simple Tags API 來(lái)構(gòu)建標(biāo)記庫(kù)。該 API 使您能夠開(kāi)發(fā)標(biāo)記處理器類,但您必須維護(hù)一個(gè)單獨(dú)的 XML 文件(稱為標(biāo)記庫(kù)描述符 (TLD)),該文件定義標(biāo)記名稱、它們的屬性等。如果這聽(tīng)起來(lái)太復(fù)雜,那么您可以簡(jiǎn)單地將 Java 代碼移到使用 JSP 語(yǔ)法的標(biāo)記文件中,讓?xiě)?yīng)用服務(wù)器生成標(biāo)記處理器類和 TLD 文件。讓我們看一下名為 oldCode.jsp 的 JSP 頁(yè)面,它混合了 Java 和 HTML。該頁(yè)面將讀取一個(gè)文本文件(其路徑將作為一個(gè)請(qǐng)求參數(shù)提供)并顯示文件的內(nèi)容(包括行號(hào))。當(dāng)您構(gòu)建演示應(yīng)用程序并想顯示代碼時(shí),這將非常有用。
重要注意事項(xiàng)!請(qǐng)勿在生產(chǎn)環(huán)境中使用本部分的示例(oldCode.jsp 和 newCode.jsp),因?yàn)樗鼈兛赡軙?huì)泄漏應(yīng)用程序的源代碼。
oldCode.jsp 頁(yè)面使用 java.io API 來(lái)讀取文本文件。它將在 JSP 頁(yè)面范圍中為每一行文本創(chuàng)建兩個(gè)名為 lineText 和 lineNo 的變量。行號(hào)將用 JSTL 的 <fmt:formatNumber> 標(biāo)記來(lái)進(jìn)行格式化,文本將通過(guò) <c:out> 標(biāo)記進(jìn)行顯示:
<!-- oldCode.jsp -->
<%@ page import="java.io.*" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<HTML>
<HEAD>
<TITLE>${param.path}</TITLE>
</HEAD>
<BODY>
<c:if test="${empty param.path}">
<P>The <CODE>path</CODE> parameter wasn't specified.
</c:if>
<c:if test="${!empty param.path}">
<P><B><CODE>${param.path}</CODE></B>
<%
String path = application.getRealPath(
request.getParameter("path"));
BufferedReader in = new BufferedReader(new FileReader(path));
try {
int lineNo = 0;
String lineText;
while ((lineText = in.readLine()) != null) {
lineNo++;
pageContext.setAttribute("lineText", lineText);
pageContext.setAttribute("lineNo",
new Integer(lineNo));
%>
<fmt:formatNumber var="fmtLineNo"
value="${lineNo}" minIntegerDigits="3"/>
<PRE>${fmtLineNo} <c:out value="${lineText}"/></PRE>
<%
}
} finally {
in.close();
}
%>
</c:if>
</BODY>
</HTML>
來(lái)自前一個(gè)頁(yè)面示例的全部 Java 代碼都可以移到一個(gè)名為 readTextFile.tag 的標(biāo)記文件中。只需進(jìn)行少許修改:您必須使用 <%@tag%> 指令和 jspContext 隱式對(duì)象,而不是 <%@page%> 和 pageContext。您還必須使用 JSP 指令來(lái)聲明屬性和變量:
<!-- readTextFile.tag -->
<%@ tag import="java.io.*" %>
<%@ attribute name="path" required="true" %>
<%@ variable name-given="lineText" scope="NESTED" %>
<%@ variable name-given="lineNo" scope="NESTED" %>
<%
String path = application.getRealPath(
(String) jspContext.getAttribute("path"));
BufferedReader in = new BufferedReader(new FileReader(path));
try {
int lineNo = 0;
String lineText;
while ((lineText = in.readLine()) != null) {
lineNo++;
jspContext.setAttribute("lineText", lineText);
jspContext.setAttribute("lineNo",
new Integer(lineNo));
%>
<jsp:doBody/>
<%
}
} finally {
in.close();
}
%>
您可以在任何需要逐行處理文本文件的 JSP 頁(yè)面中使用 readTextFile.tag 文件。每一個(gè)頁(yè)面都可以對(duì)文本行執(zhí)行任何需要的操作,因?yàn)樵摌?biāo)記文件使用了 <jsp:doBody/>,從而允許 JSP 頁(yè)面將處理當(dāng)前行的代碼放到 <tags:readTextFile> 和 </tags:readTextFile> 之間。newCode.jsp 頁(yè)面的功能與舊樣式的示例相同,但它沒(méi)有混合 Java 和 HTML:
<!-- newCode.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %>
<HTML>
<HEAD>
<TITLE>${param.path}</TITLE>
</HEAD>
<BODY>
<c:if test="${empty param.path}">
<P>The <CODE>path</CODE> parameter wasn't specified.
</c:if>
<c:if test="${!empty param.path}">
<P><B><CODE>${param.path}</CODE></B>
<tags:readTextFile path="${param.path}">
<fmt:formatNumber var="fmtLineNo"
value="${lineNo}" minIntegerDigits="3"/>
<PRE>${fmtLineNo} <c:out value="${lineText}"/></PRE>
</tags:readTextFile>
</c:if>
</BODY>
</HTML>
正如您所見(jiàn),修改舊的 JSP 頁(yè)面以便可以重用代碼并不是很難。維護(hù)也變得更加容易。
隨意重用
重復(fù)的代碼或內(nèi)容是最令人頭疼的事情。有時(shí)它可能是一種容易的解決方案,但修改應(yīng)用程序變得更加困難,這意味著從長(zhǎng)遠(yuǎn)來(lái)看,您將不能適應(yīng)新的用戶需求或快速地修復(fù)問(wèn)題。在理想情況下,應(yīng)用程序不應(yīng)包含相同代碼的多個(gè)版本,Web 內(nèi)容不應(yīng)被復(fù)制和粘貼。
posted on 2006-05-10 14:53
KingWell 閱讀(381)
評(píng)論(0) 編輯 收藏 所屬分類:
Java Server Faces