轉(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ā)表
代碼重用是提高開發(fā)人員生產(chǎn)效率和應(yīng)用程序可維護(hù)性的一種非常好的方式。您應(yīng)當(dāng)總是尋找設(shè)計(jì)良好的框架和可自定義的組件,而不是從頭重來(lái)。應(yīng)用程序特有的代碼也可以在模塊甚至相關(guān)項(xiàng)目間重用。后一種可重用性可使您快速修改,整體利用新特性,并減少測(cè)試和調(diào)試的時(shí)間。
雖然這些聽起來(lái)像是針對(duì)程序員的不錯(cuò)建議,但 Web 開發(fā)人員也應(yīng)當(dāng)注意這些事情。許多 Web 開發(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è)公司和開放源代碼機(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)(撰寫此文時(shí)為早期試用版)基于 JSF 和 Oracle ADF Faces 構(gòu)建自定義模板。
JDeveloper 提供了一個(gè)創(chuàng)建 JSF 頁(yè)面模板的向?qū)А?File 菜單中選擇 New 項(xiàng),打開 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)建該模板并在可視化編輯器中將其打開。您可以使用 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),打開 New Gallery 窗口。然后,轉(zhuǎn)至 Web Tier 中的 JSP 類別,在右側(cè)面板中選擇 JSP Tag File 并單擊 OK:

JDeveloper 將打開一個(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 將打開它進(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 將打開一個(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)行相同的修改。
開發(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)定義。將該常量公開為名為 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)打開 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ū)У闹鞅韱尉帉憺榭芍赜脴?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>除了所有可見的組件之外,向?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 從頭開始構(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 開發(fā)人員和 Web 設(shè)計(jì)人員無(wú)法輕松地展開并行工作。
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 使您能夠開發(fā)標(biāo)記處理器類,但您必須維護(hù)一個(gè)單獨(dú)的 XML 文件(稱為標(biāo)記庫(kù)描述符 (TLD)),該文件定義標(biāo)記名稱、它們的屬性等。如果這聽起來(lái)太復(fù)雜,那么您可以簡(jiǎn)單地將 Java 代碼移到使用 JSP 語(yǔ)法的標(biāo)記文件中,讓應(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>正如您所見,修改舊的 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ù)制和粘貼。