Web 應用程序中的可重用性
作者:Andrei Cioroianu
了解如何利用 JSP 標記文件、JSF 和 Oracle ADF Faces 重用 Web 內容和 Java 代碼。
|
2005 年 10 月發(fā)表
代碼重用是提高開(kāi)發(fā)人員生產(chǎn)效率和應用程序可維護性的一種非常好的方式。您應當總是尋找設計良好的框架和可自定義的組件,而不是從頭重來(lái)。應用程序特有的代碼也可以在模塊甚至相關(guān)項目間重用。后一種可重用性可使您快速修改,整體利用新特性,并減少測試和調試的時(shí)間。
雖然這些聽(tīng)起來(lái)像是針對程序員的不錯建議,但 Web 開(kāi)發(fā)人員也應當注意這些事情。許多 Web 開(kāi)發(fā)人員已經(jīng)在使用諸如 Java Server Faces (JSF)、Oracle ADF Faces 和 Apache MyFaces 之類(lèi)的框架,這些框架提供了許多內置組件并支持創(chuàng )建其他可重用組件。然而,很多時(shí)候是將許多 HTML 和 JSP 標記從一個(gè) Web 頁(yè)面復制粘貼到其他頁(yè)面中,這意味著(zhù)當 Web 內容改變時(shí)將不得不修改這些頁(yè)面中的重復標記。此外,如果沒(méi)有更新某些頁(yè)面,那么應用程序的外觀(guān)將會(huì )不一致。如果跨頁(yè)面重用 UI 組件就不會(huì )發(fā)生這種情況了,這是因為發(fā)生變化時(shí)只需在一個(gè)地方進(jìn)行編輯就可以了。
在本文中,我將提供一些在基于 JSF 和 ADF Faces 的 Web 應用程序中重用 UI 組件的最佳實(shí)踐。您將了解到如何創(chuàng )建定義 Web 頁(yè)面布局的模板,以及如何重用表單、菜單和按鈕欄。您還將了解到如何轉換現有的 JSP 頁(yè)面以使它們更易于維護,以及如何將由 JSF 和 Oracle ADF Faces 提供的組件與 JSTL 和現代 JSP 特性(例如標記文件)一起使用。
Java Web 可重用特性
自從第一個(gè)版本起,JSP 就已經(jīng)提供了一些鼓勵可重用的基本機制,例如 JavaBeans 支持、基于 Servlets API RequestDispatcher 的 <%@include%> 指令和 <jsp:include> 標記。JSTL 增加了 <c:import> 標記,它使您能夠包含某個(gè)資源的內容,該資源可以位于同一個(gè)應用程序、服務(wù)器中,甚至也可以在遠程服務(wù)器上。Struts Tiles 圍繞著(zhù)這種內容包含特性構建了一個(gè)完整的框架。JSF 也支持這一特性,允許您構建使用 <f:subview> 標記的子表單。JSP 2.0 增加了一個(gè)稱(chēng)為“隱式包含”的新特性。這些特性使用 <include-prelude> 和 <include-coda> 在 web.xml 文件中聲明。正如您所能看到的,雖然頁(yè)面/片斷包含種類(lèi)各異,但每一種都有其自己的用途和上下文。
對自定義標記的支持從 JSP 1.1 就有了,它為構建標記庫提供了一個(gè) API。JSP 1.2 對該 API 進(jìn)行了增強,但很多人認為它太復雜了。因此,JSP 2.0 定義了一個(gè)具有相同功能的全新 API。這個(gè)為標記庫提供的新 API 稱(chēng)為簡(jiǎn)單標記 API,舊 API 現在稱(chēng)為標準標記 API。許多 Web 框架(如 Struts、JSF 和 JSTL)仍使用標準標記 API,以便可以與 JSP 1.2 以及 JSP 2.0 一起使用。簡(jiǎn)單標記 API 是另一種 JSP 2.0 特性 — 標記文件 — 的基礎,該特性使您能夠使用 JSP 語(yǔ)法構建標記庫。除了簡(jiǎn)單標記和標記文件之外,JSP 2.0 規范還定義了 EL 函數,后者使您能夠使用 EL 語(yǔ)法從 JSP 頁(yè)面中調用靜態(tài) Java 方法。
JSF 標準將組件定義為它的可重用單元。這些組件比自定義標記更強大,但也更難設計和實(shí)施。因為有幾個(gè)公司和開(kāi)放源代碼機構正在制作可供使用的 JSF 組件庫,所以您可能不需要構建自己的 JSF 組件。本文的示例使用了 Oracle ADF Faces,它是基于 JSF 標準的最先進(jìn)的框架。
創(chuàng )建頁(yè)面模板。典型 Web 應用程序的所有頁(yè)面共享一個(gè)公共布局,該布局可以定義在一個(gè)地方,如 JSP 標記文件中。該模板可以生成標題和正文標記、應用程序的菜單以及在所有頁(yè)面中出現的其他部分。此外,它可以包含用于加載資源綁定、設置 JSP 變量等的設置標記。在應用程序的每個(gè) Web 頁(yè)面中重復該標記是沒(méi)有意義的。在這一部分中,您將了解如何使用 Oracle JDeveloper 10g (10.1.3)(撰寫(xiě)此文時(shí)為早期試用版)基于 JSF 和 Oracle ADF Faces 構建自定義模板。
JDeveloper 提供了一個(gè)創(chuàng )建 JSF 頁(yè)面模板的向導。從 File 菜單中選擇 New 項,打開(kāi) New Gallery 窗口。然后,轉至 Web Tier 中的 JSF 類(lèi)別,在右側面板中選擇 JSF JSP Template 并單擊 OK:

單擊 Next 跳過(guò) Welcome 頁(yè)面,然后選擇您使用的 J2EE 版本,并再次單擊 Next:

為模板提供一個(gè)文件名:

選擇組件綁定樣式:

指定是否要使用錯誤頁(yè)面:

選擇要使用的標記庫:

提供頁(yè)面標題和其他頁(yè)面屬性:

單擊 Finish。JDeveloper 將創(chuàng )建該模板并在可視化編輯器中將其打開(kāi)。您可以使用 Component Palette 將 JSF 和 Oracle ADF Faces 組件添加到該模板中。然后,您可以在 New Gallery 窗口中從 Template 中選擇 JSF JSP,基于您剛創(chuàng )建的模板創(chuàng )建 JSF 頁(yè)面。這是從模板構建頁(yè)面的一種非常簡(jiǎn)單的方法。另一種方法是將該共用的 JSF 標記移到一個(gè)可重用的標記文件中。以下段落使用了第二種方法。
創(chuàng )建標記文件。從 File 菜單中選擇 New 項,打開(kāi) New Gallery 窗口。然后,轉至 Web Tier 中的 JSP 類(lèi)別,在右側面板中選擇 JSP Tag File 并單擊 OK:

JDeveloper 將打開(kāi)一個(gè)創(chuàng )建 JSP 標記文件的向導窗口。單擊 Next 跳過(guò) Welcome 頁(yè)面,在 File Name 域中輸入 pageTemplate.tag 并單擊 Next:

現在您就可以定義模板標記的屬性了。假定您正在構建一個(gè)基于 Web 的向導,您希望每個(gè)頁(yè)面都有一個(gè)步驟 ID 和一個(gè)標題。標記文件需要該信息來(lái)為每個(gè)頁(yè)面自定義模板標記。單擊 Add 按鈕,輸入 step 屬性名,并將 Required 設為 true。對另一個(gè)名稱(chēng)為 title 的屬性執行同樣的操作:

單擊 Next 和 Finish。JDeveloper 將在 WEB-INF 目錄的 tags 子目錄下創(chuàng )建 pageTemplate.tag 文件。用 <%@attribute%> 指令定義這兩個(gè)標記屬性:
<%@ attribute name="step" required="true" %><%@ attribute name="title" required="true" %>在創(chuàng )建標記文件之后,JDeveloper 將打開(kāi)它進(jìn)行編輯。以下段落介紹了本文通篇用到的示例應用程序的模板代碼。
在標記文件中使用 JSF 和 Oracle ADF Faces。無(wú)論您是否想在普通頁(yè)面的標記文件內使用這些框架,您都必須用 <%@taglib%> 指令來(lái)對其進(jìn)行聲明。在 Component Palette 中選擇 JSP,然后單擊 Taglib。您需要在一個(gè)對話(huà)框中輸入要使用的標記庫的 URI 和前綴:

您還可以使用 Component Palette 來(lái)將任何標記拖放到 JSP 頁(yè)面上,如果它的 <%@taglib%> 指令不在頁(yè)面中,那么 JDeveloper 將自動(dòng)添加它。pageTemplate.tag 示例使用了四個(gè)標記庫: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í),或者在標記文件中使用 JSF 時(shí)有一件非常重要的事情您必須始終牢記:JSF 框架不支持頁(yè)面范圍,后者是 JSTL 標記的默認 JSP 范圍。因此,您應當顯式指定與 JSTL、JSF 和 Oracle ADF Faces 的標記結合使用的 JSP 變量的請求范圍。此外,標記文件的所有屬性都可以通過(guò)保存在頁(yè)面范圍內的 JSP 變量來(lái)訪(fǎng)問(wèn)。您必須復制請求范圍內的屬性,以便它們可以和 JSF 和 Oracle ADF Faces 標記一起使用:
<c:set var="pageTitle" scope="request" value="${pageScope.title}"/>您還可以將屬性的值存儲在由 JSF 管理的 Bean 內。假定您有一個(gè)名稱(chēng)為 WizardBean 的類(lèi),該類(lèi)在 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 屬性,您可以在該屬性中存儲 step 屬性的值。該操作可以利用 JSTL 的 <c:set> 標記來(lái)完成,但您必須確保該 Bean 存在于會(huì )話(huà)范圍中(如 faces-config.xml 文件中所聲明的那樣)。只有在 JSF EL 表達式中首次引用受管 Bean 實(shí)例時(shí),JSF 才會(huì )創(chuàng )建該實(shí)例。如果在訪(fǎng)問(wèn)受管 Bean 的 JSF 或 Oracle ADF Faces 標記之前執行 JSTL 標記,那么,您應當使用 <jsp:useBean>以確保該 Bean 實(shí)例存在于會(huì )話(huà)范圍中。如此,您就可以安全地使用 <c:set> 標記了:
<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> 標記。JDeveloper 將打開(kāi)一個(gè)對話(huà)框,您必須在其中提供該標記的屬性: 
最后,您可以在 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> 內部。<afh:html>、<afh:head> 和 <afh:body> 組件將生成具有相同名稱(chēng)的 HTML 標記。<af:panelPage> 組件顯示頁(yè)面標題。然后,<jsp:doBody> 將調用您放在使用模板標記的 JSP 頁(yè)面中的 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內容。這種 JSP 頁(yè)面將類(lèi)似于: <%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %><tags:pageTemplate step="..." title="...">... JSP content executed by doBody ...</tags:pageTemplate>當執行該頁(yè)面時(shí),標記文件將生成 <html>、<head> 和 <body> 標記以及頁(yè)面標題和其他標題標記。然后,模板標記文件將調用包在 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內容。在此之后,標記文件可能會(huì )生成一個(gè)頁(yè)腳,并用 </body> 和 </html> 來(lái)完成頁(yè)面。下一部分將在基于 Web 的向導的頁(yè)面中使用模板標記。
重用表單、菜單和其他 UI 組件
利用 JSF 和 Oracle ADF Faces 組件構建的 UI 面板可以使用 "subviews" 或 JSP 標記文件在多個(gè)頁(yè)面中重用。前一種解決方案與舊的 JSP 1.2 版本兼容,但標記文件更靈活。這一部分將介紹如何基于 JSF 和 Oracle ADF Faces 將一個(gè) Web 表單分成多個(gè)頁(yè)面和標記文件。當用戶(hù)第一次訪(fǎng)問(wèn)應用程序時(shí),他將使用包含后退和前進(jìn)按鈕的向導式界面來(lái)逐步瀏覽這些頁(yè)面。在此以后,他可能想更新最初提供的信息。在這種情況下,用戶(hù)需要使用基于菜單的界面直接訪(fǎng)問(wèn)應用程序的頁(yè)面。
可以在上述的兩種情況下使用同一種 Web 表單。此外,所有的表單可以組合在一個(gè)確認頁(yè)面中,該頁(yè)面可以讓用戶(hù)以只讀模式查看信息。當用戶(hù)必須修改其中一個(gè)表單時(shí),可以編輯單個(gè)文件。每一個(gè)修改都將顯示在向導風(fēng)格的界面(用于從用戶(hù)那獲取信息)、確認頁(yè)面(用于查看信息)和基于菜單的界面(由用戶(hù)用于更新信息)中。如果不重用表單,您將必須在三個(gè)不同的地方進(jìn)行相同的修改。
開(kāi)發(fā) Backing Bean。您在前一部分中已經(jīng)發(fā)現,示例應用程序使用了一個(gè)名稱(chēng)為 WizardBean 的 Backing Bean。pageTemplate.tag 文件設置了 currentStep 屬性,該屬性保存用戶(hù)在瀏覽器中看到的當前表單的步驟 ID。示例應用程序在確認頁(yè)面(第四步)之前使用了三個(gè)表單,確認頁(yè)面的 ID 由 MAX_STEP 常量來(lái)定義。將該常量公開(kāi)為名為 maxStep 的 bean 屬性,以便可以在 Web 頁(yè)面中使用 JSF EL 來(lái)訪(fǎng)問(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 類(lèi)還有幾個(gè)其他的屬性可用于保存用戶(hù)提供的向導參數:connName、connType、userName、password、role、driver、hostName、sid 和 jdbcPort。該示例應用程序與用戶(hù)數據無(wú)關(guān),但在實(shí)際情況中,向導將用它來(lái)配置數據庫連接。WizardBean 還實(shí)施了幾個(gè)在用戶(hù)單擊向導的按鈕時(shí)將執行 JSF 操作。這些方法稍后將在本部分中進(jìn)行介紹。 要創(chuàng )建您自己的 Bean,您可以在 File 菜單中選擇 New 來(lái)打開(kāi) New Gallery 窗口。然后,轉至 General 中的 Simple Files 類(lèi)別,在右側面板中選擇 Java Class,并單擊 OK:

提供類(lèi)名和程序包名稱(chēng)。然后單擊 OK,創(chuàng )建該類(lèi):

在聲明一個(gè)字段之后(例如 private int currentStep),右鍵單擊其名稱(chēng)并選擇 Generate Accessors。單擊 OK,生成 get 和 set 方法:

創(chuàng )建可重用表單。將向導的主表單編寫(xiě)為可重用標記文件。第一個(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è)表單類(lèi)似: <!-- 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è)標記文件中沒(méi)有一個(gè)包含了用于排列 Oracle ADF Faces 組件的標記。不含任何布局標記使得您可以獨立地使用表單標記,或在確認頁(yè)面中組合它們。Oracle ADF Faces 提供了一個(gè)名稱(chēng)為 <af:panelForm> 的強大組件,它將自動(dòng)執行布局。除了主要的組件之外,表單通常包含有其他的標記,例如 <h:messages globalOnly="true"/> 和 <af:objectLegend name="required"/>。所有這些標記都可以集中在一個(gè)名為 formTemplate.tag 的標記文件中: <!-- 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è)面將類(lèi)似于:
<%@ 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/> 代表向導的三個(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è)面。這些具體的例子充分說(shuō)明了利用可重用標記文件構建 JSF 用戶(hù)界面的實(shí)際好處。
向導風(fēng)格的界面。基于 Web 的向導的頁(yè)面將包含標記為 Back、Next 和 Finish 的按鈕??梢栽谝粋€(gè)名為 stepButtons.tag 的標記文件中定義這些按鈕:
<!-- 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:singleStepButtonBarselectedStep="#{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 類(lèi)包含當用戶(hù)單擊按鈕時(shí)將執行的操作方法: 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"; } ...}操作方法返回的結果將在 faces-config.xml 文件的導航規則中使用: <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)的組件之外,向導頁(yè)面還將包含一個(gè)與 WizardBean 的 currentStep 屬性綁定的隱藏字段。您已經(jīng)看到了 pageTemplate.tag 將在每一次執行頁(yè)面時(shí)設置該屬性。然而,用戶(hù)可能單擊瀏覽器的后退按鈕。作為該操作的結果,在瀏覽器中看到的當前步驟將與 currentStep 屬性的值不符,因為瀏覽器將從其緩存中檢索到頁(yè)面,而不是請求執行 JSF 頁(yè)面。
如果每一次用戶(hù)單擊按鈕時(shí) currentStep 值都與表單數據一起提交,則不會(huì )導致任何問(wèn)題。hiddenData.tag 文件將 currentStep 作為一個(gè)隱藏字段包含在內。此外,如果 currentStep 等于 maxStep(這意味著(zhù)標記在確認頁(yè)面中使用),那么該標記文件將為所有 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>所有的向導部分都可以在 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 非常類(lèi)似。作為練習,您可以嘗試為這些頁(yè)面構建一個(gè)模板,從而將這些頁(yè)面都減少為四行代碼。confirm.jsp 頁(yè)面將一起顯示所有三個(gè)表單,但組件在只讀模式下工作,從而占用的屏幕空間更少并且無(wú)需用戶(hù)交互:
<!-- 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>基于菜單的界面。假定用戶(hù)逐步瀏覽向導的頁(yè)面,提供所有需要的信息。如果用戶(hù)需要在以后修改某些地方,那么他應當不需要再次瀏覽所有的向導頁(yè)面。相反,用戶(hù)界面將讓用戶(hù)直接轉至必須修改信息的表單。menuTabs.tag 文件使用相同名稱(chēng)的 Oracle ADF Faces 組件來(lái)構建菜單:
<!-- 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>菜單的導航規則在 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>當用戶(hù)單擊菜單的標簽時(shí),表單數據將被提交給 Web 服務(wù)器,在該服務(wù)器上 JSF 框架將更新 Backing Bean。如果用戶(hù)想更新表單而不改變當前的標簽,那么需要使用提交按鈕。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 文件將把菜單附加到向導的表單上。下面是 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)始構建新的頁(yè)面,那么一切都沒(méi)什么問(wèn)題。但是,對于包含了以 Java 代碼形式存在的顯示邏輯的舊 JSP 頁(yè)面,該如何處理呢?維護這些頁(yè)面困難重重,并且,若不將顯示代碼從 JSP 頁(yè)面中分離出來(lái),則無(wú)法對其進(jìn)行重用。此外,Java 代碼和 HTML 標記不應混合在同一個(gè)頁(yè)面中,因為這將使 Java 開(kāi)發(fā)人員和 Web 設計人員無(wú)法輕松地展開(kāi)并行工作。
JSF 和 Oracle ADF Faces 組件解決了多數情況下的此種問(wèn)題,因為 Web 頁(yè)面將使用這兩個(gè)框架提供的標記來(lái)構建,同時(shí)將 Java 代碼放到了 Backing Bean 中。JSTL 和其他的標記庫也非常有用,但有時(shí)您必須只能使用 Java 代碼來(lái)動(dòng)態(tài)生成內容。
一種好的解決方案是使用 JSP 2.0 提供的 Simple Tags API 來(lái)構建標記庫。該 API 使您能夠開(kāi)發(fā)標記處理器類(lèi),但您必須維護一個(gè)單獨的 XML 文件(稱(chēng)為標記庫描述符 (TLD)),該文件定義標記名稱(chēng)、它們的屬性等。如果這聽(tīng)起來(lái)太復雜,那么您可以簡(jiǎn)單地將 Java 代碼移到使用 JSP 語(yǔ)法的標記文件中,讓?xiě)梅?wù)器生成標記處理器類(lèi)和 TLD 文件。讓我們看一下名為 oldCode.jsp 的 JSP 頁(yè)面,它混合了 Java 和 HTML。該頁(yè)面將讀取一個(gè)文本文件(其路徑將作為一個(gè)請求參數提供)并顯示文件的內容(包括行號)。當您構建演示應用程序并想顯示代碼時(shí),這將非常有用。
重要注意事項!請勿在生產(chǎn)環(huán)境中使用本部分的示例(oldCode.jsp 和 newCode.jsp),因為它們可能會(huì )泄漏應用程序的源代碼。
oldCode.jsp 頁(yè)面使用 java.io API 來(lái)讀取文本文件。它將在 JSP 頁(yè)面范圍中為每一行文本創(chuàng )建兩個(gè)名為 lineText 和 lineNo 的變量。行號將用 JSTL 的 <fmt:formatNumber> 標記來(lái)進(jìn)行格式化,文本將通過(guò) <c:out> 標記進(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 的標記文件中。只需進(jìn)行少許修改:您必須使用 <%@tag%> 指令和 jspContext 隱式對象,而不是 <%@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è)面都可以對文本行執行任何需要的操作,因為該標記文件使用了 <jsp:doBody/>,從而允許 JSP 頁(yè)面將處理當前行的代碼放到 <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è)面以便可以重用代碼并不是很難。維護也變得更加容易。 隨意重用
聯(lián)系客服