Facelets 非常適合 JSF最終形成了專為 JSF 設計的視圖技術! |
|
|
級別: 初級
Richard Hightower, 開發人員, ArcMind Inc.
2006 年 3 月 27 日
試圖把 JSF 和 JSP 結合起來就像試圖要把腳硬塞進手套一樣:可能做得到,但是只是更好的解決辦法出現之前的一個權宜之計。在這篇文章中,JSF 的熱心支持者 Rick Hightower 介紹了關于 Facelets 他最喜歡的內容:容易的 HTML 樣式的模板化和可重用的復合組件。
由于最近在 Java? 服務器外觀(JSF)項目上工作,我很有幸第一次使用了 Facelets。關于 Facelets,我最喜歡的是它讓我可以創建可重用的復合組件。能夠拿出一個頁面(例如 JSP)并把它變成組件,對于我的 JSF 開發來說真是莫大的好處。我的結論是什么?如果不用 Facelets,那么就無法得到能從 JSF 獲得的最大收獲。
JSF 和 Java 服務器頁面技術之間的不匹配,是 JSF 開發中的一個嚴重問題。問題是如何把 JSP 的動態內容集成到 JSF 基于組件的模型中。JSP 非常重視生成動態內容輸出,而 JSF 需要 JSP 來協調組件模型的構建。因為這個任務超出了 JSP 原來的目的,所以產生了距離。
大多數 JSF 開發人員只是學會了一事一議地解決這類問題,但是這就像在錘子上放一個枕頭,最終還會掉下來打傷腦袋。Facelets 是更加全面的解決方案:專為 JSF 組件模型度身定制的模板化語言。
Facelets 有以下吸引人的特性:
- 模板化(像 Tiles)
- 復合組件
- 定制的邏輯標記
- 表達式語言
- 對設計師友好的頁面開發
- 創建組件庫
這些特性比我想像的要更相關和統一。在這篇文章中,我討論前兩個:模板化和復合組件。我使用的 Web 應用程序基于為我的針對懷疑者的 JSF 系列開發的一個應用程序,我把它更新成使用 Facelets 視圖而不是 Tiles。在進一步閱讀之前,應當 下載示例代碼。如果要隨著討論一起操作,還需要 安裝 Facelets。
對于 Facelets 可能會做的最大一個錯誤假設,就是它只是 Tiles 的替代品。Facelets 遠不止如此:它是思考 JSF 的新方式。
|
|
JSP 是種生成 servlet 的模板化語言。JSP 的主體與 servlet 的 doGet()
和 doPost()
方法等價(也就是說,成為 jspService()
方法)。JSF 定制標記(例如 f:view
和 h:form
)只是調用 JSF 組件來呈現它們自己的當前狀態。JSF 組件模型的生命周期獨立于 JSP 生成的 servlet 的生命周期。這種獨立性就是混淆的來源。
與 JSP 不同,Facelets 這個模板化語言,從構建之初,就考慮了 JSF 的組件生命周期。使用 Facelets,生成的模板會構建組件樹,而不是 servlet。這就允許更好的重用,因為可以把組件組合成另一個組件。
Facelets 減少了編寫定制標記才能使用 JSF 的需求。Facelets 本身就可以使用 JSF 定制組件。溝通 JSF 和 Facelets 只需要很少的特殊編碼:要做的全部工作就是在 Facelet 標記庫文件中聲明 JSF 組件。在 Facelets 模板化語言中可以直接使用 JSF 組件,不用任何額外的開發。
在提供針對組件構建設計的模板框架方面,Facelets 與 Tapestry (請參閱 參考資料)類似。但是,對于具有 JSP 背景的我們來說,Facelets 看起來比 Tapestry 友好得多。它允許使用熟悉的 JSTL 樣式的標記和 JSTL/JSF/JSP 樣式的表達式語言。大大降低的學習曲線意味著可以更加迅速地開始開發。
|
|
Facelets 允許定義能夠直接包含進頁面或者容易地添加到 Facelet 標記庫的組件集。實際上讓人高興的是在 Facelets 中定義定制標記(復合組件和類似 JSP 定制標記的標記)的迅速。使用這些組件集,Facelets 還允許定義站點模板(和更小的模板)。這與使用 Tiles 很相似,但是少了定義文件。也可以在定制 JSF 組件內部使用 Facelets,因為 Facelets API 提供了可以容易地與 JSF 組件集成的接口。
如前所述,在這里使用的示例 Web 應用程序基于為我的 針對懷疑者的 JSF 系列創建的示例。它為一家在線 CD 店管理庫存,創建、讀取、更新和刪除(CRUD)清單。它包含一個表單,讓用戶向系統輸入新 CD,有一個單選按鈕列表,允許用戶選擇音樂分類。當用戶選擇了一個分類時,就觸發某些 JavaScript 立即把表單提交回服務器。應用程序還包含一個 CD 清單,用戶可以根據標題或藝術家對清單中的 CD 排序。圖 1 是應用程序類的 UML 圖表:
圖 1. 在線 CD 商店示例的類圖
圖 2 提供了商店的 CD 清單頁面:
圖 2. 在線 CD 商店的清單頁面
原來的應用程序從 Tiles 得到視圖支持,現在我將用 Facelets 構建視圖。我先從用 Facelets 替換示例中的 Tiles 支持開始,然后編寫復合組件。在開始之前,需要已經安裝了 Facelets。
安裝 Facelets 的步驟很容易。請注意,我假設已經下載并安裝了 示例應用程序。
-
下載 Facelets 發行包 并解壓縮。
- 把 jsf-facelets.jar 拷貝到 WEB-INF/lib 目錄(在應用程序部署時,它最終必須放在 WEB-INF/lib 目錄中)。
- 把 Facelet 初始化參數添加到 web.xml 文件中。
- 把 FaceletViewHandler 添加到 faces-config.xml 文件中。
步驟 1 和 2 比較基本。我將詳細介紹其他兩個步驟。
這一步假設已經安裝了工作正常的 JSF 應用程序(例如 在線 CD 商店示例),然后編輯現有的 web.xml 頁面,添加以下參數:
|
這告訴 JSF 采用 xhtml 前綴,Facelet 渲染器能夠解釋這個前綴。
Facelets 有許多參數,請參閱 參考資料 獲得完整清單。如果示例有問題,請參考 DEVELOPMENT init
參數,它適合調試。把 REFRESH_PERIOD
參數設置為 low
在開發期間會有幫助。
要讓 Facelets 模板生效,需要把 Facelets 視圖處理器告訴 JSF。JSF ViewHandler
是個插件,為不同的響應生成技術(包括 Facelets)處理 JSF 請求處理生命周期的 “渲染器響應和恢復視圖” 階段。(任何認為 JSF 不能擴展的人都是被誤導了!)通過添加以下視圖處理器到 faces-config.xml 中,就把 Facelets 插進了 JSF 中:
|
|
|
首先介紹 Facelets 模板化框架,因為它相對容易理解。創建和使用 Facelets 模板的步驟如下:
- 創建 layout.xhtml 頁面。
- 定義 Facelet 的命名空間,導入對 Facelets 的使用。
- 用
ui:insert
標記定義頁面的邏輯區域。 - 用純文本和
ui:include
標記定義合理的默認值。
我要逐步介紹這些步驟,用在線 CD 商店清單頁面作為我的布局示例。
layout.xhtml 頁面就是一個一般的 XHTML 文本文件,使用了以下文檔類型聲明:
|
不要求進一步細節!
為了用 Facelets 標記進行模板化,需要用 XML 命名空間像下面這樣導入它們:
|
請注意 ui 命名空間的定義。
下面,定義布局的邏輯區域,例如頁面標題、小標題、導航、內容等等。下面是定義頁面標題的示例:
|
請注意使用 ui:insert
標記定義了標題的邏輯區域。ui:insert
元素內的文本 “Default title” 定義了模板用戶不傳遞標題時顯示的文本。也可以像下面這樣編寫上面的內容:
|
可以傳遞更多的純文本作為默認值。例如,請研究 layout.xhtml 中的以下代碼片段:
|
在這里,我用了 ui:insert
標記定義邏輯區域,用 ui:include
標記插入默認值。默認情況下,使用布局的頁面會采用 header.xhtml 的內容作為標題文本,但是因為標題是 ui:insert
定義的邏輯區域,所以用這個模板也能傳遞不同的標題。對于擁有前端(例如,帶有購物車的目錄)和后端管理(例如添加新產品)的應用程序,后端站點在標題或導航上可能不同的鏈接。ui:include
標記可以容易地用新標題換掉默認標題。
清單 1 顯示了示例應用程序的清單頁面 list.xhtml 的完整代碼:
清單 1. 完整的 list.xhtml
|
現在已經知道了如何定義布局,我將介紹如何使用布局!
|
|
為了調用模板,要使用 ui:composition
標記。為了把參數傳遞給模板,要使用 ui:define
標記,它是 ui:composition
標記的子元素。在清單 2 中,我調用了在線 CD 商店示例的布局頁面:
清單 2. 調用布局頁面
|
請注意在上面的調用中包含了以下命名空間:
- xmlns:h="http://java.sun.com/jsf/html"
- xmlns:f="http://java.sun.com/jsf/core"
有了 Facelets,就不必依賴 JSF 標記庫,所以要使用核心和 HTML JSF 組件,必須通過以上命名空間導入它們。
html
標記的使用看起來可能有些奇怪。畢竟,清單 2 所示的布局頁面要調用的模板已經有了一個 html
標記;所以這是不是意味著會得到兩個 html
標記?如果真的這樣,那么在 ui:composition
標記之外的內容全部被忽略,所以 html
標記所做的只是讓 HTML 編輯器能夠看到 HTML 片段。它不會影響運行時行為。
當頁面調用布局模板時,只需指定模板的位置,如下所示:
|
這個標記調用清單 1 所示的模板,所以我要做的全部工作就是把參數傳遞給模板。然后,在復合標記內部,可以傳遞像標題這樣的簡單文本:
|
或者傳遞整個組件樹:
|
請注意,在我定義和傳遞的許多邏輯區域中,cdForm.xhtml 只傳遞兩個:內容和標題。
|
|
如果只用 Facelets 定義和使用模板,那么可能會有點失敗。雖然 Facelets 的模板化特性完整而且豐富,但是它沒有 Tiles 之類的框架那么多的特性,后者還擅長定義默認值、相關模板的層次結構以及類似的東西。
但模板化不是 Facelets 真正出色的地方:Facelets 把它的精華放在復合組件上。(有趣的是,復合組件也給 Facelets 模板化帶來了一些好處;例如,在 Facelets 中可以舍棄 f:verbatim
標記和各種 h:outputText
標記,因為所有的東西都被當成組件樹中的組件。關于這方面的更多內容稍后介紹。)
對于這篇文章余下的部分,我將重點放在創建和使用復合組件的步驟上。但在開始之前,先要確保能夠清楚地理解是什么讓這些方便的小代碼段這么棒。
您是否曾經編寫過像清單 3 所示的代碼片段?
清單 3. 復合組件之前的生活
|
這段來自 listing.xhtml 的代碼為示例應用程序的清單頁面生成列標題和升序/降序排列鏈接。請注意,必須在多個地方重復代碼,才能輸出多列。(在上面的示例中您還會注意到,我在 ${..}
和 #{..}
之間切換;這可能讓人迷惑,但它們做的是同樣的事!)
所有這些渲染標題列和藝術家列的重復代碼都破壞了 DRY 原則 —— 即,不要重復自己。那么您說,這里錯在哪兒呢?假設在清單中平均有 5 列,應用程序中有 20 個不同的清單。使用清單 3 的方法,就不得不重復這 35 行代碼 100 次,合計 3,500 行代碼!維護所有這些代碼會是種痛苦,但是如果決定修改清單的表示或添加一種通用的清單過濾方式,會怎么樣?需要更多、更多的工作。
現在拿清單 3 和這個代碼比較:
清單 4. 創建字段的新方式
|
看起來好像我只用 4 行代碼就替代了 70 行或更多行代碼!可以猜出,a:column
是個復合組件。在 Facelets 中,可以容易地定義這樣的組件,如清單 5 所示:
清單 5. column.xhtml 渲染帶有排序鏈接的列
|
在進入更高級的示例之前,我想把您的注意力引到幾件事上。首先,請注意在清單 5 中,我如何用一種通用方式引用值綁定:
|
第二,當調用這個復合組件時,我把 entity
和 fieldName
作為屬性傳遞,如下所示:
|
Facelets 使用的 EL 規范允許用圓點(.
)表示法或較少使用的映射表示法引用字段。例如,如果像上面那樣調用,那么 ${entity[fieldName]}
等價于 CDManager.title
。還請注意,我不需要 f:verbatim
標記或輔助的 h:outputText
。對于您編寫的任何 Facelets 頁面,都可以這樣。Facelets 知道 JSF 組件樹,它的唯一目的就是構建這個組件樹。這是使用 Facelets 與使用 JSP 和 Tiles 相比的另一個優勢。
編寫完成之后,就可以在其他許多地方使用 column.xhtml 復合組件。作為一個通用規則:如果正在破壞 DRY 原則,那么請考慮改用復合組件。
|
|
現在通過 column.xhtml 示例已經快速查看了復合組件。下面一步一步地介紹創建復合組件的過程。以下是創建復合組件的步驟:
- 創建 Facelets 標記庫。
- 在 web.xml 中聲明標記庫。
- 用命名空間導入標記文件。
標記文件 是符合 facelet_taglib_1_0.dtd 的文件。在概念上它與 JSP 中的 TLD 文件相似。清單 6 是一個示例標記庫文件:
清單 6. 標記庫文件 —— arcmind.taglib.xml
|
arcmind.taglib.xml 文件聲明了三個標記:field
、column
(已經看過這個!)和 columnCommand
。需要做的只是用 tag-name
指定標記的名稱和實現文件的位置。實現文件的名稱是相對的。可以在示例 Web 應用程序下的 WEB-INF\facelets\tags 文件中找到所有這些代碼,包括 DTD。
請一定注意在上面的標記元素之前聲明的 namespace
元素:稍后需要通過它在其他 Facelets 頁面中使用這個標記庫。
有了一個標記庫是很好,但是要讓它有用,還必須把它的存在告訴 Facelets。在 web.xml 文件中用 facelets.LIBRARIES init
參數做這件事,如下所示:
|
將 facelets.LIBRARIES
以分號分隔的列表形式傳遞,就可以想定義多少就定義多少標記文件。
創建了標記文件并在 Facelets 標記庫中定義了它之后,就可以使用它了。標記文件的使用要求把它聲明為 XML 命名空間,如下所示:
|
請注意命名空間的定義如下所示:
|
命名空間的值與前面步驟 1 中標記庫中聲明的命名空間元素一樣。
|
|
上面只介紹了復合組件的基礎知識。用我目前為止介紹的內容能夠創建可重用組件。在我自己使用 Facelets 時,發現了一些可以讓復合組件更有用的小技巧,而且在某些情況下能夠解決一些小問題。例如,請考慮來自 cdForm.xhtml 模板的以下代碼片段:
清單 7. cdForm.xhtml 的片段
|
以上頁面在概念上與 清單 3 類似,但它為 Facelets 和一個字段復合組件留出了空間,可以避免重復代碼。基于這個代碼,應當可以容易地創建顯示字段的復合組件,但是有一個小麻煩。不知道您看到沒有?請看表單的 price
字段:它包含一個驗證器。
現在,如何把驗證器傳遞給復合組件?
|
|
這里有一個關于 Facelets 的小秘密:復合組件基本上也是一類模板。所以,可以用 ui:define
標記傳遞模板參數,帶上具體的 ui:insert
,或者也可以把體作為默認 ui:insert
傳遞。
清單 8 就是字段組件的這樣一種實現(field.xhtml):
清單 8. field.xhtml
|
目前為止,清單 8 的工作基本上是不出所料。請注意 h:inputText
內部未命名 ui:insert
標記的使用。理解了它之后,就可以像清單 9 所示那樣使用這個復合組件:
清單 9. 字段標記復合組件
|
price
的字段標記被傳遞給驗證器,作為匿名插入。因為其他字段沒有定義體,所以匿名插入對于默認值沒有影響。
|
|
在想傳遞動作綁定來創建像工具欄或導航列表這樣的元素時,問題就是使用標準的表達式語言,不能傳遞,但是有方法可以做到!使用從對象中引用字段的相同方式,可以引用對象中的方法。所以,要創建可以創建動作綁定的組件,可以像下面這樣做(來自 columnCommand.xhtml):
|
請研究動作屬性的 value
。請注意,我使用與前面從實體引用字段相同的方式,訪問到了方法。可以用以下語法調用這個組件:
|
這個調用把 CDManagerBean
的 editCD()
方法綁定到鏈接。清單 10 顯示了 columnCommand.xhtml
的完整清單:
清單 10. columnCommand.xhtml
|
|
|
我已經清楚地演示了使用 Facelets 的好處:即組件復合和模板框架,它的核心 是組件,而不是 Servlets 輸出。但是采用 Facelets 也有些不足。其中之一就是,對 Facelets 的 IDE 支持極少。只有一個 Eclipse IDE 實現完全支持 Facelets(商業版的實現,請參閱 參考資料),而且看起來還不支持代碼補足。
而且也沒對 Facelets 調試的 IDE 支持(也就是說,設置斷點之類的東西)。要想調試,需要閱讀 Facelets 手冊,打開 JDK 1.4 樣式的日志,根據開發情況設置它的 init
參數。
在有利方面來說,我發現使用 Facelets API 非常自然和直觀。調試在開始的時候有些怪異,但是后來就適應了。隨 Facelets 發行包提供的演示應用程序沒有定制標記或功能的示例,但是核心項目代碼中有,所以請用核心項目代碼作為指導。
如果要使用新的 JSF 組件庫,必須有公開這個庫的 Facelets 標記庫。有些主要的組件庫(例如 Oracle 和 Tomahawk)的標記庫存在,但是即使這些也需要調整。我必須調整 Tomahawk 標記庫才能在應用程序中得到 Tomahawk 的日歷組件。雖然編寫導出組件的標記庫文件比較容易,但是也是件麻煩事。如果想使用新的定制組件庫,就必須編寫標記庫文件。
因為在其他實現中的問題,Facelets 看起來只能用于 MyFaces 1.1.1 和 Sun 1.2 JSF 的參考實現(Sun 的 JSF RI 1.2 還沒有正式發布)。不能把 Facelets 用于 1.1 RI。雖然可以把 MyFaces 用于 IBM WebSphere,但不能把 Facelets 用于 IBM 的實現。(如果使用最新版本的 Facelets,必須使用最新構建的 MyFaces 1.1.2,它現在還沒推出。)
還要注意的是,MyFaces 1.1 和 JSF RI 1.2 的底層機制不同。盡管如此,Facelets 試圖把這兩者的實現保持為它們當前的形式(MyFaces 1.1.2 和 JSF RI 1.2),這看起來解釋了花在 Facelets 上的大量時間。如果雙方更團結協調一點,讓 Facelets 在兩個環境中做同樣的事上少花些時間,就可以把更多時間花在改進 Facelets 上。
|
|
雖然有些缺點,我還是強烈推薦您下載 Facelets 并盡快開始使用它。Facelets 是 JSF 的未來,或者將會是,使用它可以使您在任何 JSF 暴風雨中都干爽(DRY 的雙關語,譯者注)。如果還不能在當前項目中使用它,請在下一個項目中想著它。
我已經用 Facelets 創建了復合組件、定制 Facelet 標記和內部 CRUD 框架功能的整個庫。我發現了許多構建復合組件的技巧和技術(例如,自動生成復選框、日歷組件的字段標記,或者根據綁定到組件的值綁定類型的文本字段),超過了在這樣一篇介紹性文章中能涉及的內容。相反,我把重點放在讓您了解和運行復合組件。通過在這里學到的知識,只用最少的定制功能和定制 Facelets 標記,就可以創建動人的組件。
特別感謝 Jacob Hookom,他是 Facelets 的創造者,感謝他對本文的審閱和貢獻,還要感謝 Athenz 細致入微的編輯。
|
|
描述 | 名字 | 大小 | 下載方法 |
---|---|---|---|
Facelets source code | j-facelets_code.zip | 267KB | HTTP |
Facelets source code with jars and wars | j-faceletsjarsandwars.zip | 47MB | HTTP |
|
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文 。
- “懷疑論者的 JSF: 消除關于 JSF 的 FUD”(Richard Hightower,developerWorks,2005 年 2 月):四部分的系列,捍衛 JSF 編程,解密 JSF 編程。請參閱 第 2 部分 獲得這里使用的原始在線 CD 商店示例。
- “了解 Tapestry,第 1 部分”(Brett McLaughlin,developerWorks,2006 年 1 月):一份精彩的兩部分的 Tapestry 介紹。
- “Inside Facelets Part 1: An Introduction”(Jacob Hookom,JSF 中心,2005 年 8 月):Facelets 創建者對它的思考。
-
Facelets 文檔:請參閱 Facelets 參數的完整清單。
-
Combining Spring, JSF, and Hibernate:作者 Rick Hightower 對把 Spring、JSF 和 Hibernate 結合在一起的思考。
-
Another Sleepless Night in Tucson:作者 Hightower 的 blog,他在這里思考 JSF(還有酒和廚藝)一直到凌晨。
-
Java 技術專區:數百篇 Java 編程各方面的文章。