JavaServer Faces (JSF) 是用於 Java™ Web 應用程序的第一個標准化的用戶界面框架。
而 Seam 是一個擴展 JSF 的強大的應用程序框架。在這個由三部分組成的新系列中的第一篇文章中,發現這兩種框架之間的互補性。Dan Allen 介紹了 Seam 對 JSF 生命周期的增強,包括上下文狀態管理、 RESTful URL、Ajax remoting、適當的異常處理和約定優於配置。
JSF 正開始憑借其 Java Web 標准的地位主導 Java Web 應用程序市場。隨著更多的開發人員受托使用 JSF 作為基礎來架構應用程序,他們發現 JSF 的核心規范中清楚地說明: JSF 不是為成為一個完整的 Web 應用程序框架而設計的。相反,它提供一個健壯的、事件驅動的 API 和 UI 組件庫,用於構建更復雜的應用程序框架。
我在尋找用於彌補 JSF 的組件驅動架構的擴展時,發現 Shale 和 Struts 2 都有不足之處。我排除了 Struts 2,因為它將 JSF 看作是面向更大范圍的設計。而 Shale 似乎更靠近一些,它基本上是基於 JSF,但是 對此我持保留意見。相反,JBoss Seam 是一個全面的應用程序框架,它構建在 JSF 的基礎上,但是並沒有損害它的核心目標。
這個由三部分組成的系列將介紹 Seam 應用程序框架,演示它的優點,並希望使您相信它與 JSF 是開發 Java 企業應用程序的極好的組合。在閱讀本系列之前,如果您想下載 Seam,那麼請閱讀 參考資料 一節。
尋找 Seam
剛剛閱讀到關於 JBoss Seam 的文章(見 參考資料)的第一頁,我就知道 Seam 正是我要找的項目。Seam 的開發人員,尤其是 Gavin King,在經過足夠多的、實際的開發之後,知道一個 Web 應用程序框架必須從一開始就攻破難題,包括上下文狀態管理、RESTful 和用戶友好的 URL、Ajax remoting、適當的異常處理和約定優於配置。令 Java 開發人員欣喜的是,Seam 可以滿足所有這些需求,甚至可以滿足更多需求。如果您正使用 JSF,並且還沒聽說過 Seam,那麼我強烈建議您看看 Seam 的參考文檔(見 參考資料)。Seam 附帶的手冊就是最好的資料!
盡管 Seam 顯然非常適合作為 JSF 的補充,但是在激烈的競爭環境中,它遭到了一定程度的輕視。當今市場中充斥著各種各樣的 Web 應用程序框架 —— 包括 Shale 和 Struts 2,新來者往往不受重視,Seam 還沒有在主流行列站穩腳跟。 Seam 沒有很快流行的另一個原因是關於這種框架的某些流言使 Java 開發人員沒能認識到它的直接優點。
我要粉碎的一個流言是:Seam 只有和 EJB 3 一起使用時才有用,或者說在使用 Seam 開發應用程序時需要一個 EJB3 容器。實際上,Seam 的文檔清楚地駁斥了這種誤解:"Seam 並不要求組件是 EJB,甚至在沒有兼容 EJB 3.0 的容器時也能使用。" 如果說只有在使用 EJB 3 的同時才能使用 Seam,那麼無異於說只有在使用 Hibernate 的同時才能使用 Spring。雖然這兩對都有很強的互補性,但是每一對的兩者之間都不是相互依賴的。
對 EJB3 的考慮
正如我將要解釋的那樣,Seam 通過一些有價值的 hook 和組件管理進程 擴展默認 JSF 生命周期。還可以完全獨立於 EJB3 使用 Seam。但是要記住,和 EJB3 一樣,Seam 依賴於 JDK 5 注釋元數據進行組件聲明,因此使用 Seam 時,還需要同時使用兼容 Java 5 的 JVM。圖 1 顯示了一個 Seam POJO 實現的應用程序堆棧:
圖 1. 一個 Seam POJO 應用程序堆棧
實際上,即使完全不引用 EJB 3 jar 或描述符文件,也可以使用 Seam 的很多功能。當和 POJO 一起使用 Seam 時,該框架保留對組件實例化的完全控制,並且不要求任何專門的配置。Seam 負責大多數 Java 5 注釋處理,而不需要依賴於 EJB 3 中的任何機制。的確 依賴於 EJB3 容器的一組有限的注釋則是專用於那個環境的。在某些情況下,將 Seam 集成到一個沒有 EJB 3 耦合的 IT 投資中可以獲得更好的成本效益。如何使用 Seam 視個人偏好而定。
配置並使用
如今有那麼多種 Java 框架,每天只有有限的那麼多小時,顯然,如果 Seam 難於集成的話,它就無立足之地。幸運的是,將 Seam 添加到項目中很簡單。因為 JSF 生命周期仍然是 Seam 應用程序的中心部分,所以不需要經歷一個再訓練時期。只需添加 4 個 jar 文件,注冊一個 servlet 監聽器和一個 JSF phase 監聽器,最後再加上一個空白的 Java 屬性文件。完成這些設置後,就可以一次性地將本地 JSF 應用程序轉移到 Seam 管理的 bean 上。
要開始使用 Seam,首先需要將所需的 jar 文件添加到項目中。如果您當前不是使用 Hibernate,或者還沒有升級到最新的版本,那麼在設置時需要執行一個額外的步驟。這裡需要包含來自 Hibernate 3.2 distribution 的 jar,以及它的眾多的依賴項。Seam 還使用 Hibernate 注釋用於數據驗證,所以除了主 Hibernate jar 之外,還必須包括那個擴展 jar。需要的 Seam 發行版中的庫有 jboss-seam.jar 和 jboss-seam-ui.jar,以及兩個支持庫:Javassist(用於 Java 的加載時反射系統)和 Java Persistence API。圖 2 中的項目樹說明了一個 Seam 項目中的 jar 集合。該圖中顯示的大多數附加庫支持 JSF 的 MyFaces 實現。
圖 2. Seam 項目中的 jar 庫
配置 Seam
接下來的步驟是在 web.xml 文件中安裝 servlet 監聽器類。該監聽器在部署應用程序時初始化 Seam。
清單 1. Seam servlet 監聽器配置
<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
接下來,將 JSF phase 監聽器添加到 faces-config.xml 文件中,如清單 2 所示。該監聽器將 Seam 集成到標准 JSF 生命周期中。(圖 3 大致描繪了集成到這個生命周期中的 Seam 增強。)
清單 2. Seam phase 監聽器配置
<lifecycle>
<phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
</lifecycle>
最後,將一個空的 seam.properties 文件放在類路徑的根下,以便指示 Seam 進行加載,如清單 3 所示。這個空白文件被用作一個 JVM 類加載器優化,使 Seam 在類路徑下更小的區域內搜索組件,從而大大減少加載時間。
清單 3. Seam 屬性文件
# The mere divsence of this file triggers Seam to load
# It can also be used to tune parameters on configurable Seam components
當然,在這種最小設置中,Seam 的很多特性是不可用的。以上說明只是為了演示 Seam 很少涉足入門級使用。例如,Seam 包括一個 servlet 過濾器,該過濾器擴展 JSF 生命周期以外的 Seam 特性。 servlet 過濾器的用法包括與非 JSF 請求集成,通過重定向傳播 conversation,以及管理文件上傳。請參閱 參考資料,看看 Seam 參考文檔,其中討論了用於控制附加功能的配置文件 —— 特別是 EJB3 集成。
與 Seam 關聯
與典型的 JSF 配置過程相比,使用 Seam 開發受管 bean 非常容易。為了將 bean 暴露到 JSF 生命周期中,只需在類定義的上面添加一個簡單的注釋 @Name。然後,Seam 會負責控制組件的可見性和生命周期。最妙的是,不需要在 faces-config.xml 文件中定義這個 bean。
清單 4 顯示了 @Name 注釋以及 @DataModel、@DataModelSelection、@In、@Out 和 @Factory。這些注釋使變量能夠在視圖模板和 Seam 組件之間雙向流動。
在 Seam 用語中,這個動作被稱作雙射(bijection,即 bidirectional injection 的簡稱)。當注出(outject)屬性數據時,視圖可以通過名稱找到它。在 postback 或者組件初始化時,數據被注入(inject)到一個組件中。後者是著名的控制反轉(inversion of control,IOC)模式的一種實現,可用於連接委托對象。傳統 IOC 與 Seam 的雙射之間的主要不同點在於,雙射使長期作用域中的組件可以引用短期作用域中的組件。可以進行這種連接是因為 Seam 在調用組件時(而不是啟動容器時)解析依賴項。雙射是有狀態組件開發的基礎。
顯然,清單 4 中的 POJO bean 只是簡單地演示了 Seam 的用法。隨著本系列討論的繼續,我將探索另外的方法來實現 Seam。
清單 4. 一個典型的 Seam POJO bean
@Name("addressManager")
public class AddressManagerBean {
@DataModel private List<Address> addresses;
@DataModelSelection @Out( required = false )
private Address selectedAddress; @Factory( value = "addresses" )
public void loadAddress() {
// logic to load addresses into this.addresses
}
public String showDetail() {
// no work needs to be done to divpare the selected address
return "/address.jspx";
}
public String list() {
return "/addresses.jspx";
}}
Spring 的注入
為了使用由一個已有的 Spring 容器管理的服務層對象中的投資,需要將所有處理相關業務邏輯的 Spring bean 注入到 Seam 組件中。首先需要確保已經配置了 Spring-JSF 集成,它由 Spring 框架附帶的一個定制變量解析器進行處理(見 參考資料)。有了這座橋梁,Spring 與 Seam 的集成就很簡單,只需使用 @In Java 5 注釋和一個值綁定表達式,以表明 Seam 組件的哪些屬性應該接收一個 Spring bean 的注入,如清單 5 所示。(將來版本的 Seam 將包括用於 Spring 的一個定制的名稱空間,以滿足值綁定表達式的需要。)
清單 5. 注入一個 Spring bean
@Name("addressManager")
public class AddressManagerBean {
@In("#{addressService}")
private AddressService addressService;
}
這個例子設置支持使用以輕量級容器(這裡就是 Spring)配置的無狀態服務和數據訪問(DAO)層。因為不需要 EJB3,所以部署的目標可以是任何基本的 servlet 容器。
現在,您對 Seam-JSF 實現有了一個初步的印象,接下來我將更深入地探討我在使用 JSF 時遇到的挑戰,以及 Seam 如何緩解這些挑戰。
再談 JSF
為了充分理解 Seam 為 JSF 帶來了什麼,就需要理解 JSF 與其他流行的基於 Web 的編程方法有何不同。JSF 是實現傳統的 Model-View-Controller (MVC) 架構的一種 Web 框架。不同之處在於,它采用該模式的一種特別豐富的實現。與 Model 2 或者 Struts、WebWork 和 Spring MVC 之類的框架中使用的 “push-MVC” 方法相比,JSF 中的 MVC 實現更接近於傳統的 GUI 應用程序。前面那些框架被歸類為基於動作的(action-based),而 JSF 則屬於基於組件模型 的新的框架家族中的一員。
如果將基於動作的框架想象為使用 “push” 模型,而將組件框架想象為使用 “pull” 模型,那麼這種區別就很容易理解了。組件框架中的控制器不是預先處理頁面請求(在基於動作的框架中控制器就是這麼做的),而是在請求生命周期中作出讓步,在視圖中調用數據提供方法。此外,頁面上的元素,即組件被綁定到事件,這些事件可以觸發服務器端對象(激活後)的方法調用,從而導致重新顯示相同的視圖,或者轉換到另一個頁面。因此,組件框架也被歸類為事件驅動的。組件框架抽象出用於事件通信的底層請求-響應協議。
事件驅動方法的優點是可以減少單個方法在呈現視圖時需要預先做的工作。在組件框架中,UI 事件或解析的值綁定表達式直接導致方法調用。
一個應用程序即使只達到中度成熟,它通常也需要在任何給定頁面上使用很多不相關的活動。如果將對所有這些信息的管理全部放入一個動作或者一個動作鏈中,那麼勢必給維護帶來極大的困擾。因此,開發人員常常發現他們的代碼偏離了面向對象模型的軌道,反而陷入了過程編程模型的泥潭。相反,組件框架將這種工作隔離出來,更自然地加強了對象的角色和責任。
Seam 與 JSF
對於 JSF 和組件框架的基礎已經介紹得差不多了。實際上 —— 很多 Java 開發人員最近發現 —— 轉移到 JSF 並非總是一帆風順。采用組件模型會帶來一些全新的問題,首要的一個問題是您通常需要試著使應用程序符合基於動作的 Web。很多時候,JSF 需要具有像基於動作的框架那樣的行為,但是在標准 JSF 中這是不可行的,至少不為每個請求使用 phase 監聽器就不行。
JSF 的其他主要缺點還包括對 HTTP 會話的依賴過重(尤其是在一序列的頁面之間傳播數據時),簡陋的異常處理,缺少書簽支持,以及太多的 XML 配置。通過與 JSF 自然地集成,同時加入 JSF 規范委員會放棄的或者忽略掉的新功能,Seam 解決了很多這樣的問題。Seam 的框架鼓勵使用緊湊的、易讀的、可重用的代碼,並且避免了所有為解決上述問題而常常加入的 “粘連(glue)” 邏輯。圖 3 涵蓋了 JSF 生命周期中用於簡化應用程序代碼的大多數 Seam 擴展點:
圖 3. Seam 生命周期增強
讓我們來考慮其中一些增強,因為它們適用於 JSF 開發中一些常見的挑戰。
並不復雜的配置
Seam 演示了 Java 5 注釋的一個非常實用的用法。Seam 的部署掃描程序檢查所有包含 seam.properties 文件的歸檔文件,並為所有標有 @Name 注釋的類創建一個 Seam 組件。由於 Java 語言缺乏用於在代碼級添加元數據的一種公共語法,因此需要設計很多 XML 配置。當 Java 5 規范中加入注釋後,就獲得了一個更好的解決方案。由於大多數 backing bean 是為了在特定應用程序中使用而開發的,因此沒有理由將這些 bean 的配置 “抽象” 到類本身以外的任何文件中。附帶的好處是,您可以少處理一個文件。Seam 提供了一組完整的注釋來幫助將 bean 集成到 JSF 生命周期中。清單 4 顯示了其中一些。
頁面動作和 RESTful URL
在不使用組件框架的情況下,另一個必須解決的熟悉的問題是預先處理每個請求,就像在基於動作的框架中那樣。受此影響的用例是 RESTful URL、書簽支持、通過 URL 模式獲得的安全性以及頁面流驗證等。這也是學習使用 JSF 的開發人員容易感到困惑的主要原因之一。有些 JSF 供應商通過用開發人員工具提供 onPageLoad 功能來繞過這個問題(見 參考資料),但這不是核心規范的一部分。
當用戶直接從書簽(比如)請求一個商品詳細信息屏幕時,通常會發生什麼事情呢?由於 JSF 控制器采取被動方式,當頁面開始呈現時,即使明顯沒有目標數據,也不能將用戶重新帶到邏輯流的開始處。相反,這種情況下只能顯示一個空頁面,其中只有一些空值或其他可能存在的假信號。
首先,您可能會本能地想要在頁面的主 backing bean 上實現一個 “divrender” 方法。然而,在組件框架中,backing bean 與頁面之間的關系並不一定都是一對一的。每個頁面可能依賴於多個 backing bean,每個那樣的 bean 也可能在多個不同的頁面上使用。必須用某種方式將一個視圖 ID(例如 /user/detail.jspx)與一個或多個方法關聯起來,當選擇呈現相應的視圖模板時就調用這個(些)方法。您可以使用 phase-listener 方法,但是這仍然需要定制的邏輯來確定對於當前視圖和階段是否應該執行該功能。這種解決方案不但會導致很多冗余邏輯,而且會將視圖 ID(很可能是應用程序中最不確定的部分)硬編碼到編譯後的 Java 代碼中。
頁面動作來幫忙
Seam 的頁面動作可以幫助您預先攔截呈現的假信號。頁面動作是使用方法綁定指定的,方法綁定在進入頁面時、Render Response 階段之前執行。對於 /WEB-INF/pages.xml 配置文件中一個給定的視圖 ID,可以配置任意數量的方法綁定。(或者,可以通過將它們放在視圖模板鄰近的一個文件中,復制它的名稱,但是將文件擴展名換為 *.page.xml,從而分解每個頁面的定義)。對於頁面動作,XML 是有必要的,因為視圖 ID 非常容易變化。就像 JSF 通過 Apply Request Values 階段的值綁定將 post 數據映射到模型對象一樣, Seam 可以通過執行頁面動作之前的值綁定將任意請求參數映射到模型對象。這些請求參數注入的配置嵌套在頁面動作 XML 聲明中。如果頁面動作方法調用返回一個非空字符串值,則 Seam 將其當作一個導航事件。因此,不必遷移到一個完整的基於動作的框架中,仍然可以比得上最特別的特性。Seam 包括很多內置的頁面動作,它們通常跨應用程序使用。其中包括用於驗證 conversation 是否建立的一個動作;可以啟動、嵌套和結束 conversation 的動作;處理預期異常的動作;以及確保適當的憑證的動作。
頁面動作是啟用對 JSF 的書簽支持的關鍵。Seam 的創立者允許在進入頁面時請求參數 actionMethod 觸發一個方法調用,從而利用了這一特性。更妙的是,您不需要做任何額外的工作就能為書簽創建鏈接。 Seam 提供了兩個組件標記:s:link 和 s:button,用以處理細節。這兩個標記分別對應於 JSF 中的 h:commandLink 和 h:commandButton。不同之處在於,Seam 組件標記組裝的鏈接使用一個 HTTP GET 操作發出請求,而不是使用 JSF 的 HTTP POST 表單提交模型表示。因此,Seam 創建的鏈接對書簽更 “友好”,對於開發人員來說更方便。
您可能還注意到,當使用頁面動作時,地址欄中的 URL 對應於正在顯示的頁面,而不總是背後的一個頁面。(後一種情況之所以會發生,是因為 JSF 將表單配置為 post 回生成它們的 URL。地址欄沒有更新,以反映執行動作後的新視圖,因為 JSF 通過一個服務器端重定向使之前進。)如果您想演示頁面動作的靈活性,那麼可以使用它們來創建 RESTful URL(例如 /faces/product/show/10)。為此,將頁面動作方法映射到視圖 ID“/product/show/*”,其中 /faces 前綴是 JSF servlet 映射部分。然後,該頁面動作方法利用請求 URL,以判斷數據類型和數據標識符,加載數據,然後導航到適當的模板。這個例子很明顯地演示了 JSF 請求 URL 與視圖模板之間並不一定存在一對一的關系。
工廠組件
JSF 最大的一個失敗是沒有在用戶觸發的動作或動作監聽器方法以外的其他地方提供可靠的機會來為視圖准備數據。將邏輯放在一個動作方法中並不能保證該邏輯在視圖呈現之前得到執行,因為頁面視圖並不總是在用戶觸發的事件之後。
例如,當一個 JSF 應用程序的 URL 第一次被請求時,會發生什麼情況?如果需要在該頁面上顯示從服務層獲得的一組數據,那麼在 JSF 生命周期中始終沒有好的機會來取數據。您可能會認為,可以將邏輯放在映射到視圖中值綁定表達式的 backing bean 的 getter 方法中。但是,每當 JSF 解析那個表達式時,就會觸發另一個調用,新的調用又會訪問服務層。即使頁面上只有少數幾個組件,getter 方法也可能被推後數次執行。顯然,就性能而言這不是最優的。即使通過使用受管 bean 上的私有屬性維護狀態,每當面對那樣的情況時,仍然必須增加額外的管道。一種解決方案是使用 Seam 的頁面動作。但是由於這種任務是如此常見,Seam 提供了一個更加容易的解決方案。
Seam 引入了工廠數據提供者(factory data provider)的概念,工廠數據提供者由 @Factory Java 5 注釋指定。雖然有兩種方法配置工廠,但是最終結果是同樣的數據只需在第一次被請求時准備一次。 Seam 確保隨後對相同數據的請求將直接返回之前創建的結果集,而不必再一次觸發對查找方法的調用。通過與 conversation 相結合,工廠數據提供者成為實現數據短期緩存的非常強大的特性,否則,取這些數據的代價可能較高。在 JSF 不負責減少它解析一個值綁定表達式的次數的情況下,Seam 的工廠特性常常變得非常方便。
有狀態 conversation
關於 JSF 很容易引起困惑的一個地方是它的狀態管理功能。JSF 規范解釋了在接收一個動作之後頁面是如何 “恢復(restored)” 的,在此期間時間事件要進行排隊,選擇要注冊。仔細研究規范中的用詞可以發現,雖然在 postback 上恢復了組件樹,但是那些組件使用的 backing bean 數據並沒有被恢復。組件只是以字符串文字的形式存儲值綁定(使用 #{value} 語法的 EL 表達式),只在運行時解析底層數據。這意味著如果一個值是短期作用域存儲的,例如頁面或請求作用域,那麼當 JSF 生命周期到達 Restore View 階段時,這個值將消失。
不將值綁定數據存儲在組件樹中的一個最值得注意的不利方面是虛幻事件效果(見 參考資料),這是由 UIData 家族中的臨時父組件導致的。如果一個值綁定表達式引用的模型數據不再可用,或者在組件樹被恢復之前發生了更改,那麼組件樹的一些部分將被撤銷。如果在這些被撤銷的分支中,有一個組件中觸發了一個事件,那麼它將不能被發現,而且這種事件丟失情況是難於覺察的。(只是隊列開發人員可能會驚呼 “為什麼我的動作沒有被調用?”)
雖然丟失的事件看上去像是異常狀況,但並不會導致 JSF 生命周期中出現紅色標志。因為這些組件依賴底層數據,以保持穩定和適當地被恢復,所以 JSF 難於知道丟失了什麼。
不幸的是,JSF 規范天真地引導開發人員將大多數 backing bean 放入 conversation 作用域中 —— 甚至可以在 “方便的” 作用域內調用它。然後,服務器管理員則必須處理由此導致的 “內存溢出” 錯誤,集群環境中的服務器相似性,以及服務器重啟時的串行化異常。MyFaces Tomahawk 項目通過 t:saveState 標記的形式提供了對虛幻事件的一個解決方案。MyFaces 標記允許將數據(包括整個 backing bean)存儲為組件樹的一部分,而僅僅是值綁定。然而,這種解決方案有些簡陋,很像使用隱藏的表單字段在請求之間傳遞值。它還造成視圖與控制器之間緊密耦合。Seam 的創立者意識到,Java Servlet 規范中三個內置的上下文(請求、會話和應用程序)不足以構成數據驅動的 Web 應用程序的全部作用域。在 Seam 中,他們引入了 conversation 作用域,這是一個有狀態作用域,由頁面流的起止點界定。
Seam 的 conversation 作用域
“Seam 強調使用有狀態組件。” Seam 參考文檔中的這句話體現了 Seam 的核心思想。很長一段時間內,關於 Web 應用程序的看法是,它們是無狀態的 —— 這種思想一定程度上要歸因於 HTTP 協議的無狀態性質。大多數框架為了迎合這一點,在結束頁面呈現之前提供 one-shot-processing。這種方法導致很大的阻力,因為任何大的應用程序都需要長時間運行的 conversation 來滿足某些用例。需要有狀態上下文的應用程序的例子有很多,例如存儲檢查過程、產品定制、多頁表單向導和很多其他基於線形交互的應用程序。雖然其中有些例子可以通過使用 URL 參數(aka RESTful URL)和隱藏字段在頁面之間遷移數據,但是這樣做對於開發人員來說有些繁雜。而且,如今這種做法已經過時了。因為大多數 Web 框架仍然在無狀態模型下操作,所以您常常發現自己走出了這種框架,而 “開辟” 出定制解決方案。
JSF 大量依賴於 HTTP 會話,試圖引入有狀態上下文。實際上,當和會話作用域的 backing bean 一起使用時,JSF 組件的行為要好得多。如果不小心設計,過度使用 HTTP 會話會導致嚴重的內存洩漏、性能瓶頸和安全問題。此外,在多標簽浏覽器環境中,使用 HTTP 會話可能導致非常奇怪的行為,破壞用戶神聖的 Back 按鈕。值得注意的是,JSF 只是與您互作讓步:它是一個有狀態 UI,處理保存和恢復組件樹的所有細節,但是它在保存和恢復數據方面沒有提供幫助。因此,JSF 帶來有狀態 UI,而您則帶來有狀態數據。不幸的是,需要由您來負責確保它們是相符的。
在 Seam 之前,使用有狀態數據的惟一方便的方式是依賴於 HTTP 會話。Seam 糾正了這個問題,它通過建立一個全新的 conversation 作用域,完善了 JSF 的狀態管理。隨著將 Seam 添加到 JSF 生命周期中,conversation 上下文與一個浏覽器窗口(或標簽頁)聯系在一起,這個浏覽器窗口(或標簽頁)由隨每個請求提交的一個標志來標識。conversation 作用域使用 HTTP 會話的一個單獨的區段在頁面之間遷移數據。記住,Seam 使用 HTTP 會話用於 conversation 持久性這一點是完全透明的。 Seam 並不是不負責任地將組件推卸到 HTTP 會話中,使其茫然地呆在那裡。相反,Seam 小心地管理那個區段的會話數據的生命周期,並且當 conversation 終止時,自動清除它們。Seam 聰明地使用雙射來允許以一種新的說明性方式使數據流入和流出一個 “Web conversation” 的每個頁面。 Seam 的 conversation 作用域同時還克服了 HTTP 會話的限制,幫助開發人員放棄使用 HTTP 會話。
異常處理
Seam 的創立者曾說過:“在異常處理方面,JSF 非常有限”。這一點顯然毫無爭議。 JSF 規范完全忽視異常管理,將責任完全放在 servlet 容器上。允許 servlet 容器處理異常的問題在於,這嚴重限制了錯誤頁面上顯示的內容,並且禁止了事務回滾。由於錯誤頁面是在請求分發器轉發之後顯示的,FacesContext 不再在作用域中,因此這時執行業務邏輯就太遲了。您的最後一線希望是使用 Servlet API,並抓住 javax.servlet.error.* 請求屬性,以搜索能表明出錯處的信息。
這一點上,Seam 再次扮演救世主,它提供了優雅的、說明性方式的異常處理。異常管理可以通過注釋指定,或者在配置文件中指定。可以將注釋 @HttpError、@Redirect 和 @ApplicationException 放在異常類的上面,表明當異常被拋出時應該采取的動作。對於那些不能修改的異常類,可以使用 XML 配置選項。Seam 中的異常管理器負責發送 HTTP 狀態碼、執行重定向、促使頁面呈現、終止 conversation 和定制出現異常時的用戶消息。由於在開始呈現響應之後,JSF 不能改變動作的過程,一些固有的限制規定了何時才能處理這些異常。通過適當使用其他 Seam 特性,例如頁面動作,可以確保大多數異常情況在呈現響應之前得到解決。
Ajax remoting
由於最後發行的 JSF 規范幾乎與 Ajax 重合,JSF 框架在異步 JavaScript 和局部頁面呈現(partial page rendering)方面幫助不大。在某些時候,甚至這兩種類型的編程甚至不一致。最終的解決辦法是建議使用 JSF PhaseListener 或組件 Renderer 來處理局部頁面更新。即使如此,這一點仍然很明顯:JSF 使得 Ajax 比過去更難於采用。有些項目,例如 ICEfaces,甚至用一個更好的、專為頁面-服務器通信設計的技術(即 direct-to-DOM rendering)來替代 JSF 生命周期。
Seam 為 JavaScript remoting(常常記在 Ajax 名下的一種技術)提供了一種獨特的方式,該方式與 Direct Web Remoting (DWR) 庫的方式大致相似。Seam 通過允許 JavaScript 直接調用服務器組件上的方法,將客戶機與服務器連在一起。Seam remoting 比 DWR 更強大,因為它可以訪問豐富的上下文組件模型,而不僅僅是一個孤立的端點。這種交互構建在 JSF 的事件驅動設計的基礎上,所以它可以更嚴格地遵從 Swing 范例。最妙的是,提供這個功能的同時並沒有增加開發方面的負擔。只需在組件的方法上加一個簡單的注釋 @WebRemote,JavaScript 就可以訪問該方法。當服務器組件的狀態被修改之後,Ajax4JSF 組件庫就可以處理局部呈現。簡言之:Seam remoting 庫使 JSF 可以實現它的創立者一向期望的交互設計。
結束語
根據您目前在 無縫集成 JSF 系列 中了解到的內容,可以毫不牽強地說在使用 JSF 的開發中不使用 Seam 是反常的。作為進一步的證明,只需看看 JSR 299, Web Beans 的投票結果(見 參考資料)。顯然,在不久的將來,Seam 會成為一個官方規范,Java EE 棧最終將提供 “顯著簡化的基於 Web 的應用程序編程模型”。這對 JSF 開發人員和 Seam 來說是一個好消息。但是,即使沒有聲明要成為一個 Java 標准,Seam 也是 JSF 的一個有價值的補充。
Seam 只需很少的設置就可以開始用於 JSF —— 而正是這一點小小的付出,就能解決 JSF 開發中的一些最麻煩的難題。回報勝於付出 —— 這裡討論的 Seam 的優點還只是一個開始。