JavaServer Faces (JSF) 作為 Java EE 官方 Web UI 標準,提供了基於組件的事件驅動開發模型,但規范明確宣稱:JSF 不是完整的 Web 應用框架,缺乏會話狀態管理、RESTful URL、書簽支持、異常處理、Ajax 整合等關鍵能力。JBoss Seam(以下簡稱 Seam)應運而生,作為 JSF 的「最佳搭檔」,通過擴展 JSF 生命周期、引入雙向注入、會話上下文、頁面動作、工廠模式、異常處理、Ajax Remoting 等機制,將 JSF 從「組件庫」提升為「企業級應用框架」。本系列首篇將系統剖析 Seam 核心架構、配置啟動、組件模型、與 Spring/EJB3 的集成策略。
| JSF 痛點 | Seam 解決方案 |
|---|---|
| 依賴 HTTP Session 傳遞多頁數據,導致內存洩漏、多標簽頁衝突 | 引入 Conversation Scope(會話上下文),綁定瀏覽器標簽頁,自動生命周期管理 |
| 無原生書簽/RESTful URL 支持,頁面導航依賴 POST-Redirect-GET | 頁面動作 + s:link/s:button 實現 GET 請求導航,URL 可收藏、可分享 |
| 異常處理僅依賴 Servlet 容器 error-page,無法回滾事務、無法訪問 FacesContext | 註解/配置驅動異常處理,支援 HTTP 狀態碼、重定向、會話終止、用戶友好提示 |
| 大量 XML 配置(faces-config.xml, pages.xml) | Java 5 註解(@Name, @In, @Out, @Factory, @Observer 等)替代 XML,約定優於配置 |
| Ajax 整合困難,需自定義 PhaseListener/Renderer | @WebRemote + Ajax4JSF 實現 JavaScript 直接調用服務端組件方法,支援局部渲染 |
Seam 組件本質是帶有 @Name 註解的 POJO,容器負責實例化、生命周期、上下文綁定、依賴注入。**不強制要求 EJB3 容器**,可在 Tomcat/Jetty 等 Servlet 容器中運行。若需 EJB3 服務(事務、安全、遠程調用),可無縫集成。
| 註解 | 作用 | 示例 |
|---|---|---|
@Name("userManager") | 聲明組件名稱,綁定到上下文變量 #{userManager} | @Name("userManager") public class UserManager {} |
@Scope(ScopeType.CONVERSATION) | 指定作用域(默認 CONVERSATION) | @Scope(CONVERSATION) @Name("cart") public class ShoppingCart {} |
@In / @Out | 雙向注入:@In 注入上下文變量,@Out 注出到上下文 | @In User user; @Out Order order; |
@Factory("users") | 工廠方法:首次請求 #{users} 時調用,結果緩存至上下文 | @Factory("users") public List |
@DataModel / @DataModelSelection | 綁定數據表格選中行,自動注入/注出 | @DataModel List |
@Observer("userSaved") | 事件觀察者:接收 Events.instance().raiseEvent("userSaved") | @Observer("userSaved") public void onUserSaved() { log.info("User saved"); } |
@WebRemote | 暴露方法供 JavaScript 遠程調用(Ajax Remoting) | @WebRemote public String validateUsername(String name) { return dao.exists(name) ? "taken" : "ok"; } |
@Name("addressManager")
@Scope(ScopeType.CONVERSATION)
public class AddressManagerBean {
@DataModel
private List addresses;
@DataModelSelection
@Out(required = false)
private Address selectedAddress;
@Factory("addresses")
public void loadAddresses() {
this.addresses = addressService.findAll();
}
public String showDetail() {
return "/address.xhtml"; // 導航結果
}
public String list() {
return "/addresses.xhtml";
}
}
jboss-seam.jar, jboss-seam-ui.jar, javassist.jar, hibernate-validator.jar, javax.persistence-api.jar 等。<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>
<lifecycle>
<phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
</lifecycle>
seam.properties:觸發組件掃描,優化啟動性能。許多項目已投入 Spring 管理服務層/DAO,Seam 提供無縫橋接:
DelegatingVariableResolver 或 SpringBeanFacesELResolver)。@In("#{springBeanName}") 注入 Spring Bean。@Name("orderManager")
public class OrderManagerBean {
@In("#{orderService}") // 引用 Spring 容器中名為 orderService 的 Bean
private OrderService orderService;
@In("#{userDao}")
private UserDao userDao; // 甚至可直接注入 DAO(不推薦,應注入 Service)
}
未來版本將提供 Spring 專用命名空間簡化配置。此模式實現:Seam 管理 Web 狀態與導航,Spring 管理業務邏輯與數據訪問,部署目標僅需 Servlet 容器。
Seam 通過 SeamPhaseListener 植入 JSF 六大階段:
關鍵增強點:頁面動作(在 Render Response 前執行)、工廠數據懶加載、Conversation 狀態跨請求保持、異常捕獲與轉換、Ajax Remoting 方法暴露。
在 WEB-INF/pages.xml 或 viewId.page.xml 中聲明:
<page view-id="/product/detail.xhtml">
<action execute="#{productManager.loadProduct(productId)}" />
<param name="productId" value="#{productManager.productId}" />
<navigation from-action="#{productManager.loadProduct}">
<rule if-outcome="notFound">
<redirect view-id="/error/404.xhtml" />
</rule>
</navigation>
</page>
優勢:
#{productManager.productId})。<s:link>, <s:button> 生成 GET 請求鏈接,天然支援書簽、SEO、瀏覽器歷史記錄。/product/123 → /product/detail.xhtml?productId=123。解決 JSF「Getter 多次調用導致重複查詢」痛點。@Factory("addresses") 標註的方法在 #{addresses} 首次被 EL 解析時執行一次,結果存入當前作用域(默認 Conversation),後續直接返回緩存對象。配合 Conversation 作用域,實現**請求級/會話級數據緩存**,極大減輕數據庫壓力。
Seam 以極低的接入成本(4 個 JAR + 3 個配置項 + 1 個空文件),為 JSF 注入了企業級應用框架靈魂:**有狀態組件模型、雙向注入、Conversation 上下文、頁面動作、工廠模式、優雅異常處理、Ajax Remoting**。下一篇將深入剖析 Conversation Scope 實現原理、異常處理最佳實踐、Ajax4JSF 整合細節、生產部署調優**,敬請期待。