將三種技術(shù)的功能、靈活性和可管理性集成到一起
您是否想將 JavaServer Faces (JSF)的強(qiáng)大前端功能、Tiles 的內(nèi)容格式編排優(yōu)勢(shì)和 Struts controller 層的靈活性都加入到您的J2EE Web 應(yīng)用程序中?企業(yè)級(jí) Java 專(zhuān)家 Srikanth Shenoy 和 Nithin Mallya 為您展示了如何將這三者的功能集成到一起。本文演示了如何在 Struts-Faces集成庫(kù)中定制類(lèi)以使得它們可以與 Tiles 和 JSF 一同使用,并用一個(gè)實(shí)際的例子解釋了這個(gè)過(guò)程背后的基本原理以及如何使用新的一組類(lèi)的細(xì)節(jié)。
將 Struts、Tiles 和 JavaServer Faces (JSF) 一起使用,開(kāi)發(fā)人員可以實(shí)現(xiàn)易于管理和重用的、健壯的、界面清晰的 Web 應(yīng)用程序。
Struts 框架推出已經(jīng)有一段時(shí)間了,它已經(jīng)成為在開(kāi)發(fā) J2EE Web 應(yīng)用程序時(shí)開(kāi)發(fā)人員所采用的事實(shí)上的標(biāo)準(zhǔn)。Tiles 框架是在 Struts 之后不久出現(xiàn)的,它通過(guò)為開(kāi)發(fā)人員提供用組件組裝展示頁(yè)面的能力開(kāi)拓了自己的生存環(huán)境。JSF 是 Web 應(yīng)用程序框架中最新的成員,它提供了驗(yàn)證用戶(hù)輸入和處理用戶(hù)事件的機(jī)制,最重要的是,這是一種以協(xié)議無(wú)關(guān)的方式呈現(xiàn)用戶(hù)界面組件的方法(有關(guān)這些 技術(shù)的概況,參見(jiàn)本文相關(guān)頁(yè)面“ The major players”)。
盡管 Struts 和 JSF 中有一些功能是重疊的,但是它們?cè)谄渌矫嫫鸬搅嘶檠a(bǔ)充的作用。這三種技術(shù)的結(jié)合可以為開(kāi)發(fā) Web 應(yīng)用程序、組織其展示和以協(xié)議無(wú)關(guān)的方式呈現(xiàn)定制的用戶(hù)界面(UI)組件提供一種高效的途徑。
為了運(yùn)行本文中的示例代碼,需要 Struts 1.1、Tiles、JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0 以及 Struts-Faces 0.4。Jakarta 項(xiàng)目提供的 Struts 1.1 發(fā)行版本將 Struts 和 Tiles 捆綁發(fā)布。還可以從 Jakarta 項(xiàng)目上下載 Struts-Faces 集成庫(kù)。JSF-RI 是 Sun 的 Web 開(kāi)發(fā)工具包(Web Services Developer Pack)的一部分(在 參考資料中有這些下載和示例代碼的鏈接)。
現(xiàn)在回到集成三種技術(shù)的細(xì)節(jié)上。首先有個(gè)壞消息:在本文發(fā)表的時(shí)候,這三種技術(shù)是不能直接互操作的。好消息是:在本文中,我們展示了集成 Struts、Tiles 和 JSF 的方法。我們假設(shè)您已經(jīng)了解 Struts 和 Tiles。對(duì) JSF 有一些了解會(huì)有幫助(參閱 參考資料中提供的 developerWorks 上的 JSF 教程的鏈接),但是不了解也不妨礙對(duì)本文的理解。
JSF 簡(jiǎn)介
JSF 應(yīng)用程序是使用 JSF 框架的普通 J2EE Web 應(yīng)用程序,JSF 框架提供了豐富的 GUI 組件模型,這些模型體現(xiàn)了真正的 GUI 框架內(nèi)涵。您可能聽(tīng)人們說(shuō)過(guò),盡管某種技術(shù)不錯(cuò),但是它的外觀仍然需要改進(jìn)。是的,用 HTML 組件構(gòu)建平淡無(wú)奇的頁(yè)面的日子已經(jīng)過(guò)去了,如果使用 JSF 的話(huà),具有更高級(jí) GUI 外觀的日子就在眼前。您會(huì)問(wèn),怎么做呢?樹(shù)形組件、菜單組件和圖形是已經(jīng)存在的 UI 組件,這些 JSF 一定要提供。更進(jìn)一步,JSF 通過(guò)提供容易使用的 API 鼓勵(lì)創(chuàng)建自定義組件。
注: 這里所提到的 UI 組件是 Sun 提供的示例的一部分。像所有規(guī)范一樣,實(shí)際的實(shí)現(xiàn)由不同的提供商完成。
在傳統(tǒng)的使用模型-視圖-控制器(MVC)的 Web 應(yīng)用程序中,GUI 組件是由處理展示和業(yè)務(wù)邏輯的自定義標(biāo)記所表示的。這樣就出現(xiàn)了必須“編寫(xiě)與客戶(hù)機(jī)設(shè)備打交道的代碼”的問(wèn)題,這會(huì)產(chǎn)生重復(fù)的代碼。使用 JSF 就不會(huì)有這個(gè)問(wèn)題。
JSF 結(jié)構(gòu)將 展示邏輯 (“什么”)與 UI 組件的 業(yè)務(wù)邏輯 (“為什么”和“如何”)分離。通過(guò)在 JSP 頁(yè)面中使用 JSF 標(biāo)記,就可以將 renderer 與 UI 組件關(guān)聯(lián)在一起。一個(gè) UI 組件可以用不同的 renderer 從而以不同的方式呈現(xiàn)。特定于 UI 組件的代碼在服務(wù)器上運(yùn)行,并且響應(yīng)用戶(hù)操作所產(chǎn)生的事件。
JSF-RI 提供了一個(gè) render kit,它帶有一個(gè)自定義標(biāo)記庫(kù),用以從 UI 組件呈現(xiàn) HTML。它還提供了根據(jù)需要定制這些組件外觀的能力。如果需要特殊的組件,那么可以為特定的客戶(hù)機(jī)設(shè)備構(gòu)造定制的標(biāo)記并讓它與一個(gè)子 UI 組件和定制的 renderer 相關(guān)聯(lián)。對(duì)于不同的設(shè)備,您所需要做的就是指定不同的 renderer。
JSF 和 UI 組件
您可能已經(jīng)用 Java AWT 或者 Swing API 創(chuàng)建過(guò) Java GUI 應(yīng)用程序,所以您應(yīng)該熟悉 JSF 的 UIComponent
(它與 AWT 或者 Swing 組件很相像)。它儲(chǔ)存其子組件的樹(shù)(如果有的話(huà))并為客戶(hù)端發(fā)生的動(dòng)作生成標(biāo)準(zhǔn)事件,例如單擊一個(gè)按鈕以提交表單。這些事件緩存在 FacesContext
中。您可以用自定義標(biāo)記關(guān)聯(lián)每一個(gè)這種事件的處理程序。例如,用一個(gè)自定義的 ActionListener
處理用戶(hù)單擊或者表單提交。
JSF UIComponent
、 Renderer
和標(biāo)記總是共同工作的。所有 JSP 自定義標(biāo)記都是通過(guò)繼承 UIComponentTag
創(chuàng)建的。 doStart
和 doEnd
方法總是在 UIComponentTag
類(lèi)中實(shí)現(xiàn)。您只需在這些標(biāo)記類(lèi)中提供其他的功能。
圖 1展示了自定義標(biāo)記、UI 組件和 renderer 之間的關(guān)系。客戶(hù)機(jī)瀏覽器訪(fǎng)問(wèn)用 JSF 標(biāo)記( jsf:myTag
)表示 UI 組件( MyComponent
)的 JSP 頁(yè)面。這個(gè) UI 組件運(yùn)行在服務(wù)器上,并用適當(dāng)?shù)?renderer ( MyRenderer
)以 HTML 的形式呈現(xiàn)給客戶(hù)。這個(gè) JSP 頁(yè)面表現(xiàn)了在 JSF-RI 中使用帶自定義標(biāo)記的用戶(hù)界面組件而不是在 HTML 中對(duì)它們進(jìn)行編碼。
例如,圖 1 展示了 h:panel:group
標(biāo)記的使用。這個(gè)標(biāo)記用于將一個(gè)父組件下面的各個(gè)組件組織到一起。如果與像 panel_grid
和 panel_data
這樣的其他面板標(biāo)記共同使用,那么它會(huì)在運(yùn)行時(shí)生成 HTML 表中的列的標(biāo)記。JSF-RI-提供的 html_basic 標(biāo)記庫(kù)用于表示像文本字段、按鈕這樣的 HTML 組件。
圖1. 呈現(xiàn)一個(gè) JSF 頁(yè)面
JSF 生命周期
JSF 生命周期包括六個(gè)階段:一個(gè)傳入的請(qǐng)求可能會(huì)經(jīng)歷全部階段,也可能不經(jīng)歷任何階段,這取決于請(qǐng)求的類(lèi)型、在生命周期中發(fā)生的驗(yàn)證和轉(zhuǎn)換錯(cuò)誤以及響應(yīng)的類(lèi)型。JSF 框架處理由 JSP 頁(yè)生成的 Faces 請(qǐng)求,并返回 faces或者 non-faces 響應(yīng)。
在提交一個(gè) JSF 表單,或者當(dāng)用戶(hù)單擊指向在 URL 中具有 /faces 前綴的 URL 的鏈接時(shí),就會(huì)出現(xiàn) faces 響應(yīng)。所有 faces 請(qǐng)求都由一個(gè) FacesServlet
處理 -- 這是 JSF 中的控制器。
發(fā)送給一個(gè) servlet 或者一個(gè)沒(méi)有 JSF 組件的 JSP 頁(yè)面的請(qǐng)求稱(chēng)為 non-faces 請(qǐng)求。如果結(jié)果頁(yè)中有 JSF 標(biāo)記,那么它就稱(chēng)為 faces 響應(yīng),如果沒(méi)有 JSF 標(biāo)記,就是 non-faces 響應(yīng)。
JSF 生命周期有六個(gè)階段:
- 重建請(qǐng)求樹(shù)
- 應(yīng)用請(qǐng)求值
- 進(jìn)行驗(yàn)證
- 更新模型值
- 調(diào)用應(yīng)用程序
- 呈現(xiàn)響應(yīng)
根據(jù) JSF 規(guī)范,每一階段表示請(qǐng)求處理生命周期的一個(gè)邏輯概念。不過(guò)在 JSF-RI 中,這些階段是由具有對(duì)應(yīng)名字的實(shí)際類(lèi)表示的。下面一節(jié)描述了每一階段是如何對(duì)請(qǐng)求進(jìn)行處理并生成響應(yīng)的。您將首先看到的是處理一個(gè) faces 請(qǐng)求所涉及的階段,然后是處理 faces 響應(yīng)所涉及的階段。
處理 faces 請(qǐng)求
為了理解 JSF 請(qǐng)求處理,請(qǐng)看 FlightSearch.jsp,這是 清單 1中的一個(gè)簡(jiǎn)單的 JSF 表單。一個(gè) JSF 頁(yè)面基本上就是這個(gè)樣子的。這個(gè) JSF 表單有輸入文本字段 from和 to cities、 departure 和 return dates,還有提交和重設(shè)表單的按鈕(我們會(huì)在稍后分析清單1中每一個(gè)標(biāo)記的意義)。現(xiàn)在,假設(shè)提交這個(gè)表單產(chǎn)生了一個(gè) faces 請(qǐng)求。
這個(gè)請(qǐng)求被 FacesServlet
所接收、并在向客戶(hù)發(fā)回響應(yīng)之前通過(guò)不同的階段。 圖 2展示了如何對(duì) JSF 請(qǐng)求進(jìn)行處理。讓我們看一看這是如何進(jìn)行的。
1. 接收請(qǐng)求
FacesServlet
接收請(qǐng)求并從 FacesContextFactory
得到 FacesContext
的一個(gè)實(shí)例。
2. 委托生命周期處理
FacesServlet
通過(guò)對(duì)在 faces 上下文中傳遞的 Lifecycle
實(shí)現(xiàn)調(diào)用 execute
方法將生命周期處理委托給 Lifecycle
接口。
3. Lifecycle 執(zhí)行每一階段
Lifecycle
實(shí)現(xiàn)執(zhí)行從重建組件樹(shù)階段開(kāi)始的每一階段。
4. 創(chuàng)建的組件樹(shù)
在重建組件樹(shù)階段,用 travelForm
中的組件創(chuàng)建一個(gè)組件樹(shù)。這個(gè)樹(shù)以 UIForm
作為根,用不同的文本字段和按鈕作為其子組件。
fromCity
字段有一個(gè)驗(yàn)證規(guī)則,它規(guī)定其不能為空,如 validate_required
標(biāo)記所示。這個(gè)標(biāo)記將 fromCity
文本字段與一個(gè) JSF Validator
鏈接起來(lái)。
JSF 有幾個(gè)內(nèi)建的驗(yàn)證器。相應(yīng)的 Validator
是在這個(gè)階段初始化的。這個(gè)組件樹(shù)緩存在 FacesContext
中、并且這個(gè)上下文會(huì)在后面用于訪(fǎng)問(wèn)樹(shù)及調(diào)用任何一個(gè)事件處理程序。同時(shí) UIForm
狀態(tài)會(huì)自動(dòng)保存。所以,當(dāng)刷新這一頁(yè)時(shí),就會(huì)顯示表單的原始內(nèi)容。
5. 從樹(shù)中提取值
在應(yīng)用請(qǐng)求值階段,JSF 實(shí)現(xiàn)遍歷組件樹(shù)并用 decode
方法從請(qǐng)求中提取值,并在本地設(shè)置每一個(gè)組件。如果在這個(gè)過(guò)程中出現(xiàn)了任何錯(cuò)誤,那么它們就在 FacesContext
中排隊(duì)并在呈現(xiàn)響應(yīng)階段顯示給用戶(hù)。
同時(shí),在這個(gè)階段排隊(duì)的所有由像單擊按鈕這樣的用戶(hù)操作產(chǎn)生的事件,都廣播給注冊(cè)的偵聽(tīng)器。單擊 reset 按鈕會(huì)將文本字段中的值重新設(shè)置為它們?cè)瓉?lái)的值。
6. 處理驗(yàn)證
在處理驗(yàn)證階段,對(duì)在應(yīng)用請(qǐng)求值階段設(shè)置的本地值進(jìn)行所有與各組件相關(guān)的驗(yàn)證。當(dāng) JSF 實(shí)現(xiàn)對(duì)每一個(gè)注冊(cè)的驗(yàn)證器調(diào)用 validate
方法時(shí)就會(huì)進(jìn)入此階段。
如果任何一項(xiàng)驗(yàn)證失敗,那么生命周期就會(huì)進(jìn)入呈現(xiàn)響應(yīng)階段,在那里呈現(xiàn)帶有錯(cuò)誤信息的同一頁(yè)面。在這里,所有在這一階段排隊(duì)的事件同樣都會(huì)廣播給注冊(cè)的偵聽(tīng)器。
JSF 實(shí)現(xiàn)處理源字段上的驗(yàn)證器。如果數(shù)據(jù)是無(wú)效的,那么控制就交給呈現(xiàn)響應(yīng)階段,在這個(gè)階段重新顯示 FlightSearch.jsp 并帶有相關(guān)組件的驗(yàn)證錯(cuò)誤。通過(guò)在 JSP 頁(yè)面中聲明 output_errors,
,頁(yè)面中的所有錯(cuò)誤都會(huì)顯示在頁(yè)面的底部。
7. 設(shè)置模型對(duì)象值
在更新模型值階段,成功處理了所有驗(yàn)證后,JSF 實(shí)現(xiàn)就通過(guò)對(duì)每一組件調(diào)用 updateModel
方法用有效值設(shè)置模型對(duì)象值。如果在將本地?cái)?shù)據(jù)轉(zhuǎn)換為由模型對(duì)象屬性所指定的類(lèi)型時(shí)出現(xiàn)任何錯(cuò)誤,那么生命周期就進(jìn)入呈現(xiàn)響應(yīng)階段,并將錯(cuò)誤顯示出來(lái)。來(lái)自表單字段屬性的值會(huì)填充為模型對(duì)象的屬性值。
8. 可以調(diào)用 ActionListener
可以將一個(gè) ActionListener
與一個(gè)用戶(hù)操作,如單擊提交按鈕相關(guān)聯(lián),如 清單 1所示。在調(diào)用應(yīng)用程序階段,對(duì) FlightSearchActionListener
調(diào)用了 processAction
方法。在實(shí)際應(yīng)用中, processAction
方法在調(diào)用后會(huì)搜索數(shù)據(jù)以找出滿(mǎn)足條件的航班,并從組件的 action 屬性中提取輸出。
在本文提供的這個(gè)示例 Web 應(yīng)用程序中,我們使用了靜態(tài)數(shù)據(jù)表示航班表。這個(gè)方法還將提取的 action 屬性發(fā)送給 NavigationHandler
實(shí)現(xiàn)。 NavigationHandler
查詢(xún) faces-config.xml 文件 -- 這是 JSF 的默認(rèn)應(yīng)用程序配置文件 -- 以根據(jù)這一輸出確定下一頁(yè)是什么。
9. 呈現(xiàn)響應(yīng) 在呈現(xiàn)響應(yīng)階段,如果在 faces 上下文中沒(méi)有錯(cuò)誤,就顯示由查詢(xún)配置文件得到的這一頁(yè) FlightList.jsp。如果是因?yàn)榍懊嫒我浑A段的錯(cuò)誤而到達(dá)這一階段的,那么就會(huì)重新顯示帶有錯(cuò)誤信息的 FlightSearch.jsp。
圖 2. 處理一個(gè) JSF 請(qǐng)求
單擊這里以觀看該圖。
清單 1. FlightSearch.jsp,一個(gè)簡(jiǎn)單的 JSF 表單
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<f:use_faces>
<h:form id="flightForm" formName="flightForm" >
<h:input_text id="fromCity" valueRef="FlightSearchBean.fromCity">
<f:validate_required/>
<h:input_text/>
<h:input_text id="toCity" valueRef="FlightSearchBean.toCity">
<h:input_text id="departureDate"
valueRef="FlightSearchBean.departureDate">
<h:input_text id="arrivalDate"
valueRef="FlightSearchBean.arrivalDate">
<h:command_button id="submit" action="success"
label="Submit" commandName="submit" >
<f:action_listener
type="foo.bar.FlightSearchActionListener"/>
</h:command_button>
<h:command_button id="reset" action="reset" label="Reset"
commandName="reset" />
<h:output_errors/>
</h:form>
</f:use_faces>
|
在這段代碼中使用了兩個(gè) JSF-RI 的標(biāo)記庫(kù)。 html_basic 標(biāo)記庫(kù)定義了 HTML 組件常用的標(biāo)記,而 jsf-core 標(biāo)記庫(kù)包含用于注冊(cè)偵聽(tīng)器和驗(yàn)證器的標(biāo)記。其他標(biāo)記有:
f:use_faces
標(biāo)記向 JSF 實(shí)現(xiàn)表明后面的標(biāo)記是 faces 標(biāo)記。
f:validate_required
標(biāo)記表明它所在的字段(在 FlightSearchBean
中是 fromCity
字段)在提交表單時(shí)應(yīng)該有值。
h:form
和 h:input_text
標(biāo)記分別表示一個(gè)名為 flightSearchForm
的 HTML 表單和各種文本字段。
h:command_button
標(biāo)記用于表示提交和重設(shè)按鈕。
- 最后,
h:output_errors
標(biāo)記類(lèi)似于 Struts html:errors
標(biāo)記,用于顯示在表單字段驗(yàn)證中出現(xiàn)的任何錯(cuò)誤。
一個(gè)名為 FlightSearchBean 的 JavaBean 表示在更新模型值階段用 UIComponent
數(shù)據(jù)更新的模型。通常在 JSP 頁(yè)中 JavaBean 是用 jsp:useBean
標(biāo)記聲明的。您可能注意到了在 FlightSearch.jsp 中沒(méi)有這樣做。這是因?yàn)榭梢允褂?JSF 的一個(gè)名為 Managed Beans 的功能,在 faces 配置文件中聲明所有 JSP 頁(yè)面使用的 JavaBeans 組件。在開(kāi)始時(shí),servlet 容器會(huì)初始化這些 JavaBeans 組件。faces-config.xml 文件中的 FlightSearchBean 入口如清單 2所示:
清單 2. faces-config.xml 的 TravelInfoBean入口
<managed-bean>
<managed-bean-name>FlightSearchBean</managed-bean-name>
<managed-bean-class>
foo.bar.FlightSearchBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
|
現(xiàn)在讓我們看一看這些階段是如何處理響應(yīng)的。
呈現(xiàn) faces 響應(yīng)
一個(gè) faces 響應(yīng)是由 Faces 應(yīng)用程序在生成包含 JSF 標(biāo)記的 JSP 頁(yè)時(shí)生成的。這個(gè)響應(yīng)可以是 JSF 應(yīng)用程序的 faces 或者 non-faces 響應(yīng)。
在我們的例子中,清單 1 中頁(yè)面的呈現(xiàn)是一個(gè) faces 響應(yīng)。您可能熟悉 Tag
接口的 doStartTag()
和 doEndTag()
方法。在 JSF 和 Struts-Faces 中,每一個(gè)標(biāo)記都是從 UIComponentTag
擴(kuò)展的。 UIComponentTag
實(shí)現(xiàn)了 doStartTag()
和 doEndTag()
方法。
它還有兩個(gè)抽象方法 getComponentType()
和 getRendererType()。
通過(guò)在具體的標(biāo)記類(lèi)中實(shí)現(xiàn)這兩個(gè)方法,就可以分別指定組件和 renderer 的類(lèi)型。
考慮一個(gè)帶有文本字段的簡(jiǎn)單 JSF 表單。在呈現(xiàn) JSF 表單時(shí)執(zhí)行以下一系列步驟。
1. 調(diào)用 doStartTag() 方法
Servlet 窗口對(duì) FormTag
調(diào)用 doStartTag()
方法。
2. 得到 UIComponent
FormTag
從 getComponentType()
方法得到其 UIComponent。
UIComponentTag
( FormTag
的父組件)使用 getComponentType()
以從 faces-config.xml 文件中查詢(xún)這個(gè)組件的類(lèi)名,并創(chuàng)建 UIComponent(FormComponent
)的一個(gè)實(shí)例。
3. 得到 renderer
下一步, FormTag
從 getRendererType
方法中得到其 renderer 。與組件類(lèi)型一樣,renderer 名是在 faces-config.xml 文件中查詢(xún)的。
4. 調(diào)用編碼方法
在創(chuàng)建了 FormComponent
和 FormRenderer
后,對(duì) FormComponent
調(diào)用 encodeBegin()
方法。每一個(gè)標(biāo)記的呈現(xiàn)都由 encodeBegin()
開(kāi)始、由 encodeEnd()
結(jié)束。 encodeBegin()
方法是按嵌套的順序調(diào)用的。
5. 結(jié)束標(biāo)記和呈現(xiàn) HTML
servlet 容器對(duì)標(biāo)記調(diào)用 doEndTag()
方法。以嵌套的反順序?qū)γ恳粋€(gè)組件調(diào)用 encodeEnd()
方法。在最后,表單和所有嵌套的組件都呈現(xiàn)為 HTML。這時(shí),HTML 就生成完畢,并呈現(xiàn)出對(duì)應(yīng)于 JSP 的 HTML。
圖 3 顯示構(gòu)成生成 faces 響應(yīng)的事件序列。
圖 3. 呈現(xiàn)一個(gè) faces 響應(yīng)
單擊這里以查看該圖。
為什么將這三者集成為一體?
隨著 JSP 和相關(guān)規(guī)范的不斷發(fā)展,像 JSF 和 JSP 標(biāo)記庫(kù)(或者 JSTL,它使用簡(jiǎn)單的標(biāo)記封裝許多 JSP 應(yīng)用程序常用的核心功能)這樣的新標(biāo)準(zhǔn)正在不斷出現(xiàn)。下面是使用集成為一個(gè)整體的新技術(shù)一些好處:
- 更清晰地分離行為和展示。 將標(biāo)記、 renderer 和組件分離,就可以更好地定義開(kāi)發(fā)周期中的頁(yè)面作者和應(yīng)用程序開(kāi)發(fā)人員的作用。
- 改變一個(gè)組件的展示不會(huì)有雪崩效應(yīng)。現(xiàn)在您可以容易地只對(duì) renderer 作出改變。在傳統(tǒng)的 MVC 模型中,由于沒(méi)有這種分離,對(duì)于標(biāo)記的任何改變都需要改變業(yè)務(wù)邏輯。現(xiàn)在再不需要這樣了。
- renderer 無(wú)關(guān)性。 也可以說(shuō)是協(xié)議無(wú)關(guān)性,通過(guò)對(duì)帶有多個(gè) renderer 的多種展示設(shè)備重復(fù)使用組件邏輯實(shí)現(xiàn)。使用不同 renderer 的能力使得不再需要對(duì)特定的設(shè)備編寫(xiě)整個(gè)表示層代碼。
- 組裝和重用自定義組件的標(biāo)準(zhǔn)。JSF 的考慮范圍超出了“表單和字段”,它提供了豐富的組件模型用以呈現(xiàn)自定義 GUI 組件。用 JSF 可以定制每一個(gè)組件在頁(yè)面中的外觀和行為。開(kāi)發(fā)人員還擁有創(chuàng)建他們自己的 GUI 組件(如菜單和樹(shù))的能力,這些組件可以用簡(jiǎn)單的自定義標(biāo)記容易地加入到任何 JSP 頁(yè)面中。就像 AWT 和 Swing 所提供的 Java 前端 GUI 組件一樣,我們可以在我們的 Web 頁(yè)而中有自定義的組件,它們使用自己的事件處理程序并有定制的外觀。這是 Web 層的 GUI 天堂!
Struts 是一種已經(jīng)擁有大量客戶(hù)基礎(chǔ)的框架。許多 IT 部門(mén)認(rèn)識(shí)到這種 MVC 框架的價(jià)值并使用它有一段時(shí)間了。JSF 沒(méi)有像 Structs 這樣強(qiáng)大的控制器結(jié)構(gòu),也沒(méi)有像它那樣標(biāo)準(zhǔn)化的 ActionForm
和 Actions
(及它們聲明的能力)。將 Tiles 集成到集合體中,就給了自己重復(fù)使用和以無(wú)縫的方式改變公司布局的能力。
移植支持 JSF 的 Struts 應(yīng)用程序的挑戰(zhàn)是雙重的。首先,Struts 標(biāo)記不是 JSF 兼容的。換句話(huà)說(shuō),它們沒(méi)有像 JSF 規(guī)范所規(guī)定的那樣擴(kuò)展 UIComponentTag
,所以,JSF 不能解釋它們并關(guān)聯(lián)到 UIComponent
和 Renderers
。
其次,在 FacesServlet
與 Struts RequestProcessor
之間沒(méi)有鏈接。在 Struts 應(yīng)用程序中, RequestProcessor
負(fù)責(zé)用 ActionForm
和 Actions
類(lèi)中的回調(diào)方法顯示。 ActionForm
屬性和 validate()
的 getter 和 setter 是 ActionForm
中的回調(diào)方法。對(duì)于 Action
, execute()
是回調(diào)方法。除非調(diào)用了 RequestProcessor
,否則 Struts ActionForm
和 Actions
類(lèi)中的回調(diào)方法沒(méi)有機(jī)會(huì)調(diào)用業(yè)務(wù)邏輯。
將 Struts 和 JSF 與 Struts-Faces 集成
這里,您可能會(huì)問(wèn)是否有軟件可以幫助將 Struts 與 JSF 集成,或者是否必須自己編寫(xiě)集成軟件。
好消息是已經(jīng)有這樣的軟件了。 Struts-Faces 是一個(gè)早期發(fā)布的 Struts JSF 集成庫(kù)。這個(gè)庫(kù)是由 Craig McClanahan 創(chuàng)建的,它使得將現(xiàn)有 Struts 應(yīng)用程序移植到 JSF 變得容易了(保留了對(duì)現(xiàn)有 Struts 投資的價(jià)值)。Struts-Faces 還力圖與 JSF 進(jìn)行簡(jiǎn)潔的集成,這樣就可以在前端使用 JSF,同時(shí)后端仍然有熟悉的 Struts 組件。
圖 4 展示了 Struts-Faces 與 JSF 類(lèi)之間的關(guān)系。藍(lán)色的類(lèi)屬于 Struts-Faces。
圖 4. Struts-Faces 類(lèi)圖
單擊這里以查看該圖。
下面是 Struts-Faces 的主要組件:
FacesRequestProcessor
類(lèi),它處理所有 faces 請(qǐng)求。這個(gè)類(lèi)繼承了常規(guī) Struts RequestProcessor
,并處理 faces 請(qǐng)求。Non-faces 請(qǐng)求發(fā)送給出其父類(lèi) -- RequestProcessor
。
ActionListenerImpl
類(lèi),它處理像提交表單或者單擊鏈接這樣的 ActionEvent
。這個(gè)類(lèi)用于代替由 JSF-RI 提供的默認(rèn) ActionListener
實(shí)現(xiàn)。只要在一個(gè) faces 請(qǐng)求中生成 ActionEvent
,就會(huì)對(duì) ActionListenerImpl
調(diào)用 processAction()
方法、并將 ActionEvents
轉(zhuǎn)送給 FacesRequestProcessor
。這很有意思,因?yàn)?RequestProcessor
通常只由 ActionServlet
調(diào)用以處理 HTTP 請(qǐng)求。
FormComponent
類(lèi),它擴(kuò)展了 JSF Form 組件,但是是在 Struts 生命周期內(nèi)調(diào)用的。
FormComponent
的 renderer 和標(biāo)記。
- 只用于輸出的數(shù)據(jù)標(biāo)記和 renderer ,這里不需要分離組件。例如,
ErrorsTag
和 ErrorsRenderer
用于在 HTML 中顯示表單錯(cuò)誤。
ServletContextListener
的名為 LifeCycleListener
的實(shí)現(xiàn)。它用于在初始化時(shí)注冊(cè)相應(yīng)的 RequestProcessor
。
- faces-config.xml 文件。這個(gè)文件已經(jīng)捆綁在 struts-faces.jar 文件中。
清單 3 展示了使用 Struts-Faces 標(biāo)記的 FlightSearch.jsp。它類(lèi)似于在 清單 1中展示的 JSF 例子。這里用粗體突出了區(qū)別之處。在這里,您會(huì)發(fā)現(xiàn)增加了一個(gè)新標(biāo)記庫(kù) tags-faces。這個(gè)標(biāo)記庫(kù)定義聲明這些標(biāo)記由 Struts-Faces API 所使用。
清單 3. FlightSearch.jsp 使用 Struts-Faces 標(biāo)記
<%@ 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" %>
<f:use_faces>
<s:form action="/listFlights">
<h:input_text id="fromCity" valueRef="FlightSearchForm.fromCity"/>
<h:input_text id="toCity" valueRef="FlightSearchForm.toCity"/>
<h:input_text id="departureDate"
valueRef="FlightSearchForm.departureDate">
<h:input_text id="arrivalDate"
valueRef="FlightSearchForm.arrivalDate">
<h:command_button id="submit" action="success" label="Submit"
commandName="submit" />
<h:command_button id="reset" action="reset" label="Reset"
commandName="reset" />
<s:errors/>
</s:form>
</f:use_faces>
|
s:form
標(biāo)記用于創(chuàng)建這個(gè) HTML 表單。表單的 action 屬性是 /listFlights而不是像 清單 1那樣指定為表單名 flightForm。在 JSF 中,表單名只是指定給 UIForm 的名字而沒(méi)有更多的意義。
FlightSearchBean
是 JSF 表單的模型,并在更新模型值階段得到其值。不過(guò)在 Struts 中,表單 action 指向 Struts 配置文件 struts-config.xml 中的 ActionMapping
。為了理解它是如何起作用的,還必須看一下清單 4 中顯示的 struts-config.xml 文件。
您會(huì)看到 /listFlights 的 ActionMapping
表明這個(gè) URI 路徑的 ActionForm
是 foo.bar.FlightSearchForm
,而 Action
類(lèi)是 foo.bar.FlightSearchAction
。換句話(huà)說(shuō), ActionForm
( FlightSearchForm
)本身就是 Struts-Faces 中的 HTML 表單的模型,它的 action 間接地指向這個(gè)模型(您可以在清單 3 中看到這一點(diǎn),那里文本字段標(biāo)記指向 FlightSearchForm
。在普通 Struts 應(yīng)用程序中這會(huì)是 <html:text property="fromCity"/>
)。
清單 4. 在 struts-config.xml 中聲明 Action
<form-bean name="FlightSearchForm"
type="foo.bar.FlightSearchForm"/>
<!-- ========== Action Mapping Definition ========================= -->
<action-mappings>
<!-- List Flights action -->
<action path="/listFlights"
type="foo.bar.FlightSearchAction"
name="FlightSearchForm"
scope="request"
input="/faces/FlightSearch.jsp">
<forward name="success" path="/faces/FlightList.jsp"/>
</action>
</action-mappings>
|
您會(huì)注意到在 action 屬性中缺少熟悉的 .do。這是因?yàn)?Struts-Faces 使用表單 action 本身作為表單名(它還應(yīng)該與 Struts 配置文件中的 ActionForm
名相匹配)。
集成 Struts 和 Tiles 的五個(gè)步驟 以下五步可以讓 Struts 1.1 和 Tiles 共同工作: 1. 創(chuàng)建一個(gè) JSP 以表示站點(diǎn)的布局。這是主 JSP,并帶有頁(yè)頭、頁(yè)體和頁(yè)腳的占位符。分別用 Tiles 標(biāo)記添加到主 JSP 頁(yè)面中。 2. 創(chuàng)建一個(gè) Tiles 定義文件并定義每個(gè)集成頁(yè)面的每個(gè)占位符中必須包括哪個(gè) JSP 頁(yè)面。用惟一的名稱(chēng)標(biāo)識(shí)出每一個(gè)合成頁(yè)面定義。 3. 在 struts-config.xml 文件中改變?nèi)趾捅镜剞D(zhuǎn)發(fā)以使用上一步驟中給出的惟一名稱(chēng)而不是別名。 4. 在啟動(dòng)時(shí)用 TilesPlugIn 裝載 Tiles 定義文件。將 TilesPlugIn 項(xiàng)加入到 struts-config.xml 文件中。 5. 將 TilesRequestProcessor 項(xiàng)添加到 struts-config.xml 文件中。這是支持 Tiles 的 Struts 應(yīng)用程序的默認(rèn)請(qǐng)求處理程序。 |
還要注意我們?cè)谶@里沒(méi)有使用 JSF validation
標(biāo)記。這是因?yàn)樵?Struts 中,驗(yàn)證是在 ActionForm
類(lèi)中的 validate()
方法中進(jìn)行的,有可能是通過(guò)使用 Commons-Validator。 s:errors
標(biāo)記類(lèi)似于 Struts 錯(cuò)誤標(biāo)記并用于顯示在驗(yàn)證時(shí)出現(xiàn)的錯(cuò)誤消息。
另一件要注意的事情是沒(méi)有 ActionListener
顯式地與提交按鈕相關(guān)聯(lián)。這是因?yàn)樵?Struts-Faces 中已經(jīng)提供了 ActionListener
并且總是將 faces 請(qǐng)求與 ActionEvent
s 一同轉(zhuǎn)交給 FacesRequestProcessor
,在那里根據(jù) struts-config.xml 文件將請(qǐng)求分派給相應(yīng)的 Action
類(lèi)。
將Struts 應(yīng)用程序移植到 JSF
為了將 Struts Web 應(yīng)用程序與 JSF 集成,遵循以下步驟:
- 將 struts-faces.jar 文件與特定于 JSF 的 JAR(jsf-api.jar、jsf-ri.jar) 添加到 Web 應(yīng)用程序的 WEB-INF/lib目錄中。
- 如果準(zhǔn)備使用 JSF 和 JSTL,則將特定于 JSTL 的 JAR(jstl.jar、standard.jar)添加到 WEB-INF/lib 文件夾中。這一步只有在部署到常規(guī) Tomcat 時(shí)才會(huì)需要。JWSDP 已經(jīng)提供了這些 JAR。
- 修改 Web 應(yīng)用程序部署描述符 ( /WEB-INF/web.xml)以便有一個(gè) Faces Servlet 項(xiàng), 如清單 5 所示。
- 修改 JSP 頁(yè)面以使用 JSF 和 Struts-Faces 標(biāo)記而不是 Struts 標(biāo)記。特別是用 Struts-Faces 相應(yīng)標(biāo)記替換
html、b
ase、
form
和 errors
標(biāo)記。用 JSF 相應(yīng)標(biāo)記替換 text
、 textarea
和 radio
標(biāo)記。Struts-Faces 沒(méi)有單獨(dú)針對(duì)這些的標(biāo)記。盡管沒(méi)有要求,但是您可能還會(huì)考慮用 JSTL 標(biāo)記替換 Struts Logic 標(biāo)記。
- 對(duì)于每一個(gè)使用 JSF 標(biāo)記的 JSP,修改 struts-config.xml 文件以在指向該 JSP 的 Action Mapping 中的 global-forwards和 local-forwards中加入前綴 /faces。
- 如果 Web 應(yīng)用程序使用了任何您創(chuàng)建的自定義組件,那么您就需要用 JSF 實(shí)現(xiàn)的默認(rèn) RenderKit 注冊(cè)它們。可以通過(guò)在 WEB-INF 文件中創(chuàng)建一個(gè) faces-config.xml 文件、并增加每一個(gè)組件和 renderer 的項(xiàng)做到這一點(diǎn)。不過(guò),要記住 faces-config.xml 文件已經(jīng)綁定在 struts-faces.jar 文件中了。您必須從 struts-faces.jar 文件中提出它、加入自己的內(nèi)容并將它放到 WEB-INF文件夾中。
清單 5. 在 web.xml 中聲明 FacesServlet
<!-- JavaServer Faces Servlet Configuration -->
<servlet>
<servlet-name>faces</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- JavaServer Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>faces</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
|
集成 Struts-Faces 和 Tiles 的挑戰(zhàn)
Struts-Faces 庫(kù)提供了 Struts 與 JSF 之間的一個(gè)高效的橋梁,使得在 J2EE Web 應(yīng)用程序中擁有豐富的表示層成為現(xiàn)實(shí)。您可以通過(guò)在組合體中添加 Titles 使表示層更豐富,這樣不僅得到了 Struts 和 JSF 組合的好處,而且還可以高效地重復(fù)使用不同的 JSP 頁(yè)面,因?yàn)樗鼈儗⒂煽梢愿鶕?jù)需要添加或者刪除的組件部分或者 tiles 所構(gòu)成。
本文已經(jīng)展示了 Struts 和 JSP 的集成,您會(huì)想將 Tiles 加入到組合中只是小事一樁,是不是?
不幸的是,JSF 仍然處于早期階段,還沒(méi)有給出最后的發(fā)布。基于這一考慮,Struts-Faces 集成軟件開(kāi)發(fā)仍然在不斷地發(fā)展以包括 JSF 的不同的功能,并且還沒(méi)有支持 Tiles。
Struts 和 Tiles 可以無(wú)縫地共同工作,但是在集成之路上您會(huì)遇到路障。在下面幾小節(jié)中,您會(huì)看到在與 Tiles 共同使用 Struts-Faces 集成庫(kù)時(shí)經(jīng)常遇到的問(wèn)題的匯總。對(duì)于每一個(gè)問(wèn)題,我們?cè)敿?xì)說(shuō)明了一個(gè)修改 Struts-Faces 類(lèi)的解決方案。我們將用一個(gè)航班搜索示例解釋這個(gè)解決方案。
清單 6 展示了航班搜索頁(yè)面的布局。注意我們稱(chēng)它為航班搜索頁(yè)面而不是 FlightSearch.jsp。這是因?yàn)?FlightSearch JSP 是用戶(hù)在 foobar 旅行 Web 站點(diǎn)看到的合成頁(yè)面的主體。
現(xiàn)在,我們保持實(shí)際的 FlightSearch.jsp 不變。我們將隨著進(jìn)展改變它。在您這邊,也需要用航班搜索頁(yè)的定義創(chuàng)建一個(gè) Tiles 定義文件。清單 7(緊接著清單 6)展示了 Tiles 定義文件中航班搜索頁(yè)的一項(xiàng)。注意對(duì)帶有 extends
屬性的主布局模板的重復(fù)使用。
在清單 6 和 7 后是每一個(gè)可能的挑戰(zhàn)。
清單 6. 航班搜索例子的 Tiles 布局
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %>
<!-- Layout component parameters: header, menu, body, footer -->
<s:html>
<head>
<title> <tiles:getAsString name="title"/></title>
<s:base/>
</head>
<body>
<TABLE border="0" width="100%" cellspacing="5">
<tr>
<td><tiles:insert attribute="header"/></td>
</tr>
<tr>
<td><tiles:insert attribute="body"/></td>
</tr>
<tr><td><hr></td></tr>
<tr>
<td><tiles:insert attribute="footer" /></td>
</tr>
</TABLE>
</body>
</s:html>
|
清單 7. 航班搜索頁(yè)的 Tiles 定義
<!-- Master Layout definition -->
<definition name="foobar.master-layout"
path="/faces/layout/MasterLayout.jsp">
<put name="title" value="Welcome to Foo Bar Travels" />
<put name="header" value="/faces/common/header.jsp" />
<put name="footer" value="/faces/common/footer.jsp" />
<put name="body" value="" />
</definition>
<!-- Definition for Flight Search Page -->
<definition name="/foobar.flight-search"
extends="foobar.master-layout">
<put name="body" value="/faces/FlightSearch.jsp" />
</definition>
|
響應(yīng)已經(jīng)提交
這是您在試圖訪(fǎng)問(wèn)航班搜索表單時(shí)馬上會(huì)看到的第一個(gè)問(wèn)題。小心查看堆棧跟蹤。您會(huì)看到問(wèn)題出在類(lèi) com.sun.faces.lifecycle.ViewHandlerImpl
上。這是一個(gè)實(shí)現(xiàn)了 ViewHandler
接口的 JSF-RI 類(lèi)。
圖 2展示了 ViewHandler
所扮演的角色。這是一個(gè)將請(qǐng)求轉(zhuǎn)發(fā)給下一頁(yè)的類(lèi)。在轉(zhuǎn)發(fā)請(qǐng)求時(shí),它不在轉(zhuǎn)發(fā)前檢查響應(yīng)的狀態(tài) -- 這只有在使用 Tiles 時(shí)才會(huì)發(fā)生,因?yàn)?Tiles 內(nèi)部將 JSP 頁(yè)面包括在響應(yīng)內(nèi),而 JSF-RI 在第一次轉(zhuǎn)發(fā)后提交響應(yīng)、然后試圖再次轉(zhuǎn)發(fā)給下面的包括 JSP 的 Tiles。
要解決這個(gè)問(wèn)題,必須創(chuàng)建一個(gè)自定義的 ViewHandler
實(shí)現(xiàn),它將檢查響應(yīng)的狀態(tài)以確定它是否提交過(guò)。如果響應(yīng)沒(méi)有提交過(guò),那么請(qǐng)求就轉(zhuǎn)發(fā)給下一頁(yè),否則,就加入請(qǐng)求并顯示相應(yīng)的 JSP。我們將創(chuàng)建一個(gè)名為 STFViewHandlerImpl
的類(lèi),它實(shí)現(xiàn)了 ViewHandler
接口并實(shí)現(xiàn)了所需要的方法 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());
}
|
現(xiàn)在您實(shí)現(xiàn)了自己的 ViewHandler
,如何通知 JSF-RI 使用您的 ViewHandler
而不是默認(rèn)的實(shí)現(xiàn)呢?要回答這個(gè)問(wèn)題,就必須理解 FacesServlet
的工作過(guò)程。
在 Faces 初始化過(guò)程中, FacesServlet
會(huì)讓 LifecycleFactory
實(shí)現(xiàn)返回 Lifecycle
類(lèi)的一個(gè)實(shí)現(xiàn),如清單 9 所示:
清單 9. FacesServlet 中 Faces 的初始化
//Get the LifecycleFactory from the Factory Finder
LifecycleFactory factory = (LifecycleFactory)
FactoryFinder.getFactory("javax.faces.lifecycle.LifecycleFactory");
//Get the context param from web.xml
String lifecycleID =
getServletContext().getInitParameter("javax.faces.lifecycle.LIFECYCLE_ID");
//Get the Lifecycle Implementation
Lifecycle lifecycle = factory.getLifecycle(lifeCycleID);
|
Lifecycle
實(shí)現(xiàn)對(duì)象擁有在呈現(xiàn)響應(yīng)階段要使用的 ViewHandler
。您可以通過(guò)對(duì) Lifecycle
實(shí)現(xiàn)調(diào)用 setViewHandler
方法讓自己的 ViewHandler
實(shí)現(xiàn)成為默認(rèn)的。
現(xiàn)在問(wèn)題變?yōu)槿绾蔚玫侥J(rèn) Lifecycle
實(shí)現(xiàn)?回答是不需要這樣做。只要?jiǎng)?chuàng)建一個(gè)新的實(shí)現(xiàn)并用一個(gè)惟一 ID 注冊(cè)它,如清單 10 所示:
清單 10. 注冊(cè)自定義 ViewHandler 和 Lifecycle
//Get the LifecycleFactory from the Factory Finder
LifecycleFactory 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 Lifecycle
stfLifecycleImpl.setViewHandler(new STFViewHandlerImpl());
//Register the new lifecycle with the factory with a unique
//name "STFLifecycle"
factory.addLifecycle("STFLifecycle", stfLifecycleImpl);
|
您可以看到 lifecycleId
硬編碼為 STFLifecycle
。實(shí)際上不是這樣。當(dāng)您回過(guò)頭分析 清單 9時(shí)就會(huì)清楚。 FacesServlet
從在 web.xml 文件中聲明的上下文參數(shù)中得到名為 javax.faces.lifecycle.LIFECYCLE_ID
的 lifecycle ID,如下所示:
<context-param>
<param-name>javax.faces.lifecycle.LIFECYCLE_ID</param-name>
<param-value>STFLifecycle</param-value>
</context-param>
|
因?yàn)?FacesServlet
取決于其初始化時(shí)的 Lifecycle
實(shí)現(xiàn),在 清單 10中展示的代碼應(yīng)該在 FacesServlet
初始化之前執(zhí)行。通過(guò)創(chuàng)建另一個(gè) servlet 并在 FacesServlet
之前初始化它而做到這一點(diǎn)。
但是一種更聰明的辦法是實(shí)現(xiàn)一個(gè) ServletContextListener
接口。這個(gè)類(lèi)聲明兩個(gè)方法: contextInitialized()
和 contextDestroyed()
,在 Web 應(yīng)用程序被創(chuàng)建及 Web 應(yīng)用程序被銷(xiāo)毀之前會(huì)分別調(diào)用它們。因而 清單 10中的代碼在 contextInitialized()
方法中執(zhí)行,而自定義 ViewHandler
已經(jīng)用標(biāo)識(shí)名 STFLifecycle
注冊(cè)到 Lifecycle
,并且可被 FacesServlet
使用。 ServletContextListener
類(lèi)本身是在 web.xml 文件中聲明的,如下所示:
<listener>
<listener-class>foo.bar.stf.application.STFContextListener
</listener-class>
</listener>
|
這不是注冊(cè)一個(gè)帶有自定義 ViewHandler
的 Lifecycle
惟一方法。事實(shí)上 FactoryFinder
實(shí)現(xiàn)了自己的發(fā)現(xiàn)算法以發(fā)現(xiàn) Factory
對(duì)象,包括 LifecycleFactory
。這些機(jī)制按照順序包括在系統(tǒng)屬性中查看工廠實(shí)現(xiàn)類(lèi)名的機(jī)制、faces.properties file、或者 1.3 Services 發(fā)現(xiàn)機(jī)制( META-INF/services/{factory-class-name}
)。不過(guò),我們討論的這種機(jī)制是最容易的,也是最不具有破壞性的一種。
404 Resource Not Found
在解決了提交響應(yīng)的問(wèn)題后,單擊任何一個(gè) Tiles 特定的鏈接或者輸入一個(gè)會(huì)呈現(xiàn) Faces 響應(yīng)的 URL。在這里,可以輸入顯示 FlightSearchForm
的 URL。
在這樣做了以后,您會(huì)得到一個(gè) foobar.flight-search - 404 Resource Not Found 錯(cuò)誤。 foobar.flight-search
是航班搜索頁(yè)面的 Tiles 定義的名字。 FacesRequestProcessor
不能處理 Tiles 請(qǐng)求(因?yàn)樗鼣U(kuò)展的是 RequestProcessor
而不是 TilesRequestProcessor
),所以會(huì)得到錯(cuò)誤。
為解決這個(gè)問(wèn)題,我們將創(chuàng)建一個(gè)名為 STFRequestProcessor
(表示 Struts-Tiles-Faces Request Processor)的新的請(qǐng)求處理程序。現(xiàn)在我們將拷貝 FacesRequestProcessor
的所有代碼到這個(gè)新類(lèi)中。惟一的區(qū)別是 STFRequestProcessor
繼承的是 TilesRequestProcessor
而不是繼承常規(guī)的 RequestProcessor
。這個(gè)新的 RequestProcessor
可以處理 Tiles 請(qǐng)求。清單 11 詳細(xì)列出了這個(gè) STFRequestProcessor
:
清單 11. STFRequestProcessor.java
正如您所知道的, Struts
框架的 RequestProcessor
是在 struts-config.xml 文件中指定的。將下面的項(xiàng)添加到 struts-cinfig.xml
文件中后, STFRequestProcessor
就成為處理程序:
<controller processorClass="foobar.stf.application.STFRequestProcessor" />
|
表單提交顯示返回同一個(gè)表單
由于 STFRequestProcessor
的作用,這時(shí)您就可以瀏覽并查看航班頁(yè)面了。不過(guò),在提交航班搜索表單時(shí),您會(huì)得到返回來(lái)的同一個(gè)表單,而且沒(méi)有頁(yè)頭和頁(yè)腳!并且沒(méi)有驗(yàn)證錯(cuò)誤。事實(shí)上,根本就沒(méi)有進(jìn)行驗(yàn)證!
為了了解到底發(fā)生了什么事情,我們用瀏覽器回到航班頁(yè)面并檢查 HTML 源代碼。您會(huì)看到像下面這樣的一項(xiàng):
<form name="FlightSearchForm" method="post"
action="/flightapp/faces/FlightSearch.jsp">
|
注意表單 action 是指向 JSP 頁(yè)而不是一個(gè) .do 的。啊哈!這就是問(wèn)題!這不是由于同時(shí)使用 Tiles 和 Struts-Faces 而帶來(lái)的新問(wèn)題,Struts-Faces 的默認(rèn)行為是讓 JSP 與表單 action 有同樣的名字。這種行為在有單一的 JSP 頁(yè)(如在前面的 Struts-Faces 例子中)時(shí)沒(méi)有問(wèn)題。 清單 3展示了原來(lái)的 FlightSearch.jsp,讓我們繼續(xù)并像下面這樣修改 action:
<s:form action="/listFlights.do>
|
當(dāng)然,光有這種修改并不能解決問(wèn)題。作了這種改變后,您就會(huì)發(fā)現(xiàn) STFRequestProcessor
不能找到 ActionForm
。顯然還需要其他的改變。
不過(guò),在繼續(xù)往下之前,看一下圖  5。它顯示了在呈現(xiàn)負(fù)責(zé) Struts-Faces 表單的 faces 時(shí)相關(guān)的一系列事件。這與 圖 3相同,除了在 FormComponent
中突出顯示的方法 createActionForm()。
由 Struts-Faces API 提供的 FormComponent
類(lèi)是 javax.faces.component.UIForm
的特殊子類(lèi),它支持請(qǐng)求或者會(huì)話(huà)范圍的表單 Bean。
圖 5. 呈現(xiàn) Struts-Faces 響應(yīng)
單擊這里以查看該圖。
正如您所看到的, createActionForm()
方法使用 action 名以從 Struts 配置文件中得到 ActionMapping
。因?yàn)闆](méi)有對(duì)于 /listFlights.do 的 ActionMapping
,所以 Struts 不能找到 ActionForm。
這個(gè)問(wèn)題的解決方法是使用 org.apache.struts.util.RequestUtils
。 RequestUtils
中的 static 方法 getActionMappingName()
具有足夠的智能解析映射到正確 ActionMapping
的路徑( /x/y/z)或者后綴( .do)。
清單 12 以粗體顯示對(duì) createActionForm
方法的改變。我們沒(méi)有對(duì) Struts-Faces 中的 FormComponent
作這些改變,而是通過(guò)繼承 FormComponent
并覆蓋 createActionForm()
方法創(chuàng)建了一個(gè)新的 STFFormComponent。
清單 12. FormComponent 中修改過(guò)的 createActionForm() 方法
// Look up the application module configuration information we need
ModuleConfig moduleConfig = lookupModuleConfig(context);
// Look up the ActionConfig we are processing
String action = getAction();
String mappingName = RequestUtils.getActionMappingName(action);
ActionConfig actionConfig = moduleConfig.findActionConfig(mappingName);
....
....
|
對(duì)新的 STFFormComponent
還要作一項(xiàng)改變。Struts-Faces 將 action 名本身作為表單名。這需要改變,因?yàn)?action 帶有后綴 .do,而表單名沒(méi)有后綴 .do。所以我們?cè)?STFFormComponent
上增加一個(gè)名為 action
的新屬性,并覆蓋 getAction()
和
setAction()
方法。
FormRenderer 的改變
必須對(duì) FormRenderer
(以 HTML 格式呈現(xiàn) Struts-Faces 表單的類(lèi))的 encodeBegin
方法進(jìn)行類(lèi)似于 清單 10所示的修改。
同樣,通過(guò)繼承 FormRenderer
做到這一點(diǎn)。此外,還必須改變寫(xiě)出到 HTML 的表單 action。清單 13以粗體詳細(xì)列出了這些改變:
清單 13. FormRenderer 的改變
protected String action(FacesContext context, UIComponent component) {
String treeId = context.getTree().getTreeId();
StringBuffer sb = new StringBuffer
(context.getExternalContext().getRequestContextPath());
sb.append("/faces");
// sb.append(treeId); -- This is old code, replaced with
// the two lines below.
STFFormComponent fComponent = (STFFormComponent) component;
sb.append(fComponent.getAction());
return (context.getExternalContext().encodeURL(sb.toString()));
}
|
FormTag的改變
正如您已經(jīng)知道的,當(dāng)組件和 renderer 改變時(shí),標(biāo)記也必須改變。在這里,通過(guò)繼承 Struts-Faces 中的 FormTag
創(chuàng)建一個(gè)新的標(biāo)記: STFFormTag
。不必改變?nèi)魏喂δ埽灰采w getComponentType()
和 getRendererType()
方法。清單 14 展示了從 STFFormComponent
覆蓋的方法:
清單 14. FormTag 的改變
public String getComponentType()
{
return ("STFFormComponent");
}
public String getRendererType()
{
return ("STFFormRenderer");
}
|
修改 faces-config.xml 文件
自定義組件和 renderer 必須在 faces-config.xml 文件中聲明,這樣 JSF 框架才可以初始化并使用它們。現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)新組件 STFFormComponent
和一個(gè)新 renderer STFFormRenderer
。
現(xiàn)在我們將在 faces-config.xml 文件中增加一個(gè)聲明,如清單 15 所示。 component-class 是組件的完全限定類(lèi)名。 component-type 指的是在 STFFormTag
( 清單 12)中用于標(biāo)識(shí)組件的名字。以類(lèi)似的方式發(fā)現(xiàn)和解釋 renderer。注意 faces-config.xml 文件是在 struts-faces.jar 文件中的。從 struts-faces.jar 文件中取出這個(gè)文件并將它放到 Web 應(yīng)用程序的 WEB-INF文件夾中并修改它。
清單 15. 在 faces-config.xml 中聲明自定義組件和 renderer
<faces-config>
<!-- Custom Components -->
<component>
<component-type>STFFormComponent</component-type>
<component-class>
foobar.stf.component.STFFormComponent
</component-class>
</component>
..
..
..
<!-- Custom Renderers -->
<render-kit>
<renderer>
<renderer-type>STFFormRenderer</renderer-type>
<renderer-class>
foobar.stf.renderer.STFFormRenderer
</renderer-class>
</renderer>
..
..
..
</render-kit>
</faces-config>
|
修改 struts-faces.tld 文件
您不會(huì)在這個(gè)示例 Struts-Faces 應(yīng)用程序中看到 struts-faces.tld 文件,它打包到了 struts-faces.jar 文件中。打開(kāi)并分析這個(gè)文件。它聲明了一個(gè)名為 org.apache.struts.faces.taglib.LifecycleListener
的類(lèi),這個(gè)類(lèi)實(shí)現(xiàn)了 ServletContextListener
并初始化 FacesRequestProcessor
。
因?yàn)橄M褂眯碌?STFRequestProccessor
,所以必須將這個(gè)文件從 struts-faces.jar 文件中刪除,將它放到 Web 應(yīng)用程序的 WEB-INF 文件夾中,并刪除偵聽(tīng)器聲明。如果讓這個(gè) tld 文件保持原樣,那么在初始化這個(gè) Web 應(yīng)用程序時(shí),除了 STFRequestProcessor
,還會(huì)實(shí)例化一個(gè) FacesRequestProcessor。
修改 base href 標(biāo)記
現(xiàn)在,您已經(jīng)完成了 Struts、Tiles、JSF 集成的最困難的部分了。您甚至可以瀏覽航班搜索頁(yè)面,并輸入搜索標(biāo)準(zhǔn)查看航班列表。現(xiàn)在試著從航班列表頁(yè)面返回航班搜索表單。您會(huì)得到一個(gè) HTTP 400 錯(cuò)誤。這個(gè)錯(cuò)誤的原因是 HTML base href
標(biāo)記。它被設(shè)置為 Master Layout 頁(yè)面。
<base href=
"http://localhost:8080/stf-example/faces/layout/MasterLayout.jsp" />
|_________| |_____________________|
Context Servlet Path
|
程序所有頁(yè)面瀏覽都是相對(duì)于布局頁(yè)面計(jì)算的。如果加入的 base href
標(biāo)記只達(dá)到 Web 應(yīng)用程序上下文則會(huì)很方便,像這樣:
<base href="http://localhost:8080/stf-example/" />
|
我們可以通過(guò)定制 Struts-Faces BaseTag
做到這一點(diǎn)。這個(gè)類(lèi)中的改變相當(dāng)微不足道。只須在 base href 中去掉 HttpServletRequest.getServletPath()
。
因?yàn)檫@些改變是與顯示相關(guān)的,所以為它創(chuàng)建了一個(gè)名為 STFBaseRenderer
的新 renderer。這個(gè)新標(biāo)記稱(chēng)為 STFBaseTag
,它聲明 STFBaseRenderer
作為其關(guān)聯(lián)的 renderer。不需要新的組件。
有了這些信息,通過(guò)繼承 BaseTag
并覆蓋 getRendererType
方法創(chuàng)建新的 STFBaseTag
,如下所示:
public String getRendererType()
{
return ("STFBaseRenderer");
}
|
到目前為止所作的改變
恭喜!經(jīng)過(guò)這些相對(duì)較小的修改,您已經(jīng)成功地集成了 Struts、Tiles 和 JSF,并保留了您以前在這些技術(shù)上所做的所有投資。本文演示了如何將 JSF 強(qiáng)大的前端能力、 Tiles 的內(nèi)容格式編排優(yōu)勢(shì)以及 Struts 控制器層的靈活性結(jié)合在一個(gè)包中,使得創(chuàng)建一個(gè) J2EE Web 應(yīng)用程序成為一項(xiàng)更容易的任務(wù)。
我們討論了定制 Struts 類(lèi)以便與 JavaServer Faces 和 Tiles 框架形成緊密集成的工作關(guān)系,包括下面這些修改和增加:
- 新的
ViewHandler
,用于檢查提交的響應(yīng)。
- 新的
ServletContextListener
,用于創(chuàng)建新的 Lifecycle
實(shí)現(xiàn)并注冊(cè)這個(gè)定制的 ViewHandler。
- 新的
RequestProcessor
,用于處理 Tiles 請(qǐng)求。
- 修改過(guò)的 web.xml 文件,聲明新的
ServletContextListener
和 JSF Lifecycle ID。
- 新的
FormTag、
FormComponent
和 FormRenderer
類(lèi)。
- 新的
BaseTag
和 BaseRenderer
類(lèi)。
- 修改過(guò)的 faces-config.xml 文件,它聲明了新的組件和 renderer。
- 修改過(guò)的 struts-faces.tld 文件,不聲明偵聽(tīng)器。
希望它可以概括本文中使用的復(fù)合技術(shù),最重要的是,我們?yōu)槟峁┝藢?Struts、Tiles 和 JavaServer Faces 結(jié)合到用于構(gòu)建 Web 應(yīng)用程序的一個(gè)強(qiáng)大而靈活的機(jī)制中的一個(gè)令人信服的路線(xiàn)圖。
參考資料
posted on 2005-11-10 00:19
閔毓 閱讀(720)
評(píng)論(0) 編輯 收藏 所屬分類(lèi):
Java開(kāi)發(fā) 、
Struts in action