在 J2EE Web 開發領域,Struts 作為 MVC 框架的事實標準,提供了穩健的控制層;Tiles 強化了頁面組合與版面復用能力;而 JavaServer Faces (JSF) 則帶來了豐富的元件模型、事件驅動機制與渲染器無關的 UI 呈現方式。本文將詳細示範如何透過 Struts-Faces 整合庫,並客製化關鍵類別,將這三者無縫整合,構建兼具彈性、可維護性與豐富前端體驗的企業級應用。
注意:當時這三者無法直接互通,需透過客製化類別橋接。
JSF 將「呈現邏輯」(What)與「商業邏輯」(Why/How)分離。每個 UI 元件(UIComponent)可搭配不同的渲染器(Renderer)輸出 HTML、WML 等不同格式。自訂標籤(繼承 UIComponentTag)負責將元件與渲染器關聯。
FacesServlet 作為控制器,委派 Lifecycle 實例依序執行各階段。驗證失敗會直接跳至「呈現回應」階段顯示錯誤。
透過 faces-config.xml 宣告 JavaBean(如 FlightSearchBean),容器自動實例化並置於指定範圍,免去 JSP 中 jsp:useBean 的撰寫。
Struts-Faces 由 Craig McClanahan 建立,主要組件包括:
RequestProcessor,專責處理 Faces 請求。ActionListener,將 ActionEvent 轉交給 FacesRequestProcessor。RequestProcessor。struts-faces.jar,定義元件與渲染器對應。Struts-Faces 0.4 尚未原生支援 Tiles,直接搭配會遇到以下問題,需客製化類別解決:
原因:JSF-RI 的 ViewHandlerImpl 在轉發請求前未檢查回應狀態。Tiles 內部會 include 多個 JSP,導致首次轉發後回應已提交,後續再轉發即拋出錯誤。
解決:實作自訂 STFViewHandlerImpl,覆寫 renderView(),檢查 response.isCommitted(),若已提交則 include,否則 forward。
if (!response.isCommitted()) {
rd.forward(request, response);
} else {
rd.include(request, response);
}
並透過 ServletContextListener 在 FacesServlet 初始化前,註冊帶有自訂 ViewHandler 的新 Lifecycle(ID 為 STFLifecycle),在 web.xml 設定 javax.faces.lifecycle.LIFECYCLE_ID 參數。
原因:FacesRequestProcessor 繼承自 RequestProcessor 而非 TilesRequestProcessor,無法處理 Tiles 定義名(如 /foobar.flight-search)的轉發。
解決:建立 STFRequestProcessor 繼承 TilesRequestProcessor,複製 FacesRequestProcessor 邏輯,並在 struts-config.xml 指定:
<controller processorClass="foobar.stf.application.STFRequestProcessor" />
原因:Struts-Faces 預設將表單 action 設為 JSP 頁面名稱(如 FlightSearch.jsp),而非 Struts 的 .do 路徑。導致 FormComponent.createActionForm() 找不到對應的 ActionMapping,進而無法取得 ActionForm、執行驗證與轉發。
解決:客製化三個類別:
FormComponent,覆寫 createActionForm(),使用 RequestUtils.getActionMappingName() 解析帶有 .do 後綴的路徑。新增 action 屬性區別於表單名稱。FormRenderer,修改 encodeBegin() 輸出正確的 action URL(含 Context Path 與 .do)。FormTag,覆寫 getComponentType()、getRendererType() 回傳客製化類別名稱。並在 faces-config.xml 宣告新元件與渲染器。
原因:Tiles 主版面頁面輸出的 <base href=".../MasterLayout.jsp">,導致後續頁面導航基準錯誤。
解決:客製化 STFBaseRenderer(繼承 BaseRenderer),移除 HttpServletRequest.getServletPath(),僅保留 Context Path。搭配 STFBaseTag 指定新渲染器。
| 類別/檔案 | 用途 |
|---|---|
STFViewHandlerImpl | 檢查回應提交狀態,決定 forward 或 include |
STFContextListener | 啟動時註冊自訂 Lifecycle 與 ViewHandler |
STFRequestProcessor | 繼承 TilesRequestProcessor,支援 Tiles 定義轉發 |
web.xml | 宣告 Listener、設定 LIFECYCLE_ID、FacesServlet 映射 |
STFFormComponent、STFFormRenderer、STFFormTag | 修正表單 action 路徑解析與輸出 |
STFBaseRenderer、STFBaseTag | 修正 base href 輸出 |
faces-config.xml | 宣告客製化元件與渲染器 |
struts-faces.tld | 移除原生 LifecycleListener 宣告,避免重複初始化 |
透過上述相對精簡的客製化,我們成功將 Struts 的控制層彈性、Tiles 的版面組合優勢、JSF 的豐富元件模型整合為一。此架構不僅保護既有投資,更為 J2EE Web 應用開發提供強大而靈活的基礎設施。隨著 JSF 規範成熟,未來整合將更加原生化,但本文展示的客製化思維與模式(ViewHandler、RequestProcessor、Component/Renderer/Tag 三元組)仍具參考價值。