理解 JSF 請求處理生命週期五大階段,是高效開發、除錯與擴展的關鍵。本文逐階段拆解執行邏輯與事件處理時機,並通過一個完整的 CD 庫存管理 CRUD 應用實戰,示範資料表排序、表單編輯、導航規則、Struts Tiles 整合,以及結合 JavaScript 的即時事件處理技術。
viewRoot (組件樹),綁定事件/驗證器。新視圖構建、原始視圖空樹、後視圖恢復狀態三種情況。immediate="true") 此階段即完成轉換/驗證並排隊事件;非即時組件僅取值。success)。FacesContext.renderResponse() 或 responseComplete() 提前跳轉。
圖 1 JSF 生命週期

圖 2 JSF 生命週期五階段物件狀態
技術棧:JSF 1.2、Struts Tiles 佈局、JavaScript 即時提交、Tomcat/JDK 1.5。
核心類別:
StoreManagerDelegate:業務代理 (DAO 封裝)。CD:DTO (id, title, artist, price, category, subCategory)。StoreController (別名 CDManagerBean):Backing Bean,Session Scope,連結視圖與模型。listing.jsp:CD 清單 (dataTable),可按標題/藝術家升降序排序,含「Add CD」連結、每行「Edit」連結。cdForm.jsp:新增/編輯表單,含分類單選按鈕 (即時事件) 與子分類下拉框 (動態顯示)。
圖 4 具有可排序列的 CD 清單頁面

圖 5 尚未選擇分類的 CD 表單頁面

圖 6 已經選擇了分類和子類組件的 CD 表單頁面
<!-- listing.jsp -->
<h:commandLink action="#{CDManagerBean.addNew}">
<f:verbatim>Add CD</f:verbatim>
</h:commandLink>
// StoreController.addNew()
public String addNew() {
if (subCategoryList == null) subCategoryList = new HtmlSelectOneListbox();
subCategoryList.setRendered(false);
this.cd = new CD(); // 重置表單綁定物件
return "success"; // 導航至 cdForm.jsp
}
faces-config.xml 導航規則:listing.jsp --(addNew/success)--> cdForm.jsp。
<h:panelGrid columns="3" rowClasses="row1,row2">
<h:outputLabel for="title"><h:outputText value="Title"/></h:outputLabel>
<h:inputText id="title" value="#{CDManagerBean.cd.title}" required="true"/>
<h:message for="title" styleClass="errorText"/>
... artist, price 同理 ...
<h:outputLabel for="category"><h:outputText value="Category"/></h:outputLabel>
<h:selectOneRadio id="category" value="#{CDManagerBean.cd.category}"
immediate="true" onclick="submit()"
valueChangeListener="#{CDManagerBean.categorySelected}">
<f:selectItems value="#{CDManagerBean.categories}"/>
</h:selectOneRadio>
<h:message for="category"/>
<h:outputLabel for="subcategory"><h:outputText value="Subcategory"/></h:outputLabel>
<h:selectOneListbox id="subcategory" value="#{CDManagerBean.cd.subCategory}"
binding="#{CDManagerBean.subCategoryList}">
<f:selectItems value="#{CDManagerBean.subCategories}"/>
</h:selectOneListbox>
<h:message for="subcategory"/>
</h:panelGrid>
<h:commandButton id="submitAdd" action="#{CDManagerBean.addCD}" value="Add CD"
rendered="#{not CDManagerBean.editMode}"/>
<h:commandButton id="submitUpdate" action="#{CDManagerBean.updateCD}" value="Update CD"
rendered="#{CDManagerBean.editMode}"/>
// StoreController - DataModel 封裝 List
private DataModel cdModel = new ListDataModel();
{ cdModel.setWrappedData(store.findTitleAsc()); }
public DataModel getCds() { return cdModel; }
<!-- listing.jsp dataTable -->
<h:dataTable id="items" value="#{CDManagerBean.cds}" var="cd"
rowClasses="oddRow,evenRow" headerClass="tableHeader">
<h:column>
<f:facet name="header">
<h:panelGroup>
<h:outputText value="Title"/>
<h:commandLink action="#{CDManagerBean.sortTitleAsc}"><h:outputText value="asc"/></h:commandLink>,
<h:commandLink action="#{CDManagerBean.sortTitleDec}"><h:outputText value="dec"/></h:commandLink>
</h:panelGroup>
</f:facet>
<h:commandLink action="#{CDManagerBean.editCD}"><h:outputText value="#{cd.title}"/></h:commandLink>
</h:column>
... artist, price 欄位 ...
</h:dataTable>
// 編輯動作:取得當前行資料,載入完整物件,啟用子分類列表
public String editCD() {
this.cd = (CD) cdModel.getRowData(); // DataModel 直接取得選中行物件
this.cd = store.getCDById(cd.getId());
if (cd.getCategory() != null && !"".equals(cd.getCategory())) {
subCategoryList.setRendered(true);
subCategories = getSubcategoriesList(cd.getCategory());
} else subCategoryList.setRendered(false);
editMode = true;
return "success"; // 導航至 cdForm.jsp (同頁面,editMode=true 顯示 Update 按鈕)
}
核心機制:immediate="true" 使組件在「應用請求值」階段即完成轉換/驗證並觸發 ValueChangeListener,跳過後續階段直接進入「進行響應」 (FacesContext.renderResponse()),避免整個表單驗證。
<h:selectOneRadio id="category" value="#{CDManagerBean.cd.category}"
immediate="true" onclick="submit()"
valueChangeListener="#{CDManagerBean.categorySelected}">
<f:selectItems value="#{CDManagerBean.categories}"/>
</h:selectOneRadio>
// 事件處理器
public void categorySelected(ValueChangeEvent event) {
subCategoryList.setRendered(true);
String newCat = (String) event.getNewValue();
if (newCat != null) subCategories = getSubcategoriesList(newCat);
FacesContext.getCurrentInstance().renderResponse(); // 強制跳轉渲染階段
}
組件綁定:binding="#{CDManagerBean.subCategoryList}" 將 HtmlSelectOneListbox 實例注入 Backing Bean,以便程式化控制 setRendered()。
public String sortTitleAsc() {
cdModel.setWrappedData(store.findTitleAsc());
return "asc"; // faces-config.xml 無映射 → 重新載入當前視圖
}
public String sortTitleDec() {
cdModel.setWrappedData(store.findTitleDec());
return "dec";
}
回傳未映射的邏輯結果 (asc/dec) 導致視圖重渲染,資料表即時反映排序後資料。
雖非 JSF 必需,Tiles 可為所有 JSF 頁面提供一致外觀 (Header/Footer/Menu)。cdForm.jsp、listing.jsp 皆作為 Tiles 定義的 body 區塊嵌入。
本文實戰演示了生命週期五階段在真實 CRUD 應用中的協同:表單提交走完整週期,即時事件縮短週期,排序操作利用未映射結果重渲染視圖。DataModel 簡化行選取,binding 實現組件程式化控制,panelGrid + CSS 實現彈性佈局。下一篇將深入類型轉換、驗證器開發與進階組件特性。