程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 集成 Struts、Tiles 和 JavaServer Faces

集成 Struts、Tiles 和 JavaServer Faces

編輯:關於JAVA

內容:JSF 簡介為什麼將這三者集成為一體?將 Struts 和 JSF 與 Struts-Faces 集成將Struts 應用程序移植到 JSF集成 Struts-Faces 和 Tiles 的挑戰到目前為止所作的改變參考資料 作者簡介對本文的評價相關內容:Struts, an open-source MVC implementationStruts and Tiles aid component-based developmentUI development with JavaServer FacesdeveloperWorks Toolbox 訂閱 訂閱:developerWorks 時事通訊將三種技術的功能、靈活性和可管理性集成到一起

Srikanth Shenoy, J2EE 顧問, Objectseek Inc.Nithin Mallya, J2EE 顧問, Objectseek Inc.2003 年 10 月 25 日

您是否想將 JavaServer Faces (JSF)的強大前端功能、Tiles 的內容格式編排優勢和 Struts controller 層的靈活性都加入到您的J2EE Web 應用程序中?企業級 Java 專家 Srikanth Shenoy 和 Nithin Mallya 為您展示了如何將這三者的功能集成到一起。本文演示了如何在 Struts-Faces集成庫中定制類以使得它們可以與 Tiles 和 JSF 一同使用,並用一個實際的例子解釋了這個過程背後的基本原理以及如何使用新的一組類的細節。

將 Struts、Tiles 和 JavaServer Faces (JSF) 一起使用,開發人員可以實現易於管理和重用的、健壯的、界面清晰的 Web 應用程序。

Struts 框架推出已經有一段時間了,它已經成為在開發 J2EE Web 應用程序時開發人員所采用的事實上的標准。Tiles 框架是在 Struts 之後不久出現的,它通過為開發人員提供用組件組裝展示頁面的能力開拓了自己的生存環境。JSF 是 Web 應用程序框架中最新的成員,它提供了驗證用戶輸入和處理用戶事件的機制,最重要的是,這是一種以協議無關的方式呈現用戶界面組件的方法(有關這些 技術的概況,參見本文相關頁面“ The major players”)。

盡管 Struts 和 JSF 中有一些功能是重疊的,但是它們在其他方面起到了互為補充的作用。這三種技術的結合可以為開發 Web 應用程序、組織其展示和以協議無關的方式呈現定制的用戶界面(UI)組件提供一種高效的途徑。

為了運行本文中的示例代碼,需要 Struts 1.1、Tiles、JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0 以及 Struts-Faces 0.4。Jakarta 項目提供的 Struts 1.1 發行版本將 Struts 和 Tiles 捆綁發布。還可以從 Jakarta 項目上下載 Struts-Faces 集成庫。JSF-RI 是 Sun 的 Web 開發工具包(Web Services Developer Pack)的一部分(在 參考資料中有這些下載和示例代碼的鏈接)。

現在回到集成三種技術的細節上。首先有個壞消息:在本文發表的時候,這三種技術是不能直接互操作的。好消息是:在本文中,我們展示了集成 Struts、Tiles 和 JSF 的方法。我們假設您已經了解 Struts 和 Tiles。對 JSF 有一些了解會有幫助(參閱 參考資料中提供的 developerWorks 上的 JSF 教程的鏈接),但是不了解也不妨礙對本文的理解。

JSF 簡介JSF 應用程序是使用 JSF 框架的普通 J2EE Web 應用程序,JSF 框架提供了豐富的 GUI 組件模型,這些模型體現了真正的 GUI 框架內涵。您可能聽人們說過,盡管某種技術不錯,但是它的外觀仍然需要改進。是的,用 Html 組件構建平淡無奇的頁面的日子已經過去了,如果使用 JSF 的話,具有更高級 GUI 外觀的日子就在眼前。您會問,怎麼做呢?樹形組件、菜單組件和圖形是已經存在的 UI 組件,這些 JSF 一定要提供。更進一步,JSF 通過提供容易使用的 API 鼓勵創建自定義組件。

注: 這裡所提到的 UI 組件是 Sun 提供的示例的一部分。像所有規范一樣,實際的實現由不同的提供商完成。

在傳統的使用模型-視圖-控制器(MVC)的 Web 應用程序中,GUI 組件是由處理展示和業務邏輯的自定義標記所表示的。這樣就出現了必須“編寫與客戶機設備打交道的代碼”的問題,這會產生重復的代碼。使用 JSF 就不會有這個問題。

JSF 結構將 展示邏輯 (“什麼”)與 UI 組件的 業務邏輯 (“為什麼”和“如何”)分離。通過在 JSP 頁面中使用 JSF 標記,就可以將 renderer 與 UI 組件關聯在一起。一個 UI 組件可以用不同的 renderer 從而以不同的方式呈現。特定於 UI 組件的代碼在服務器上運行,並且響應用戶操作所產生的事件。

JSF-RI 提供了一個 render kit,它帶有一個自定義標記庫,用以從 UI 組件呈現 Html。它還提供了根據需要定制這些組件外觀的能力。如果需要特殊的組件,那麼可以為特定的客戶機設備構造定制的標記並讓它與一個子 UI 組件和定制的 renderer 相關聯。對於不同的設備,您所需要做的就是指定不同的 renderer。

JSF 和 UI 組件您可能已經用 Java AWT 或者 Swing API 創建過 Java GUI 應用程序,所以您應該熟悉 JSF 的 UIComponent (它與 AWT 或者 Swing 組件很相像)。它儲存其子組件的樹(如果有的話)並為客戶端發生的動作生成標准事件,例如單擊一個按鈕以提交表單。這些事件緩存在 FacesContext 中。您可以用自定義標記關聯每一個這種事件的處理程序。例如,用一個自定義的 ActionListener 處理用戶單擊或者表單提交。

JSF UIComponent 、 Renderer 和標記總是共同工作的。所有 JSP 自定義標記都是通過繼承 UIComponentTag 創建的。 DOStart 和 doEnd 方法總是在 UIComponentTag 類中實現。您只需在這些標記類中提供其他的功能。

圖 1展示了自定義標記、UI 組件和 renderer 之間的關系。客戶機浏覽器訪問用 JSF 標記( jsf:myTag )表示 UI 組件( MyComponent )的 JSP 頁面。這個 UI 組件運行在服務器上,並用適當的 renderer ( MyRenderer )以 HTML 的形式呈現給客戶。這個 JSP 頁面表現了在 JSF-RI 中使用帶自定義標記的用戶界面組件而不是在 Html 中對它們進行編碼。

例如,圖 1 展示了 h:panel:group 標記的使用。這個標記用於將一個父組件下面的各個組件組織到一起。如果與像 panel_grid 和 panel_data 這樣的其他面板標記共同使用,那麼它會在運行時生成 HTML 表中的列的標記。JSF-RI-提供的 html_basic 標記庫用於表示像文本字段、按鈕這樣的 Html 組件。

圖1. 呈現一個 JSF 頁面

JSF 生命周期JSF 生命周期包括六個階段:一個傳入的請求可能會經歷全部階段,也可能不經歷任何階段,這取決於請求的類型、在生命周期中發生的驗證和轉換錯誤以及響應的類型。JSF 框架處理由 JSP 頁生成的 Faces 請求,並返回 faces或者 non-faces 響應。

在提交一個 JSF 表單,或者當用戶單擊指向在 URL 中具有 /faces 前綴的 URL 的鏈接時,就會出現 faces 響應。所有 faces 請求都由一個 FacesServlet 處理 -- 這是 JSF 中的控制器。

發送給一個 servlet 或者一個沒有 JSF 組件的 JSP 頁面的請求稱為 non-faces 請求。如果結果頁中有 JSF 標記,那麼它就稱為 faces 響應,如果沒有 JSF 標記,就是 non-faces 響應。

JSF 生命周期有六個階段:

重建請求樹 應用請求值 進行驗證 更新模型值 調用應用程序 呈現響應

根據 JSF 規范,每一階段表示請求處理生命周期的一個邏輯概念。不過在 JSF-RI 中,這些階段是由具有對應名字的實際類表示的。下面一節描述了每一階段是如何對請求進行處理並生成響應的。您將首先看到的是處理一個 faces 請求所涉及的階段,然後是處理 faces 響應所涉及的階段。

處理 faces 請求為了理解 JSF 請求處理,請看 FlightSearch.JSP,這是 清單 1中的一個簡單的 JSF 表單。一個 JSF 頁面基本上就是這個樣子的。這個 JSF 表單有輸入文本字段 from和 to citIEs、 departure 和 return dates,還有提交和重設表單的按鈕(我們會在稍後分析清單1中每一個標記的意義)。現在,假設提交這個表單產生了一個 faces 請求。

這個請求被 FacesServlet 所接收、並在向客戶發回響應之前通過不同的階段。 圖 2展示了如何對 JSF 請求進行處理。讓我們看一看這是如何進行的。

1. 接收請求 FacesServlet 接收請求並從 FacesContextFactory 得到 FacesContext 的一個實例。

2. 委托生命周期處理 FacesServlet 通過對在 faces 上下文中傳遞的 Lifecycle 實現調用 execute 方法將生命周期處理委托給 Lifecycle 接口。

3. Lifecycle 執行每一階段 Lifecycle 實現執行從重建組件樹階段開始的每一階段。

4. 創建的組件樹 在重建組件樹階段,用 travelForm 中的組件創建一個組件樹。這個樹以 UIForm 作為根,用不同的文本字段和按鈕作為其子組件。

fromCity 字段有一個驗證規則,它規定其不能為空,如 validate_required 標記所示。這個標記將 fromCity 文本字段與一個 JSF Validator 鏈接起來。

JSF 有幾個內建的驗證器。相應的 Validator 是在這個階段初始化的。這個組件樹緩存在 FacesContext 中、並且這個上下文會在後面用於訪問樹及調用任何一個事件處理程序。同時 UIForm 狀態會自動保存。所以,當刷新這一頁時,就會顯示表單的原始內容。

5. 從樹中提取值 在應用請求值階段,JSF 實現遍歷組件樹並用 decode 方法從請求中提取值,並在本地設置每一個組件。如果在這個過程中出現了任何錯誤,那麼它們就在 FacesContext 中排隊並在呈現響應階段顯示給用戶。

同時,在這個階段排隊的所有由像單擊按鈕這樣的用戶操作產生的事件,都廣播給注冊的偵聽器。單擊 reset 按鈕會將文本字段中的值重新設置為它們原來的值。

6. 處理驗證 在處理驗證階段,對在應用請求值階段設置的本地值進行所有與各組件相關的驗證。當 JSF 實現對每一個注冊的驗證器調用 validate 方法時就會進入此階段。

如果任何一項驗證失敗,那麼生命周期就會進入呈現響應階段,在那裡呈現帶有錯誤信息的同一頁面。在這裡,所有在這一階段排隊的事件同樣都會廣播給注冊的偵聽器。

JSF 實現處理源字段上的驗證器。如果數據是無效的,那麼控制就交給呈現響應階段,在這個階段重新顯示 FlightSearch.JSp 並帶有相關組件的驗證錯誤。通過在 JSP 頁面中聲明 output_errors, ,頁面中的所有錯誤都會顯示在頁面的底部。

7. 設置模型對象值 在更新模型值階段,成功處理了所有驗證後,JSF 實現就通過對每一組件調用 updateModel 方法用有效值設置模型對象值。如果在將本地數據轉換為由模型對象屬性所指定的類型時出現任何錯誤,那麼生命周期就進入呈現響應階段,並將錯誤顯示出來。來自表單字段屬性的值會填充為模型對象的屬性值。

8. 可以調用 ActionListener 可以將一個 ActionListener 與一個用戶操作,如單擊提交按鈕相關聯,如 清單 1所示。在調用應用程序階段,對 FlightSearchActionListener 調用了 processAction 方法。在實際應用中, processAction 方法在調用後會搜索數據以找出滿足條件的航班,並從組件的 action 屬性中提取輸出。

在本文提供的這個示例 Web 應用程序中,我們使用了靜態數據表示航班表。這個方法還將提取的 action 屬性發送給 NavigationHandler 實現。 NavigationHandler 查詢 faces-config.XML 文件 -- 這是 JSF 的默認應用程序配置文件 -- 以根據這一輸出確定是什麼。

9. 呈現響應 在呈現響應階段,如果在 faces 上下文中沒有錯誤,就顯示由查詢配置文件得到的這一頁 FlightList.JSp。如果是因為前面任一階段的錯誤而到達這一階段的,那麼就會重新顯示帶有錯誤信息的 FlightSearch.JSP。

圖 2. 處理一個 JSF 請求 單擊這裡以觀看該圖。

清單 1. FlightSearch.JSP,一個簡單的 JSF 表單

<%@ taglib uri="http://java.sun.com/JSf/Html" prefix="h" %><%@ taglib uri="http://Java.sun.com/JSf/core" prefix="f" %>

在這段代碼中使用了兩個 JSF-RI 的標記庫。 html_basic 標記庫定義了 Html 組件常用的標記,而 JSf-core 標記庫包含用於注冊偵聽器和驗證器的標記。其他標記有:

f:use_faces 標記向 JSF 實現表明後面的標記是 faces 標記。 f:validate_required 標記表明它所在的字段(在 FlightSearchBean 中是 fromCity 字段)在提交表單時應該有值。 h:form 和 h:input_text 標記分別表示一個名為 flightSearchForm 的 HTML 表單和各種文本字段。 h:command_button 標記用於表示提交和重設按鈕。 最後, h:output_errors 標記類似於 Struts Html:errors 標記,用於顯示在表單字段驗證中出現的任何錯誤。

一個名為 FlightSearchBean 的 JavaBean 表示在更新模型值階段用 UIComponent 數據更新的模型。通常在 JSP 頁中 JavaBean 是用 jsp:useBean 標記聲明的。您可能注意到了在 FlightSearch.jsp 中沒有這樣做。這是因為可以使用 JSF 的一個名為 Managed Beans 的功能,在 faces 配置文件中聲明所有 JSP 頁面使用的 JavaBeans 組件。在開始時,servlet 容器會初始化這些 JavaBeans 組件。faces-config.XML 文件中的 FlightSearchBean 入口如清單 2所示:

清單 2. faces-config.XML 的 TravelInfoBean入口

FlightSearchBean foo.bar.FlightSearchBean session

現在讓我們看一看這些階段是如何處理響應的。

呈現 faces 響應一個 faces 響應是由 Faces 應用程序在生成包含 JSF 標記的 JSP 頁時生成的。這個響應可以是 JSF 應用程序的 faces 或者 non-faces 響應。

在我們的例子中,清單 1 中頁面的呈現是一個 faces 響應。您可能熟悉 Tag 接口的 DOStartTag() 和 doEndTag() 方法。在 JSF 和 Struts-Faces 中,每一個標記都是從 UIComponentTag 擴展的。 UIComponentTag 實現了 DOStartTag() 和 doEndTag() 方法。

它還有兩個抽象方法 getComponentType() 和 getRendererType()。 通過在具體的標記類中實現這兩個方法,就可以分別指定組件和 renderer 的類型。

考慮一個帶有文本字段的簡單 JSF 表單。在呈現 JSF 表單時執行以下一系列步驟。

1. 調用 doStartTag() 方法 Servlet 窗口對 FormTag 調用 DOStartTag() 方法。

2. 得到 UIComponent FormTag 從 getComponentType() 方法得到其 UIComponent。 UIComponentTag ( FormTag 的父組件)使用 getComponentType() 以從 faces-config.XML 文件中查詢這個組件的類名,並創建 UIComponent(FormComponent )的一個實例。

3. 得到 renderer 下一步, FormTag 從 getRendererType 方法中得到其 renderer 。與組件類型一樣,renderer 名是在 faces-config.XML 文件中查詢的。

4. 調用編碼方法 在創建了 FormComponent 和 FormRenderer 後,對 FormComponent 調用 encodeBegin() 方法。每一個標記的呈現都由 encodeBegin() 開始、由 encodeEnd() 結束。 encodeBegin() 方法是按嵌套的順序調用的。

5. 結束標記和呈現 HTML servlet 容器對標記調用 doEndTag() 方法。以嵌套的反順序對每一個組件調用 encodeEnd() 方法。在最後,表單和所有嵌套的組件都呈現為 HTML。這時,HTML 就生成完畢,並呈現出對應於 JSP 的 Html。

圖 3 顯示構成生成 faces 響應的事件序列。

圖 3. 呈現一個 faces 響應 單擊這裡以查看該圖。

為什麼將這三者集成為一體?隨著 JSP 和相關規范的不斷發展,像 JSF 和 JSP 標記庫(或者 JSTL,它使用簡單的標記封裝許多 JSP 應用程序常用的核心功能)這樣的新標准正在不斷出現。下面是使用集成為一個整體的新技術一些好處:

更清晰地分離行為和展示。 將標記、 renderer 和組件分離,就可以更好地定義開發周期中的頁面作者和應用程序開發人員的作用。 改變一個組件的展示不會有雪崩效應。現在您可以容易地只對 renderer 作出改變。在傳統的 MVC 模型中,由於沒有這種分離,對於標記的任何改變都需要改變業務邏輯。現在再不需要這樣了。 renderer 無關性。 也可以說是協議無關性,通過對帶有多個 renderer 的多種展示設備重復使用組件邏輯實現。使用不同 renderer 的能力使得不再需要對特定的設備編寫整個表示層代碼。 組裝和重用自定義組件的標准。JSF 的考慮范圍超出了“表單和字段”,它提供了豐富的組件模型用以呈現自定義 GUI 組件。用 JSF 可以定制每一個組件在頁面中的外觀和行為。開發人員還擁有創建他們自己的 GUI 組件(如菜單和樹)的能力,這些組件可以用簡單的自定義標記容易地加入到任何 JSP 頁面中。就像 AWT 和 Swing 所提供的 Java 前端 GUI 組件一樣,我們可以在我們的 Web 頁而中有自定義的組件,它們使用自己的事件處理程序並有定制的外觀。這是 Web 層的 GUI 天堂!

Struts 是一種已經擁有大量客戶基礎的框架。許多 IT 部門認識到這種 MVC 框架的價值並使用它有一段時間了。JSF 沒有像 Structs 這樣強大的控制器結構,也沒有像它那樣標准化的 ActionForm 和 Actions (及它們聲明的能力)。將 Tiles 集成到集合體中,就給了自己重復使用和以無縫的方式改變公司布局的能力。

移植支持 JSF 的 Struts 應用程序的挑戰是雙重的。首先,Struts 標記不是 JSF 兼容的。換句話說,它們沒有像 JSF 規范所規定的那樣擴展 UIComponentTag ,所以,JSF 不能解釋它們並關聯到 UIComponent 和 Renderers 。

其次,在 FacesServlet 與 Struts RequestProcessor 之間沒有鏈接。在 Struts 應用程序中, RequestProcessor 負責用 ActionForm 和 Actions 類中的回調方法顯示。 ActionForm 屬性和 validate() 的 getter 和 setter 是 ActionForm 中的回調方法。對於 Action , execute() 是回調方法。除非調用了 RequestProcessor ,否則 Struts ActionForm 和 Actions 類中的回調方法沒有機會調用業務邏輯。

將 Struts 和 JSF 與 Struts-Faces 集成這裡,您可能會問是否有軟件可以幫助將 Struts 與 JSF 集成,或者是否必須自己編寫集成軟件。

好消息是已經有這樣的軟件了。 Struts-Faces 是一個早期發布的 Struts JSF 集成庫。這個庫是由 Craig McClanahan 創建的,它使得將現有 Struts 應用程序移植到 JSF 變得容易了(保留了對現有 Struts 投資的價值)。Struts-Faces 還力圖與 JSF 進行簡潔的集成,這樣就可以在前端使用 JSF,同時後端仍然有熟悉的 Struts 組件。

圖 4 展示了 Struts-Faces 與 JSF 類之間的關系。藍色的類屬於 Struts-Faces。

圖 4. Struts-Faces 類圖 單擊這裡以查看該圖。

下面是 Struts-Faces 的主要組件:

FacesRequestProcessor 類,它處理所有 faces 請求。這個類繼承了常規 Struts RequestProcessor ,並處理 faces 請求。Non-faces 請求發送給出其父類 -- RequestProcessor 。 ActionListenerImpl 類,它處理像提交表單或者單擊鏈接這樣的 ActionEvent 。這個類用於代替由 JSF-RI 提供的默認 ActionListener 實現。只要在一個 faces 請求中生成 ActionEvent ,就會對 ActionListenerImpl 調用 processAction() 方法、並將 ActionEvents 轉送給 FacesRequestProcessor 。這很有意思,因為 RequestProcessor 通常只由 ActionServlet 調用以處理 HTTP 請求。 FormComponent 類,它擴展了 JSF Form 組件,但是是在 Struts 生命周期內調用的。 FormComponent 的 renderer 和標記。 只用於輸出的數據標記和 renderer ,這裡不需要分離組件。例如, ErrorsTag 和 ErrorsRenderer 用於在 Html 中顯示表單錯誤。 ServletContextListener 的名為 LifeCycleListener 的實現。它用於在初始化時注冊相應的 RequestProcessor 。 faces-config.XML 文件。這個文件已經捆綁在 struts-faces.jar 文件中。

清單 3 展示了使用 Struts-Faces 標記的 FlightSearch.JSP。它類似於在 清單 1中展示的 JSF 例子。這裡用粗體突出了區別之處。在這裡,您會發現增加了一個新標記庫 tags-faces。這個標記庫定義聲明這些標記由 Struts-Faces API 所使用。

清單 3. FlightSearch.JSP 使用 Struts-Faces 標記

<%@ taglib uri="http://java.sun.com/JSf/Html" prefix="h" %><%@ taglib uri="http://Java.sun.com/JSf/core" prefix="f" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-faces" prefix="s" %>

s:form 標記用於創建這個 Html 表單。表單的 action 屬性是 /listFlights而不是像 清單 1那樣指定為表單名 flightForm。在 JSF 中,表單名只是指定給 UIForm 的名字而沒有更多的意義。

FlightSearchBean 是 JSF 表單的模型,並在更新模型值階段得到其值。不過在 Struts 中,表單 action 指向 Struts 配置文件 struts-config.xml 中的 ActionMapping 。為了理解它是如何起作用的,還必須看一下清單 4 中顯示的 struts-config.XML 文件。

您會看到 /listFlights 的 ActionMapping 表明這個 URI 路徑的 ActionForm 是 foo.bar.FlightSearchForm ,而 Action 類是 foo.bar.FlightSearchAction 。換句話說, ActionForm ( FlightSearchForm )本身就是 Struts-Faces 中的 Html 表單的模型,它的 action 間接地指向這個模型(您可以在清單 3 中看到這一點,那裡文本字段標記指向 FlightSearchForm 。在普通 Struts 應用程序中這會是 )。

清單 4. 在 struts-config.XML 中聲明 Action

您會注意到在 action 屬性中缺少熟悉的 .do。這是因為 Struts-Faces 使用表單 action 本身作為表單名(它還應該與 Struts 配置文件中的 ActionForm 名相匹配)。

集成 Struts 和 Tiles 的五個步驟以下五步可以讓 Struts 1.1 和 Tiles 共同工作: 1. 創建一個 JSP 以表示站點的布局。這是主 JSP,並帶有頁頭、頁體和頁腳的占位符。分別用 Tiles 標記添加到主 JSP 頁面中。 2. 創建一個 Tiles 定義文件並定義每個集成頁面的每個占位符中必須包括哪個 JSP 頁面。用惟一的名稱標識出每一個合成頁面定義。 3. 在 struts-config.xml 文件中改變全局和本地轉發以使用上一步驟中給出的惟一名稱而不是別名。 4. 在啟動時用 TilesPlugIn 裝載 Tiles 定義文件。將 TilesPlugIn 項加入到 struts-config.xml 文件中。 5. 將 TilesRequestProcessor 項添加到 struts-config.XML 文件中。這是支持 Tiles 的 Struts 應用程序的默認請求處理程序。

還要注意我們在這裡沒有使用 JSF validation 標記。這是因為在 Struts 中,驗證是在 ActionForm 類中的 validate() 方法中進行的,有可能是通過使用 Commons-Validator。 s:errors 標記類似於 Struts 錯誤標記並用於顯示在驗證時出現的錯誤消息。

另一件要注意的事情是沒有 ActionListener 顯式地與提交按鈕相關聯。這是因為在 Struts-Faces 中已經提供了 ActionListener 並且總是將 faces 請求與 ActionEvent s 一同轉交給 FacesRequestProcessor ,在那裡根據 struts-config.XML 文件將請求分派給相應的 Action 類。

將Struts 應用程序移植到 JSF為了將 Struts Web 應用程序與 JSF 集成,遵循以下步驟:

將 struts-faces.jar 文件與特定於 JSF 的 JAR(jsf-api.jar、jsf-ri.jar) 添加到 Web 應用程序的 WEB-INF/lib目錄中。 如果准備使用 JSF 和 JSTL,則將特定於 JSTL 的 JAR(JStl.jar、standard.jar)添加到 WEB-INF/lib 文件夾中。這一步只有在部署到常規 Tomcat 時才會需要。JWSDP 已經提供了這些 JAR。 修改 Web 應用程序部署描述符 ( /WEB-INF/web.XML)以便有一個 Faces Servlet 項, 如清單 5 所示。 修改 JSP 頁面以使用 JSF 和 Struts-Faces 標記而不是 Struts 標記。特別是用 Struts-Faces 相應標記替換 Html、b ase、 form 和 errors 標記。用 JSF 相應標記替換 text 、 textarea 和 radio 標記。Struts-Faces 沒有單獨針對這些的標記。盡管沒有要求,但是您可能還會考慮用 JSTL 標記替換 Struts Logic 標記。 對於每一個使用 JSF 標記的 JSP,修改 struts-config.XML 文件以在指向該 JSP 的 Action Mapping 中的 global-forwards和 local-forwards中加入前綴 /faces。 如果 Web 應用程序使用了任何您創建的自定義組件,那麼您就需要用 JSF 實現的默認 RenderKit 注冊它們。可以通過在 WEB-INF 文件中創建一個 faces-config.xml 文件、並增加每一個組件和 renderer 的項做到這一點。不過,要記住 faces-config.xml 文件已經綁定在 struts-faces.jar 文件中了。您必須從 struts-faces.jar 文件中提出它、加入自己的內容並將它放到 WEB-INF文件夾中。 清單 5. 在 web.XML 中聲明 FacesServlet

facesJavax.faces.webapp.FacesServlet1 faces /faces/*

集成 Struts-Faces 和 Tiles 的挑戰Struts-Faces 庫提供了 Struts 與 JSF 之間的一個高效的橋梁,使得在 J2EE Web 應用程序中擁有豐富的表示層成為現實。您可以通過在組合體中添加 Titles 使表示層更豐富,這樣不僅得到了 Struts 和 JSF 組合的好處,而且還可以高效地重復使用不同的 JSP 頁面,因為它們將由可以根據需要添加或者刪除的組件部分或者 tiles 所構成。

本文已經展示了 Struts 和 JSP 的集成,您會想將 Tiles 加入到組合中只是小事一樁,是不是?

不幸的是,JSF 仍然處於早期階段,還沒有給出最後的發布。基於這一考慮,Struts-Faces 集成軟件開發仍然在不斷地發展以包括 JSF 的不同的功能,並且還沒有支持 Tiles。

Struts 和 Tiles 可以無縫地共同工作,但是在集成之路上您會遇到路障。在下面幾小節中,您會看到在與 Tiles 共同使用 Struts-Faces 集成庫時經常遇到的問題的匯總。對於每一個問題,我們詳細說明了一個修改 Struts-Faces 類的解決方案。我們將用一個航班搜索示例解釋這個解決方案。

清單 6 展示了航班搜索頁面的布局。注意我們稱它為航班搜索頁面而不是 FlightSearch.JSp。這是因為 FlightSearch JSP 是用戶在 foobar 旅行 Web 站點看到的合成頁面的主體。

現在,我們保持實際的 FlightSearch.JSP 不變。我們將隨著進展改變它。在您這邊,也需要用航班搜索頁的定義創建一個 Tiles 定義文件。清單 7(緊接著清單 6)展示了 Tiles 定義文件中航班搜索頁的一項。注意對帶有 extends 屬性的主布局模板的重復使用。

在清單 6 和 7 後是每一個可能的挑戰。

清單 6. 航班搜索例子的 Tiles 布局

<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %>


清單 7. 航班搜索頁的 Tiles 定義

響應已經提交這是您在試圖訪問航班搜索表單時馬上會看到的第一個問題。小心查看堆棧跟蹤。您會看到問題出在類 com.sun.faces.lifecycle.ViewHandlerImpl 上。這是一個實現了 VIEwHandler 接口的 JSF-RI 類。

圖 2展示了 VIEwHandler 所扮演的角色。這是一個將請求轉發給的類。在轉發請求時,它不在轉發前檢查響應的狀態 -- 這只有在使用 Tiles 時才會發生,因為 Tiles 內部將 JSP 頁面包括在響應內,而 JSF-RI 在第一次轉發後提交響應、然後試圖再次轉發給下面的包括 JSP 的 Tiles。

要解決這個問題,必須創建一個自定義的 VIEwHandler 實現,它將檢查響應的狀態以確定它是否提交過。如果響應沒有提交過,那麼請求就轉發給,否則,就加入請求並顯示相應的 JSP。我們將創建一個名為 STFViewHandlerImpl 的類,它實現了 ViewHandler 接口並實現了所需要的方法 renderView()。 清單 8 展示了 STFViewHandlerImpl 中的 renderVIEw() 方法:

清單 8. STFViewHandlerImpl 中的 renderVIEw()方法

RequestDispatcher rd = null;Tree tree = context.getTree();String requestURI = context.getTree().getTreeId();rd = request.getRequestDispatcher(requestURI);/** If the response is committed, include the resource **/if( !response.isCommitted() ) { rd.forward(request, context.getServletResponse());}else { rd.include(request, context.getServletResponse());}

現在您實現了自己的 VIEwHandler ,如何通知 JSF-RI 使用您的 VIEwHandler 而不是默認的實現呢?要回答這個問題,就必須理解 FacesServlet 的工作過程。

在 Faces 初始化過程中, FacesServlet 會讓 LifecycleFactory 實現返回 Lifecycle 類的一個實現,如清單 9 所示:

清單 9. FacesServlet 中 Faces 的初始化

//Get the LifecycleFactory from the Factory FinderLifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");//Get the context param from web.XMLString lifecycleID = getServletContext().getInitParameter("Javax.faces.lifecycle.LIFECYCLE_ID");//Get the Lifecycle ImplementationLifecycle lifecycle = factory.getLifecycle(lifeCycleID);

Lifecycle 實現對象擁有在呈現響應階段要使用的 ViewHandler 。您可以通過對 Lifecycle 實現調用 setViewHandler 方法讓自己的 VIEwHandler 實現成為默認的。

現在問題變為如何得到默認 Lifecycle 實現?回答是不需要這樣做。只要創建一個新的實現並用一個惟一 ID 注冊它,如清單 10 所示:

清單 10. 注冊自定義 VIEwHandler 和 Lifecycle

//Get the LifecycleFactory from the Factory FinderLifecycleFactory factory = (LifecycleFactory) FactoryFinder.getFactory("Javax.faces.lifecycle.LifecycleFactory");//Create a new instance of Lifecycle implementation - //com.sun.faces.lifecycle.LifecycleImpl//According to the documentation, factory.getLifecycle("STFLifecycle") //should work, but JSF-RI has a defect.//Hence this workaround of creating a RI class explicitly.LifecycleImpl stfLifecycleImpl = new LifecycleImpl();//Create a new instance of our STFViewHandler and set it on the LifecyclestfLifecycleImpl.setViewHandler(new STFVIEwHandlerImpl());//Register the new lifecycle with the factory with a unique //name "STFLifecycle"factory.addLifecycle("STFLifecycle", stfLifecycleImpl);

您可以看到 lifecycleId 硬編碼為 STFLifecycle 。實際上不是這樣。當您回過頭分析 清單 9時就會清楚。 FacesServlet 從在 web.XML 文件中聲明的上下文參數中得到名為 Javax.faces.lifecycle.LIFECYCLE_ID 的 lifecycle ID,如下所示:

Javax.faces.lifecycle.LIFECYCLE_ID STFLifecycle

因為 FacesServlet 取決於其初始化時的 Lifecycle 實現,在 清單 10中展示的代碼應該在 FacesServlet 初始化之前執行。通過創建另一個 servlet 並在 FacesServlet 之前初始化它而做到這一點。

但是一種更聰明的辦法是實現一個 ServletContextListener 接口。這個類聲明兩個方法: contextInitialized() 和 contextDestroyed() ,在 Web 應用程序被創建及 Web 應用程序被銷毀之前會分別調用它們。因而 清單 10中的代碼在 contextInitialized() 方法中執行,而自定義 VIEwHandler 已經用標識名 STFLifecycle 注冊到 Lifecycle ,並且可被 FacesServlet 使用。 ServletContextListener 類本身是在 web.XML 文件中聲明的,如下所示:

foo.bar.stf.application.STFContextListener

這不是注冊一個帶有自定義 ViewHandler 的 Lifecycle 惟一方法。事實上 FactoryFinder 實現了自己的發現算法以發現 Factory 對象,包括 LifecycleFactory 。這些機制按照順序包括在系統屬性中查看工廠實現類名的機制、faces.propertIEs file、或者 1.3 Services 發現機制( META-INF/services/{factory-class-name} )。不過,我們討論的這種機制是最容易的,也是最不具有破壞性的一種。

404 Resource Not Found在解決了提交響應的問題後,單擊任何一個 Tiles 特定的鏈接或者輸入一個會呈現 Faces 響應的 URL。在這裡,可以輸入顯示 FlightSearchForm 的 URL。

在這樣做了以後,您會得到一個 foobar.flight-search - 404 Resource Not Found 錯誤。 foobar.flight-search 是航班搜索頁面的 Tiles 定義的名字。 FacesRequestProcessor 不能處理 Tiles 請求(因為它擴展的是 RequestProcessor 而不是 TilesRequestProcessor ),所以會得到錯誤。

為解決這個問題,我們將創建一個名為 STFRequestProcessor (表示 Struts-Tiles-Faces Request Processor)的新的請求處理程序。現在我們將拷貝 FacesRequestProcessor 的所有代碼到這個新類中。惟一的區別是 STFRequestProcessor 繼承的是 TilesRequestProcessor 而不是繼承常規的 RequestProcessor 。這個新的 RequestProcessor 可以處理 Tiles 請求。清單 11 詳細列出了這個 STFRequestProcessor :

清單 11. STFRequestProcessor.Java

正如您所知道的, Struts 框架的 RequestProcessor 是在 struts-config.xml 文件中指定的。將下面的項添加到 struts-cinfig.XML 文件中後, STFRequestProcessor 就成為處理程序:

表單提交顯示返回同一個表單由於 STFRequestProcessor 的作用,這時您就可以浏覽並查看航班頁面了。不過,在提交航班搜索表單時,您會得到返回來的同一個表單,而且沒有頁頭和頁腳!並且沒有驗證錯誤。事實上,根本就沒有進行驗證!

為了了解到底發生了什麼事情,我們用浏覽器回到航班頁面並檢查 Html 源代碼。您會看到像下面這樣的一項:

注意表單 action 是指向 JSP 頁而不是一個 .do 的。啊哈!這就是問題!這不是由於同時使用 Tiles 和 Struts-Faces 而帶來的新問題,Struts-Faces 的默認行為是讓 JSP 與表單 action 有同樣的名字。這種行為在有單一的 JSP 頁(如在前面的 Struts-Faces 例子中)時沒有問題。 清單 3展示了原來的 FlightSearch.JSP,讓我們繼續並像下面這樣修改 action:

FormTag的改變 正如您已經知道的,當組件和 renderer 改變時,標記也必須改變。在這裡,通過繼承 Struts-Faces 中的 FormTag 創建一個新的標記: STFFormTag 。不必改變任何功能,只要覆蓋 getComponentType() 和 getRendererType() 方法。清單 14 展示了從 STFFormComponent 覆蓋的方法:

清單 14. FormTag 的改變

public String getComponentType(){ return ("STFFormComponent");}public String getRendererType(){ return ("STFFormRenderer");}

修改 faces-config.xml 文件自定義組件和 renderer 必須在 faces-config.XML 文件中聲明,這樣 JSF 框架才可以初始化並使用它們。現在我們已經創建了一個新組件 STFFormComponent 和一個新 renderer STFFormRenderer 。

現在我們將在 faces-config.xml 文件中增加一個聲明,如清單 15 所示。 component-class 是組件的完全限定類名。 component-type 指的是在 STFFormTag ( 清單 12)中用於標識組件的名字。以類似的方式發現和解釋 renderer。注意 faces-config.XML 文件是在 struts-faces.jar 文件中的。從 struts-faces.jar 文件中取出這個文件並將它放到 Web 應用程序的 WEB-INF文件夾中並修改它。

清單 15. 在 faces-config.XML 中聲明自定義組件和 renderer

STFFormComponent foobar.stf.component.STFFormComponent .. .. .. STFFormRenderer foobar.stf.renderer.STFFormRenderer.. .. ..

修改 struts-faces.tld 文件您不會在這個示例 Struts-Faces 應用程序中看到 struts-faces.tld 文件,它打包到了 struts-faces.jar 文件中。打開並分析這個文件。它聲明了一個名為 org.apache.struts.faces.taglib.LifecycleListener 的類,這個類實現了 ServletContextListener 並初始化 FacesRequestProcessor 。

因為希望使用新的 STFRequestProccessor ,所以必須將這個文件從 struts-faces.jar 文件中刪除,將它放到 Web 應用程序的 WEB-INF 文件夾中,並刪除偵聽器聲明。如果讓這個 tld 文件保持原樣,那麼在初始化這個 Web 應用程序時,除了 STFRequestProcessor ,還會實例化一個 FacesRequestProcessor。

修改 base href 標記 現在,您已經完成了 Struts、Tiles、JSF 集成的最困難的部分了。您甚至可以浏覽航班搜索頁面,並輸入搜索標准查看航班列表。現在試著從航班列表頁面返回航班搜索表單。您會得到一個 HTTP 400 錯誤。這個錯誤的原因是 Html base href 標記。它被設置為 Master Layout 頁面。

|_________| |_____________________| Context Servlet Path

程序所有頁面浏覽都是相對於布局頁面計算的。如果加入的 base href 標記只達到 Web 應用程序上下文則會很方便,像這樣:

我們可以通過定制 Struts-Faces BaseTag 做到這一點。這個類中的改變相當微不足道。只須在 base href 中去掉 HttpServletRequest.getServletPath() 。

因為這些改變是與顯示相關的,所以為它創建了一個名為 STFBaseRenderer 的新 renderer。這個新標記稱為 STFBaseTag ,它聲明 STFBaseRenderer 作為其關聯的 renderer。不需要新的組件。

有了這些信息,通過繼承 BaseTag 並覆蓋 getRendererType 方法創建新的 STFBaseTag ,如下所示:

public String getRendererType(){ return ("STFBaseRenderer");}

到目前為止所作的改變恭喜!經過這些相對較小的修改,您已經成功地集成了 Struts、Tiles 和 JSF,並保留了您以前在這些技術上所做的所有投資。本文演示了如何將 JSF 強大的前端能力、 Tiles 的內容格式編排優勢以及 Struts 控制器層的靈活性結合在一個包中,使得創建一個 J2EE Web 應用程序成為一項更容易的任務。

我們討論了定制 Struts 類以便與 JavaServer Faces 和 Tiles 框架形成緊密集成的工作關系,包括下面這些修改和增加:

新的 ViewHandler ,用於檢查提交的響應。 新的 ServletContextListener ,用於創建新的 Lifecycle 實現並注冊這個定制的 VIEwHandler。 新的 RequestProcessor ,用於處理 Tiles 請求。 修改過的 web.xml 文件,聲明新的 ServletContextListener 和 JSF Lifecycle ID。 新的 FormTag、 FormComponent 和 FormRenderer 類。 新的 BaseTag 和 BaseRenderer 類。 修改過的 faces-config.XML 文件,它聲明了新的組件和 renderer。 修改過的 struts-faces.tld 文件,不聲明偵聽器。

希望它可以概括本文中使用的復合技術,最重要的是,我們為您提供了將 Struts、Tiles 和 JavaServer Faces 結合到用於構建 Web 應用程序的一個強大而靈活的機制中的一個令人信服的路線圖。

參考資料

您可以參閱本文在 developerWorks 全球站點上的 英文原文. 下載本文的 例子和代碼,並遵循 README.txt 中給出的編譯和部署的說明。 Ant 用於對例子進行編譯,可以從 Apache Ant 項目Web 站點下載它。 有關 Struts 和 Tiles 的更多內容,包括可下載的教程、文檔、二進制文件和源代碼,可從 apache Jakarta Project StrutsWeb 站點獲得。 可以將 JSF Early Acess Release 4 (EA4) 作為 Java Web Services Developer Pack Version 1.2 的一部分下載 -- 它帶有自己版本的 Tomcat。 可以從 Jakarta 站點下載 Struts-Faces integration library的 0.3 和 0.4 版本。 可以從 Java Web Services Developer Pack 1.2 下載 JSF-RI。 “ Struts, an open-source MVC implementation”( developerWorks, 2001年2月)介紹了 Struts,這是一個使用了 servlets 和 JavaServer Pages 技術的模型-視圖-控制器實現。 “ Struts and Tiles aid component-based development”( developerWorks,2002年6月)解釋了為什麼結合 Struts 和 Tiles 可以成為創建 Web 應用程序的出色軟件包,並展示了如何使用它,側重於 Struts 0.9 之後的改變。 “ Struttin' your stuff with WebSphere Studio Application Developer, Part 2: Tiles” ( developerWorks,2002年11月)是一個教程,主要關注在使用 WebShpere Studio Application Developer 作為開發環境時結合 Struts 使用 Tiles 模型框架。 “ Architect Struts applications for Web services”( developerWorks,2003年4月)展示了如何基於 MVB 設計模式用 Struts 建立 Web 服務應用程序。 “ A JSTL primer”( developerWorks,2003年2-5月),這是一個分為四部分的系列,提供了有關 JSTL 的所有內容,包括如何使用 JSTL 標記以避免在 JSP 頁面中使用腳本元素、如何通過刪除表示層中的代碼簡化軟件維護、以及 JSTL 的簡化的表達式語言,它使得無需使用全功能的編程語言就可以為 JSTL action 指定動態屬性值。 學習用 JSF 開發 Web 應用程序的基本內容。在其教程“ UI development with JavaServer Faces” ( developerWorks,2003年9月)中,Jackwind Li GuojIE 探討了 JSF 生命周期、輸入驗證、事件處理、頁面浏覽和國際化。 Sun 的 JSF Web 站點是另一個很好的學習有關 JavaServer Faces 技術的起點。 ServerSide.com J2EE 社區是查找有關 J2EE 的資源及參加開發者論壇的理想地點。 在 Java Community Process站點可以迅速得到有關 JavaServer Pages 1.2 規范的內容。 在 developerWorks Java 技術專區 可以找到關於 Java 編程各方面的數百篇文章。

作者簡介Srikanth Shenoy 專門從事大型 J2EE 和 EAI 項目的體系結構、設計、開發和部署工作。Srikanth 已經幫他的制造業、物流業和金融業客戶實現了 Java 平台“一次編寫,隨處運行”的夢想。他是 Sun 認證的企業架構師,並且是即將出版的 Practical Guide to J2EE Web Projects一書的作者之一。可以通過 [email protected]與他聯系。

Nithin Mallya 專門為金融客戶提供企業級解決方案。他有七年架構和開發服務器端解決方案的經驗,大多數是 Java 平台。他是 Sun 認證的企業架構師,並且是 Sun 認證的 Web 組件開發者。他也是即將出版的 Practical Guide to J2EE Web Projects一書的作者之一。可以通過 [email protected] 與他聯系。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved