Seam: 為 JSF 量身定做的應用程式框架

梧桐雨—168發表於2008-04-19
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

關於 Shale

Shale 有一段並不美妙的歷史。它因 Struts-JSF 整合包而生,但是後來受到 Struts 開發人員的冷落。如今,它成了專用於 JSF 的一個頂級 Apache 專案。

我認為 Shale 的問題在於將自身定位為一組鬆散耦合的服務,這等於將整合的負擔壓在了開發人員肩上。Shale 的 Java 5 語言增強是不錯,但是它們都被包裝在一個擴充套件包中。檢視控制器通過命名約定與一個模板耦合,這也帶來很多限制。 Shale 應用程式控制器和對話方塊管理器都是大型增件,它們似乎為標準 JSF 生命週期減輕了很多負擔。

Seam 可以提供 Shale 中所有的特性,而且將這些特性放在一個良好整合的、緊密耦合的包中。

剛剛閱讀到關於 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。雖然這兩對都有很強的互補性,但是每一對的兩者之間都不是相互依賴的。

JBoss Seam 與 JSR 299

JBoss Seam 是一種開源應用程式框架,其目的是提高 JSF 與業務元件(例如 EJB 3 和 Spring bean)之間的整合。Seam 能夠跨 Web 環境中的不同上下文管理元件,並且幾乎避免了在開發 JSF 應用程式時進行 XML 配置。該專案是 Hibernate 的創立者 Gavin King 的傑作,目前還在 JBoss 實驗室中。

最近,JBoss 向 JCP 提交了一個建議,要求標準化 Seam 背後的概念。該建議被接受為 JSR 299, Web Beans。這個規範的目的是統一 JSF 管理的 bean 元件模型和 EJB3 元件模型,形成一種明顯簡化的用於基於 Web 的應用程式程式設計模型。

對 EJB3 的考慮

正如我將要解釋的那樣,Seam 通過一些有價值的 hook 和元件管理程式 擴充套件預設 JSF 生命週期。還可以完全獨立於 EJB3 使用 Seam。但是要記住,和 EJB3 一樣,Seam 依賴於 JDK 5 註釋後設資料進行元件宣告,因此使用 Seam 時,還需要同時使用相容 Java 5 的 JVM。圖 1 顯示了一個 Seam POJO 實現的應用程式堆疊:


圖 1. 一個 Seam POJO 應用程式堆疊
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 庫
JAR 檔案清單

配置 Seam

接下來的步驟是在 web.xml 檔案中安裝 servlet 監聽器類。該監聽器在部署應用程式時初始化 Seam。


清單 1. Seam servlet 監聽器配置
                

  org.jboss.seam.servlet.SeamListener


接下來,將 JSF phase 監聽器新增到 faces-config.xml 檔案中,如清單 2 所示。該監聽器將 Seam 整合到標準 JSF 生命週期中。(圖 3 大致描繪了整合到這個生命週期中的 Seam 增強。)


清單 2. Seam phase 監聽器配置
                

  org.jboss.seam.jsf.SeamPhaseListener


最後,將一個空的 seam.properties 檔案放在類路徑的根下,以便指示 Seam 進行載入,如清單 3 所示。這個空白檔案被用作一個 JVM 類載入器優化,使 Seam 在類路徑下更小的區域內搜尋元件,從而大大減少載入時間。


清單 3. Seam 屬性檔案
                
# The mere presence 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
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 prepare 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 生命週期增強
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 上實現一個 “prerender” 方法。然而,在元件框架中,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:links:button,用以處理細節。這兩個標記分別對應於 JSF 中的 h:commandLinkh: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 的優點還只是一個開始。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/13270562/viewspace-244088/,如需轉載,請註明出處,否則將追究法律責任。

相關文章