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

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

為模板提供一個文件名:

選擇組件綁定樣式:

指定是否要使用錯誤頁面:

選擇要使用的標記庫:

提供頁面標題和其他頁面屬性:

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

JDeveloper 將打開一個創建 JSP 標記文件的向導窗口。單擊 Next 跳過 Welcome 頁面,在 File Name 域中輸入 pageTemplate.tag 并單擊 Next:

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

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

您還可以使用 Component Palette 來將任何標記拖放到 JSP 頁面上,如果它的 <%@taglib%> 指令不在頁面中,那么 JDeveloper 將自動添加它。pageTemplate.tag 示例使用了四個標記庫: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" %>在同時使用 JSF 和 JSTL 時,或者在標記文件中使用 JSF 時有一件非常重要的事情您必須始終牢記:JSF 框架不支持頁面范圍,后者是 JSTL 標記的默認 JSP 范圍。因此,您應當顯式指定與 JSTL、JSF 和 Oracle ADF Faces 的標記結合使用的 JSP 變量的請求范圍。此外,標記文件的所有屬性都可以通過保存在頁面范圍內的 JSP 變量來訪問。您必須復制請求范圍內的屬性,以便它們可以和 JSF 和 Oracle ADF Faces 標記一起使用:
<c:set var="pageTitle" scope="request" value="${pageScope.title}"/>您還可以將屬性的值存儲在由 JSF 管理的 Bean 內。假定您有一個名稱為 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 有一個 currentStep 屬性,您可以在該屬性中存儲 step 屬性的值。該操作可以利用 JSTL 的 <c:set> 標記來完成,但您必須確保該 Bean 存在于會話范圍中(如 faces-config.xml 文件中所聲明的那樣)。只有在 JSF EL 表達式中首次引用受管 Bean 實例時,JSF 才會創建該實例。如果在訪問受管 Bean 的 JSF 或 Oracle ADF Faces 標記之前執行 JSTL 標記,那么,您應當使用 <jsp:useBean>以確保該 Bean 實例存在于會話范圍中。如此,您就可以安全地使用 <c:set> 標記了:
<jsp:useBean class="webreuse.WizardBean" id="wizard" scope="session"/> <c:set target="${wizard}" property="currentStep" value="${pageScope.step}"/>以上代碼可以手動輸入,或者可以使用 JDeveloper 的 Component Palette。選擇 JSP,然后單擊 UseBean,添加 <jsp:useBean> 標記。JDeveloper 將打開一個對話框,您必須在其中提供該標記的屬性:

最后,您可以在 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> 組件將生成具有相同名稱的 HTML 標記。<af:panelPage> 組件顯示頁面標題。然后,<jsp:doBody> 將調用您放在使用模板標記的 JSP 頁面中的 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內容。這種 JSP 頁面將類似于:
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags" %> <tags:pageTemplate step="..." title="..."> ... JSP content executed by doBody ... </tags:pageTemplate>當執行該頁面時,標記文件將生成 <html>、<head> 和 <body> 標記以及頁面標題和其他標題標記。然后,模板標記文件將調用包在 <tags:pageTemplate> 和 </tags:pageTemplate> 之間的 JSP 內容。在此之后,標記文件可能會生成一個頁腳,并用 </body> 和 </html> 來完成頁面。下一部分將在基于 Web 的向導的頁面中使用模板標記。
重用表單、菜單和其他 UI 組件
利用 JSF 和 Oracle ADF Faces 組件構建的 UI 面板可以使用 "subviews" 或 JSP 標記文件在多個頁面中重用。前一種解決方案與舊的 JSP 1.2 版本兼容,但標記文件更靈活。這一部分將介紹如何基于 JSF 和 Oracle ADF Faces 將一個 Web 表單分成多個頁面和標記文件。當用戶第一次訪問應用程序時,他將使用包含后退和前進按鈕的向導式界面來逐步瀏覽這些頁面。在此以后,他可能想更新最初提供的信息。在這種情況下,用戶需要使用基于菜單的界面直接訪問應用程序的頁面。
可以在上述的兩種情況下使用同一種 Web 表單。此外,所有的表單可以組合在一個確認頁面中,該頁面可以讓用戶以只讀模式查看信息。當用戶必須修改其中一個表單時,可以編輯單個文件。每一個修改都將顯示在向導風格的界面(用于從用戶那獲取信息)、確認頁面(用于查看信息)和基于菜單的界面(由用戶用于更新信息)中。如果不重用表單,您將必須在三個不同的地方進行相同的修改。
開發 Backing Bean。您在前一部分中已經發現,示例應用程序使用了一個名稱為 WizardBean 的 Backing Bean。pageTemplate.tag 文件設置了 currentStep 屬性,該屬性保存用戶在瀏覽器中看到的當前表單的步驟 ID。示例應用程序在確認頁面(第四步)之前使用了三個表單,確認頁面的 ID 由 MAX_STEP 常量來定義。將該常量公開為名為 maxStep 的 bean 屬性,以便可以在 Web 頁面中使用 JSF EL 來訪問它:
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 類還有幾個其他的屬性可用于保存用戶提供的向導參數:connName、connType、userName、password、role、driver、hostName、sid 和 jdbcPort。該示例應用程序與用戶數據無關,但在實際情況中,向導將用它來配置數據庫連接。WizardBean 還實施了幾個在用戶單擊向導的按鈕時將執行 JSF 操作。這些方法稍后將在本部分中進行介紹。
要創建您自己的 Bean,您可以在 File 菜單中選擇 New 來打開 New Gallery 窗口。然后,轉至 General 中的 Simple Files 類別,在右側面板中選擇 Java Class,并單擊 OK:

提供類名和程序包名稱。然后單擊 OK,創建該類:

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

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