程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 懷疑論者的JSF: JSF應用程序的生命周期

懷疑論者的JSF: JSF應用程序的生命周期

編輯:關於JAVA

與很多流行的觀點不同,我們無需了解技術工作原理的所有細節,就可以編寫 JSF 應用 程序。您只需要給自己設置一個項目,並從頭到尾不斷修修補補,這樣就可以學習到大量的 知識。另一方面,理解必要的基礎知識可以使您的開發工作更加有效 —— 而且會節省很多 時間。

在本系列 懷疑論者的 JSF 的第 2 篇文章中,我們將逐一介紹一下 JSF 請求處理生命周 期的 5 個階段。我們將介紹在每個階段中會發生什麼,以及這些階段是如何相互連接在一起 的,然後使用一個示例程序來展示實際的生命周期。隨著學習的深入,我們還將介紹如何使 用在 第 1 部分 中簡要提及的一些內置的 JSF 組件。我們還將向您介紹如何在 JSF 開發中 采用 Struts Tiles,以及如何組合使用 JSF 和 JavaScript 技術進行即時事件的處理。

正如上一篇文章中介紹的一樣,示例程序的默認編譯環境是 Maven。您可以通過點擊頁面 頂部或底部的 Code 圖標下載源代碼。為了簡單性起見,您會發現與上一篇文章中一樣的示 例設置。有關編譯環境的設置的詳細說明,請參閱 參考資料,其中包括使用 Ant(而不是 Maven)來編譯並運行示例程序的說明。

JSF 生命周期:概述

JSF 程序生命周期的 5 個階段如下(注意每個階段的事件處理):

恢復視圖

應用請求的值;處理驗證

更新模型值;處理事件

調用程序;處理事件

進行響應;處理事件

這 5 個階段顯示了 JSF 通常處理 GUI 的順序。雖然這個清單列出了每個階段中事件處 理的可能執行順序,但是 JSF 的生命周期很難是固定一成不變的。您可以通過忽略某個階段 或合並整個生命周期從而對執行順序進行修改。例如,如果一個無效的請求值被拷貝到一個 組件中,那麼當前的視圖就會重新顯示,而有些階段就可能不會執行。在這種情況中,您可 以執行一個 FacesContext.responseComplete 方法調用,將用戶重定向到一個不同的頁面上 ,然後使用請求分發器(從 FacesContext 中的請求對象中獲得)將其轉發到一個適當的 Web 資源上。另外,您可以調用 FacesContext.renderResponse 重新顯示原來的視圖。(詳 細信息請參看下面的示例程序。)

關鍵是讓生命周期構成您的開發項目,而不完全依賴於生命周期。在需要時,您可以修改 生命周期,而不用擔心破壞您的程序。在大部分情況中,您會發現 JSF 的生命周期是值得遵 守的,因為它的邏輯非常好。表單必須在任何應用程序邏輯執行之前進行驗證,並且在進行 驗證之前,必須對域中的數據進行轉換。遵守生命周期的規定,可以讓您更自由地考慮有關 驗證和轉換的問題,而不是請求處理本身的階段。有一點非常重要:其他 Web 框架也都具有 類似的生命周期;它們只不過是沒有很好地進行宣傳。

專注

有些使用 JSF 的開發者可能從來都不會編寫一個組件,也不會對框架進行任何擴展;而 另外一些人則專注於這種任務的開發。盡管 JSF 的生命周期與大部分那其他項目都是相同的 ,但是根據在項目中的角色您可以采用不同的階段。如果您更專注於通用的應用程序開發, 就可能會關注請求處理生命周期的中間階段:

應用請求值

更新模型值

調用程序

如果您專注於 JSF 組件的開發,就可能會關注於整個生命周期中的第一個階段和最後一 個階段:

恢復視圖

進行響應

在接下來的幾節中,我們將遍歷 JSF 請求處理生命周期的每個步驟,包括事件處理和驗 證。了解了每個步驟的基本知識之後,我們將簡要介紹一個示例程序,它可以展示這些步驟 如何一起使用。在開始之前,首先來看一下圖 1,這是一個有關 JSF 生命周期的圖。

圖 1. JSF 生命周期

階段 1:恢復視圖

在 JSF 生命周期的第一個階段 ——恢復視圖 —— 中,會有一個來自 FacesServlet 控 制器的請求。控制器會對請求進行考查,並提取出視圖的 ID,這是由 JSP 頁面的名字來確 定的。

JSF 框架控制器使用這個視圖 ID 來為當前的視圖查找組件。如果這個視圖尚未存在,那 麼 JSF 控制器就會創建它。如果這個視圖早已存在,那麼 JSF 控制器就會使用它。這個視 圖包含了所有的 GUI 組件。

生命周期的這個階段表示為三個視圖實例:新視圖、原始視圖和後視圖,每個視圖的處理 方式都不相同。在 新視圖 的情況中,JSF 會構建 Faces 頁面的視圖,並將事件處理程序和 驗證程序綁定到組件上。這個視圖被保存在一個 FacesContext 對象中。

FacesContext 對象包含了 JSF 用來管理當前會話中當前請求的 GUI 組件狀態所需要的 所有狀態信息。FacesContext 將視圖保存在自己的 viewRoot 屬性中;viewRoot 包含了當 前視圖 ID 的所有 JSF 組件。

在 原始視圖 的情況中(第一次加載的是一個頁面),JSF 會創建一個空視圖。這個空視 圖會在用戶事件產生時進行填充。JSF 可以直接從原始視圖過渡到進行響應的階段。

在 後視圖(postback) 的情況中(用戶返回之前訪問過的頁面),包含頁面的視圖早已 經存在了,因此只需要進行恢復就可以了。在這種情況中,JSF 就使用現有視圖的狀態信息 來重構狀態。後視圖的下一個階段是應用請求值。

階段 2:應用請求值

應用請求值 階段的目的是讓每個組件檢索自己當前的狀態信息。這些組件必須首先通過 FacesContext 對象進行檢索或創建(使用其值)。雖然組件值也可以從 cookie 或頭文件中 進行檢索,但是它們通常是通過請求參數進行檢索的。

如果一個組件的即時事件處理屬性 沒有 設置為 true,那麼就會對這些值進行轉換。因 此,如果 域 被綁定到一個 Integer 屬性上,那麼該值就會被轉換為一個 Integer 類型。 如果值的轉換失敗了,那麼就會生成一個錯誤消息,並在 FacesContext 中進行排隊,在產 生響應的階段會顯示其中的消息,同時還會顯示所有的驗證錯誤。

如果一個組件的即時事件處理屬性 的確 被設置為 true,那麼這些值就會被轉換為適當 的類型,並進行有效性驗證。然後轉換後的值會被保存到組件中。如果值轉換或值的有效性 驗證失敗了,就會生成一個錯誤消息,並在 FacesContext 中進行排隊,在產生響應的階段 會顯示其中的消息,同時還會顯示所有的驗證錯誤。

處理驗證

生命周期中的第一個事件處理發生在應用請求值階段之後。在這個階段中,每個組件都有 一些值需要根據應用程序的驗證規則進行有效性驗證。這些驗證規則可以是預先進行定義的 (JSF 中提供的),也可以由開發者進行定義。用戶所輸入的值會與這些驗證規則進行比較 。如果說輸入的值無效,就會向 FacesContext 中添加一個錯誤消息,並且該組件會被表示 為無效的。如果一個組件被表示為無效的,那麼 JSF 就會轉到產生響應的階段,在這個階段 中會顯示當前的視圖,以及驗證錯誤消息。如果沒有有效性驗證錯誤,那麼 JSF 就會轉到更 新模型值的階段。

階段 3:更新模型值

JSF 應用程序生命周期中的第三個階段 ——更新模型值 —— 負責更新服務器端模型的 實際值,通常來講,這都是通過更新後台 bean(稱為管理 bean)的屬性實現的。只有那些 與組件值綁定在一起的 bean 屬性才會被更新。注意這個階段發生在有效性驗證之後,因此 可以確保拷貝到 bean 屬性的值都是有效的(至少在表單域一級都是有效的;在業務規則一 級仍可能無效)。

階段 4:調用程序

在生命周期的第四個階段 ——調用程序 —— 中,JSF 控制程序會調用程序來處理 表單 的提交操作。組件值已經經過了類型轉換和有效性驗證,並被應用到模型對象中了,因此您 現在可以使用它們來執行應用程序的業務邏輯了。

在這個階段,您還可以為一個給定的序列或很多可能的序列指定後面的邏輯視圖,這可以 通過為一次成功的表單提交定義一個特定的結果並返回這個結果來實現。例如:在成功輸出 時,將用戶重定向到下一頁中。要讓這種導航工作能夠起作用,您需要在 faces-config.xml 文件中創建一個到 成功輸出 的映射作為一條導航規則。一旦導航發生之後,您就轉換到生 命周期的最後一個階段了。

階段 5:進行響應

在生命周期的第五個階段 ——進行響應 —— 中,您可以在視圖中顯示當前狀態中的所 有組件。

圖 2 是 JSF 生命周期的第五個階段的一個對象狀態圖,包括時間有效性驗證和處理。

圖 2. JSF 生命周期的五階段

范例

現在您已經對 JSF 生命周期的階段有了基本的了解,下面我們將向您介紹在一個范例 Web 應用程序中,這些階段是如何協同工作的。除了展示 JSF 生命周期的基本功能之外,這 個應用程序還會利用一些通用的 JSF GUI 組件,例如 Radio List, List, Text Field, Label, Panel 等等,這樣您就可以親自體驗一下在 第 1 部分 中曾經簡要討論過的這些組 件。

這個示例程序還會展示在 JSF 中使用其他 Java 技術的兩種方法。它將組合使用 JSF 和 JavaScript 來啟用即時事件處理(在那些對整個表單進行驗證是多余的情況中),其布局是 由 Struts Tiles 進行管理的。雖然 Struts Tiles 並不是 JSF 的一個必要部分,但是 tiles 通常用來為一個程序中的所有 JSF 頁面提供一致的外觀。要學習更多有關 Struts Tiles 的內容,請參閱 參考資料。

程序設置

這個示例 Web 程序實際上是一個非常簡單的創建、閱讀、更新並刪除(CRUD)一個在線 CD 倉庫中庫存的程序。它包括一個表單,讓用戶可以向系統中輸入新 CD;有一些單選按鈕 ,讓用戶選擇音樂的分類。當用戶選擇一個分類時,就啟動一些 JavaScript 腳本將這個表 單立即發回服務器。程序組合采用 JSF 和 JavaScript 技術來處理一個組件,而不是整個頁 面,這種技術稱為 即時事件處理。在這種情況中,您可以填充一個子類清單,而不用驗證表 單的其他內容。

這個示例程序還包括一個 CD 清單,它將展示如何使用 JSF 的 dataTable。從這個頁面 中,最終用戶可以根據標題或者藝術家對 CD 清單進行排序。

類和方法

圖 3 列出了這個示例程序的類。圖中列出了 4 個類,我們只關注其中的 3 個: StoreManagerDelegate、CD 和 StoreController。

圖 3. 示例程序類

StoreManagerDelegate 類是這個程序的業務代表。它為整個模型呈現了主界面。CD 類是 一個數據轉換對象(DTO)。如果這是一個真實的程序,那麼 StoreManagerDelegate 類就會 為添加、刪除和編輯 CD 實現所有的業務規則,還會負責使用一個數據訪問對象(DAO)將 CD 存儲到一個永久的存儲介質中。StoreManagerDelegate 和 CD 包含了一些用於這個 MVC 程序的 模型。

StoreController 類是本例中的主要後台 bean。StoreController 類是 GUI 世界和模型 世界之間的黏合劑。它將自己的很多行為都委托給 StoreManagerDelegate 進行處理。 StoreController 是這個 MVC 程序的 控制程序。

StoreController 類展示了如何構建一個可排序的 CRUD 清單。它具有以下與 CRUD 相關 的方法:editCD、addNew、addCD 以及 updateCD。StoreController 還負責將模型對象呈現 給表單。在這種情況中,它使用 cd 屬性將當前的 CD 對象呈現給 CD 表單,該屬性的類型 就是 CD。

開始編碼

開始編寫這個示例程序的最好方法是遍歷它的使用案例:

新增 CD

編輯現有的 CD

根據標題對 CD 進行排序

根據藝術家對 CD 進行排序

第三個使用案例和第四個使用案例的代碼基本上是相同的,因此我將向您展示如何根據標 題進行排序,並將第四個使用案例留作練習,請您自行完成。我們很快就會對使用案例進行 編碼,但是首先讓我們來了解一下完成後的應用程序的頁面將是什麼樣子。

圖 4 顯示了具有可排序列的 CD 清單頁面。

圖 4. 具有可排序列的 CD 清單頁面

圖 5 顯示了具有分類組件的 CD 表單頁面。

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

圖 6 顯示了具有分類和子類組件的 CD 表單頁面。

圖 6. 已經選擇了分類和子類組件的 CD 表單頁面

使用案例 1:新增 CD

在該程序的第一個使用案例中,用戶將添加一個新 CD:切換到 CD 清單頁面上,點擊 Add CD 鏈接(這是在 listing.jsp 文件中定義的),如清單 1 所示。

清單 1. 在 listing.jsp 中定義的 Add CD 按鈕

<h:commandLink action="#{CDManagerBean.addNew}">
   <f:verbatim>Add CD</f:verbatim>
</h:commandLink>

這個鏈接被綁定到 CDManagerBean 的 addNew 方法上。這個 addNew 方法在 JSF 生命周 期的調用程序階段(最後一個階段)被調用的。操作被使用 JSF 綁定表達式 # {CDManagerBean.addNew} 綁定到這個方法上。CDManagerBean 是這個程序的存儲控制器的一 個別名。CDManagerBean 是這個控制器的邏輯名。控制器類是一個在 faces-config.xml 文 件中定義的管理 bean,如清單 2 所示。

清單 2. 在 faces-config.xml 中定義的 StoreController 類

<managed-bean>
  <description>The "backing file" bean that backs up the CD application</description>
  <managed-bean-name>CDManagerBean</managed-bean-name>
  <managed-bean- class>com.arcmind.jsfquickstart.controller.StoreController</managed-bean- class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

准備表單

addNew() 方法通過創建一個空 CD 來准備表單,如清單 3 所示。

清單 3. addNew() 創建一個空 CD 表單

[StoreController.java]
/**
* Prepare the cdForm to add a new CD.
* This gets executed before we prompt
* the user to add a new CD.
*
* @return success
*/
public String addNew() {
   if (subCategoryList == null) {
     subCategoryList = new HtmlSelectOneListbox();
   }
   subCategoryList.setRendered(false);
   this.cd = new CD();
   return "success";
}

addNew() 方法通過創建一個新的 CD 來清空 CD 表單域。這個 CD 表單的域被綁定到 cd 屬性的屬性中。這個方法還會將正在顯示的子類清單置空。

返回成功結果

接下來,addNew() 方法會被調用,控制權被重定向到成功映射頁面,即 cdForm.jsp 文 件。cdForm.jsp 文件是在 faces-config.xml 文件中定義的,如清單 4 所示。

清單 4. cdForm.jsp 是 addNew() 的成功映射

<navigation-rule>
  <from-view-id>/listing.jsp</from-view-id>
  ...
  <navigation-case>
   <from-action>#{CDManagerBean.addNew}</from-action>
   <from-outcome>success</from-outcome>
   <to-view-id>/cdForm.jsp</to-view-id>
  </navigation-case>

</navigation-rule>

清單 4 表明如果用戶從清單切換到 addNew (#{CDManagerBean.addNew}) 操作,並且 addNew 操作成功返回,那就會切換到 cdForm.jsp 頁面。

設置 cdForm 和 panelGrid

cdForm.jsp 是包含 CD 表單的表單。其中具有 ID、Title、Artist、Price、Category 和 Subcategory 的域。這些域被放到一個名為 panelGrid 的容器中。JSF 組件,例如 AWT 組件,具有一些容器和組件。容器 是一個包含其他組件的組件。這是一個 混合設計模式 的 例子。panelGrid 有 3 列。每個域都各在一行中,還會有一個標簽和消息用於顯示該域的錯 誤消息。cdForm 和 panelGrid 是在清單 5 中定義的。

清單 5. 定義 cdForm 和 panelGrid

<f:view>
   <h2>CD Form</h2>
   <h:form id="cdForm">
   <h:inputHidden id="cdid" value="#{CDManagerBean.cd.id}"/>
   <h:panelGrid columns="3" rowClasses="row1, row2">
    <h:outputLabel for="title" styleClass="label">
      <h:outputText value="Title"/>
    </h:outputLabel>
    <h:inputText id="title" value="#{CDManagerBean.cd.title}" required="true"/>
    <h:message for="title" styleClass="errorText"/>
    <h:outputLabel for="artist" styleClass="label">
      <h:outputText value="Artist"/>
    </h:outputLabel>
    <h:inputText id="artist" value="#{CDManagerBean.cd.artist}" required="true"/>
    <h:message for="artist" styleClass="errorText"/>
    <h:outputLabel for="price" styleClass="label">
      <h:outputText value="Price"/>
    </h:outputLabel>
    <h:inputText id="price" value="#{CDManagerBean.cd.price}" required="true"/>
    <h:message for="price" styleClass="errorText"/>
    <h:outputLabel for="category" styleClass="label">
      <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" styleClass="errorText"/>
    <h:outputLabel for="subcategory" styleClass="label">
      <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" styleClass="errorText"/>
   </h:panelGrid>
   <br />
   <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}"/>
   </h:form>
</f:view>

關於代碼的注釋

每個輸入域都將該域綁定到控制器的 cd 屬性的一個屬性上。例如,標題的輸入文本域被 使用下面的 JSF 綁定表達式綁定到 cd 屬性上:value="#{CDManagerBean.cd.title}"。

您可能會注意到在清單 5 中幾乎沒有什麼 HTML 語句。這是由於 panelGrid 會生成大部 分的 HMTL 語句。注意實際的外觀是由與 panelGrid 相關的樣式表決定的。屬性 rowClasses="row1, row2" 會為正在修改的行設置 CSS 類。第一行是白色的,第二行是灰色 的。您還可以為列或其他內容指定 CSS 類。JSF panelGrid 組件可以方便地快速設置表單的 布局。如果您希望實現 panelGrid 沒有提供的功能,就不能使用它:不過可以使用 HTML 自 己設置組件的布局。然而,如果您發現自己在很多頁面上都使用了定制的 HTML,那麼就可能 會考慮編寫自己的定制組件。這種想法可以讓您盡可能 DRY 地重用 HTML 語句(DRY 是 don't repeat yourself 的縮寫,這個術語來自於 Dave Thomas 的 Pragmatic Programmer 一書)。

關於清單 5 另外需要注意的是控制器有一個 editMode 屬性,由 cdForm.jsp 用於有選 擇地顯示 submitAdd 按鈕或 submitUpdate 按鈕;submitAdd 按鈕是在表單不處於編輯模式 時顯示的。submitUpdate 按鈕是在表單處於編輯模式時顯示的。這可以簡化為編輯和添加模 式使用相同的 JSP。(默認情況下,表單不處於編輯模式。)這種功能是由 cdForm.jsp 中 的每個按鈕上的呈現表達式實現的。例如,清單 6 列出了 submitAdd button rendered="# {not CDManagerBean.editMode}" 上的呈現表達式。submitAdd 按鈕被使用表達式 (action="#{CDManagerBean.addCD}") 綁定到 addCD 方法上。

清單 6. 使用 addCD() 方法添加一個 CD

[StoreController.java]
/**
* Add a cd to the store.
*
* @return outcome
*/
public String addCD() {
   store.addCD(this.cd);
   return "success";
}

對域進行有效性驗證

在 addCD 方法被調用之前,JSF 必須對 GUI 中的域進行有效性驗證。這實際上非常簡單 ,因為您還沒有為域關聯任何有效性驗證條件。在應用請求值階段,這些值被從請求參數拷 貝到組件值中(這是由組件本身進行的)。現在,價格從一個字符串轉換為一個浮點類型。 如果用戶為價格輸入的是“abc”,那麼轉換為浮點類型的操作就會失敗,控制權將被重新定 向到 cdForm.jsp 頁面上,供最終用戶進行修正。與價格相關的 h:message 將顯示一個轉換 錯誤消息。如果所有的值都可以正常進行類型轉換,並且現在都可以使用了(如果需要的話 ),那麼您就可以進行有效性驗證的處理了。由於這個示例程序並沒有與組件關聯任何有效 性驗證規則(在下一篇文章中我們將介紹這種特性),因此您可以繼續進入更新模型值的階 段了。

在更新模型值的階段中,會使用保存在 GUI 組件中的經過轉換和有效性驗證的值來調用 CD 的賦值方法。addCD() 方法是在 調用程序 階段中被調用的。addCD() 方法使用一個業務 代理(store 對象)來執行這個操作。addCD 方法在系統中使用 store 對象來存儲 CD。由 於 addCD 方法會返回成功,因此接下來會顯示這個清單,這是在 faces-config.xm 中定義 的。在 faces-config.xml 中定義的導航規則如清單 7 所示。

清單 7. addCD 成功輸出的導航規則

<navigation-rule>
  <from-view-id>/cdForm.jsp</from-view-id>
  <navigation-case>
   <from-action>#{CDManagerBean.addCD}</from-action>
   <from-outcome>success</from-outcome>
   <to-view-id>/listing.jsp</to-view-id>
  </navigation-case>
  ...
</navigation-rule>

使用案例 2:編輯 CD

這個示例程序的第二個使用案例也會在這個清單頁面(listing.jsp)中啟動。除了向您 介紹如何編輯 JSF 頁面中的數據之外,這個使用案例還將向您介紹 JSF dataTable 組件。

這個清單頁面使用一個 dataTable 組件來顯示 CD 的清單。dataTable 的值被綁定到控 制程序類 StoreController 的 cds 屬性。cds 屬性的定義如清單 8 所示。

清單 8. 在 StoreController.java 中定義的 cds 屬性

[StoreController.java]
/** List of cds for CD listing. */
private DataModel cdModel = new ListDataModel();
{
   cdModel.setWrappedData(store.findTitleAsc());
}
/**
* List of CDs in the system.
*
* @return Returns the cds.
*/
public DataModel getCds() {
   return cdModel;
}

cds 屬性是基於從存儲對象 StoreManagerDelegate 返回的 java.util.List 的,這個對 象是該程序的業務代理。cdModel 對從 DataModel 中的存儲對象返回的清單進行了封裝。 DataModel 是一個用於 dataTable 的模型。

dataTable 的定義如清單 9 所示。

清單 9. listing.jsp 中的 dataTable 定義

<f:view>
   <h:form>
    <h:dataTable id="items"
     value="#{CDManagerBean.cds}"
     var="cd"
     rowClasses="oddRow, evenRow"
     headerClass="tableHeader">

注意該值被綁定到控制程序的 cds 屬性上。rowClasses 和 headerClass 屬性用來指定 CSS 類,後者用來定義 dataTable 的外觀。正如前面介紹的一樣,JSF 嚴重依賴於 CSS 來 定義 GUI 的外觀。如果您並不了解 CSS(即您之前都是使用字體標簽和 HTML 表來設置外觀 的),就可能會希望在靈活運行 JSF 之前首先來學習一下有關 CSS 的知識。

column 組件

Title、Artist 和 Price 域都是使用 column 組件顯示的,如清單 10 所示(此處只顯 示了 Title 域)。

清單 10. 在 column 組件中添加域

<h:column>
   <f:facet name="header">
    ...
     <h:outputText value="Title"/>
   </f:facet>
    <h:commandLink action="#{CDManagerBean.editCD}">
     <h:outputText value="#{cd.title}"/>
    </h:commandLink>
</h:column>

column 組件是 dataTable 的一個子組件。column 組件使用一個子組件和一個 facet。 facet 是一個有名的子組件;它並不是一個子孫組件。column 組件有一個名為 header 的 facet,它定義了在 header 中顯示的內容。對於本例來說,commandLink 是 column 組件的 一個子孫組件。commandLink 在一個鏈接中顯示了 CD 的標題,該鏈接被綁定到操作 # {CDManagerBean.editCD} 上。這個操作屬性將 commandLink 綁定到控制程序類的 editCD() 方法上,如清單 11 所示。

清單 11. editCD commandLink 的後台 bean 方法

[StoreController.java]
/**
* Edit the CD. This get executed before the edit cdForm
* page gets loaded.
*
* @return outcome
*/
public String editCD() {
   this.cd = (CD) cdModel.getRowData();
   this.cd = (CD) store.getCDById(cd.getId());
   if ((cd.getCategory() != null) || !"".equals(cd.getCategory())) {
     this.subCategoryList.setRendered(true);
     this.subCategories = getSubcategoriesList(cd.getCategory());
   } else {
     this.subCategoryList.setRendered(false);
   }
   this.editMode = true;
   return "success";
}

editCD() 方法

editCD() 方法是在 JSF 生命周期的調用程序階段調用的。editCD() 方法准備控制程序 以使用編輯模式來顯示 cdForm.jsp 頁面。這是通過查看當前選定的 CD 來實現的,CD 是通 過調用 cdModel.getRowData() 方法來選擇的。

注意 JSF DataModel 允許您從比傳統的 Web 應用程序更高的層次上使用數據。您並不需 要對請求參數進行檢查:只需要調用 cdModel.getRowData() 方法向 DataModel(cdModel) 查詢已經選擇了哪個 CD。這個更高級別的抽象對 Web 開發進行了相當程度的簡化。

一旦取得當前選擇的 CD 之後,就可以使用業務代理來加載該 CD 的最新拷貝了 (store.getCDById())。在加載這個 CD 之後,store.getCDById() 會激活 subCategory 清單(假設這個 CD 已經關聯了一個子目錄),然後將 editMode 屬性設置為 true。回想一 下,editMode 屬性是由 cdForm 用來顯示 Add 或 Update 按鈕。最後,store.getCDById() 方法返回 success。在清單 12 中重要的導航規則可以保證返回成功之後,切換到 cdForm.jsp 頁面,如下所示。

清單 12. 一條重要的導航規則

<navigation-rule>
  <from-view-id>/listing.jsp</from-view-id>
  <navigation-case>
   <from-action>#{CDManagerBean.editCD}</from-action>
   <from-outcome>success</from-outcome>
   <to-view-id>/cdForm.jsp</to-view-id>
  </navigation-case>
  <navigation-case>
   <from-action>#{CDManagerBean.addNew}</from-action>
   <from-outcome>success</from-outcome>
   <to-view-id>/cdForm.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

updateCD() 方法

CD 表單會加載並顯示 CD 屬性的屬性設置。最終用戶可以根據需要編輯所得到的表單, 並在完成時點擊 Update 按鈕。Update 按鈕是當用戶處於 Edit 模式時所顯示的惟一一個按 鈕,它只會在 editMode 為 true 時顯示,如清單 13 所示。

清單 13. Update CD 按鈕

[cdForm.jsp]
<h:commandButton id="submitUpdate"
  action="#{CDManagerBean.updateCD}"
  value="Update CD"
  endered="#{CDManagerBean.editMode}"/>

Update 按鈕被綁定到 updateCD() 方法上。在調用 update 方法之前,JSF 必須對 GUI 中的域進行有效性驗證。在應用請求值階段,這些值被從請求參數中拷貝到組件值中(這是 由組件本身完成的)。現在,價格被從一個字符串轉換成了一個浮點類型。由於沒有為組件 關聯任何有效性驗證規則,因此如果所有請求的值都已經存在並經過轉換了,就可以轉換到 生命周期的下一個步驟了。

更新模型值

在更新模型值階段中,會使用保存在 GUI 組件中經過類型轉換和有效性驗證的值來調用 CD 的賦值函數。updateCD() 方法是在調用程序階段被調用的。updateCD() 方法如清單 14 所示。

清單 14. updateCD() 方法

[StoreController.java]
/**
* Update the CD loaded on the form.
*
* @return success
*/
public String updateCD() {
   store.updateCD(this.cd);
   this.editMode = false;
   return "success";
}

updateCD() 方法可以代理業務代理的大部分職責。它將 editMode 設置為 false(這是 默認值),並返回成功。成功輸出將您重定向回清單頁面中,在這個頁面中您可以查看根據 清單 15 中顯示的導航規則新編輯的 CD。

清單 15. 成功的 UpdateCD 會將您帶回 listing.jsp

[faces-config.xml]
<navigation-rule>
  <from-view-id>/cdForm.jsp</from-view-id>
  ...
  <navigation-case>
   <from-action>#{CDManagerBean.updateCD}</from-action>
   <from-outcome>success</from-outcome>
   <to-view-id>/listing.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

使用案例 3:對 CD 進行排序

我們要介紹的最後一個使用案例將向您展示如何對表進行排序。這個使用案例也是在 CD 清單頁面上啟動的。清單頁允許根據標題和藝術家對 CD 按照升序或降序的順序進行排列。 在本例中,我將向您展示如何根據標題進行排序,並將根據藝術家進行排序留作練習。

標題頭排序有一些到控制程序中排序方法的鏈接。清單 16 顯示了在 listing.jsp 中是 如何顯示標題頭的。

清單 16. 對 commandLinks 進行排序

[listing.jsp]
<h:column>
  <f:facet name="header">
   <h:panelGroup>
     <h:outputText value="Title"/>
      <f:verbatim>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [</f:verbatim>
        <h:commandLink styleClass="smallLink" action="# {CDManagerBean.sortTitleAsc}">
          <h:outputText id="ascTitle" value="asc"/>
        </h:commandLink>
          <h:outputText value=","/>
        <h:commandLink styleClass="smallLink" action="# {CDManagerBean.sortTitleDec}">
          <h:outputText id="decTitle" value="dec"/>
        </h:commandLink>
      <f:verbatim>]</f:verbatim>
   </h:panelGroup>
  </f:facet>
        <h:commandLink action="#{CDManagerBean.editCD}">
         <h:outputText value="#{cd.title}"/>
        </h:commandLink>
</h:column>

panelGroup 組件

注意一下清單 16,鏈接是在標題列的 header facet 中定義的。facet 只會關聯一個惟 一名字的組件;這樣,要在 header facet 中放置一個多鏈接的組件,您需要使用 panelGroup。panelGroup (與 panelGrid 類似)是一個單獨的組件,其中包含了很多子組 件。panelGroup 包含兩個鏈接,如清單 17 所示。

清單 17. panelGroup 組件鏈接

[listing.jsp]
<h:commandLink styleClass="smallLink" action="# {CDManagerBean.sortTitleAsc}">
   <h:outputText id="ascTitle" value="asc"/>
</h:commandLink>
...
<h:commandLink styleClass="smallLink" action="# {CDManagerBean.sortTitleDec}">
   <h:outputText id="decTitle" value="dec"/>
</h:commandLink>

第一個鏈接被綁定到控制程序的 sortTitleAsc 方法上,第二個鏈接被綁定到 sortTitleDec 上。這兩個方法如清單 18 所示。

清單 18. panelGroup 鏈接方法

[StoreController.java ]
/**
* Uses the store delegate to return
* a sorted list of CDs by title (ascending).
*
* @return asc
*/
public String sortTitleAsc() {
   this.cdModel.setWrappedData(store.findTitleAsc());
   return "asc";
}
/**
* Uses the store delegate to return
* a sorted list of CDs by title (descending).
*
* @return dec
*/
public String sortTitleDec() {
   this.cdModel.setWrappedData(store.findTitleDec());
   return "dec";
}

這兩個方法都依賴於業務代理返回一個按照正確要求排序後的 java.util.List。注意這 個方法會返回邏輯輸出 asc 和 dec。這兩個輸出在 faces-config.xml 文件中都沒有映射。 沒有映射的輸出會導致重新加載當前的視圖;這樣,listing.jsp 將會在調用這些方法時重 新進行加載,清單頁面也會按照正確的順序重新顯示。

這種方法的優點是它依賴於業務代理進行排序。業務代理又可能會依賴於一個 DAO 對象 ,而後者又依賴於一個數據庫查詢或 OR 映射查詢,這樣可以對 CD 進行有效的查詢。這種 方法通常比具有一個“智能” GUI 組件的方法更好,後一種方法知道如何對隨機的域對象( CD 就是一個域對象)進行排序,因為排序操作是一個經常發生的操作,嚴格來說,是模型的 一部分(即域對象的一部分),而不是視圖的一部分。

正如前面介紹的一樣,對標題進行排序和對藝術家進行排序的代碼幾乎是相同的。作為一 個練習,請自己試圖為第四個使用案例編寫代碼,對藝術家而不是標題進行排序。

即時事件處理

我們要介紹的最後一個主題是即時事件處理。即時事件處理在您不希望(或需要)對整個 頁面進行有效性驗證來處理用戶輸入的情況中非常有用。回想一下,示例程序的 cdForm.jsp 頁面使用單選按鈕來顯示一個目錄和子目錄清單。當最終用戶選擇一個目錄時,cdForm.jsp 頁面就會使用 JavaScript 重新生成表單,這樣就可以顯示子目錄清單了。

這是一個即時事件處理的例子,因為整個表單 沒有 在調用事件處理程序之前進行有效性 驗證。相反,類清單的事件處理程序會生成子目錄,並強制 JSF 跳過進行響應的階段。組件 的事件處理程序通常都是在調用程序階段執行的。即時事件組件的事件處理程序是在應用請 求值階段執行的,這發生在其余組件的類型轉換和有效性驗證之前。

清單 19 顯示了在 cdForm.jsp 頁面中再次顯示的目錄清單。

清單 19. cdForm.jsp 中的目錄清單

[cdForm.jsp]
<h:selectOneRadio id="category" value="#{CDManagerBean.cd.category}"
  immediate="true"
  onclick="submit()"
  valueChangeListener="#{CDManagerBean.categorySelected}">
   <f:selectItems value="#{CDManagerBean.categories}"/>
</h:selectOneRadio>

selectOneRadio 目錄域被綁定到 CD 的目錄屬性(value="# {CDManagerBean.cd.category}")上。注意這個即時事件處理被激活了(immediate="true" )。這種設置意味著 Category 組件的事件會在應用值階段(而不是在調用程序階段)進行 處理(以及類型轉換和有效性驗證)。

JavaScript 功能是在 onclick="submit()" 這一行 —— 即當用戶進行修改時,它應該 立即被提交到 Web 程序中進行處理。

事件處理程序方法

在清單中顯示的可用分類是由 f:selectItems 標簽值(value="# {CDManagerBean.categories}")確定的。這個組件的事件處理程序的變化是控制程序的 categorySelected() 方法(valueChangeListener="#{CDManagerBean.categorySelected}" )。事件處理程序如清單 20 所示。

清單 20. categorySelected 事件處理程序

[StoreController.java]
/**
* Event Handler for a category getting selected.
*
* @param event event data
*/
public void categorySelected(ValueChangeEvent event) {
   subCategoryList.setRendered(true);
   String value = (String) event.getNewValue();
   if (value != null) {
     this.subCategories = this.getSubcategoriesList(value);
   }
   FacesContext context = FacesContext.getCurrentInstance();
   context.renderResponse();
}

categorySelected() 方法做的第一件事情是允許 subCategoryList 調用自己。 categorySelected() 方法然後會使用所選擇的分類值來查找一個 subCategories 清單。 subCategories 屬性被綁定到 subcategoryList 值上。接下來,事件處理程序通過調用當前 FacesContext 上的 renderResponse() 方法強制 JSF 轉到進行響應階段。然後,GUI (cdForm.jsp)為當前顯示的目錄重新顯示可用的子目錄。

將組件綁定到控制程序上

subCategoryList 組件是從 GUI 上綁定的。正如您可以將值綁定到組件上一樣,您也可 以將這些組件綁定到一個控制程序上。子目錄是在 cdForm.jsp 頁面中定義的,如清單 21 所示。

清單 21. 在 cdForm.jsp 頁面中定義的子目錄清單

[cdForm.jsp]
<h:selectOneListbox id="subcategory" value="# {CDManagerBean.cd.subCategory}"
  binding="#{CDManagerBean.subCategoryList}">
   <f:selectItems value="#{CDManagerBean.subCategories}"/>
</h:selectOneListbox>

binding 屬性允許您將 GUI 的組件綁定到後端的 bean(控制程序)上。這樣,上面的組 件就會被綁定到 CDManagerBean.subCategoryList 上,這是在清單 22 中定義的控制程序中 的一個屬性。

清單 22. subCategoryList 屬性

[StoreController.java ]
/** GUI Component that represents
  the Subcategory list on the CDForm. */
private UIInput subCategoryList;
{
   subCategoryList = new HtmlSelectOneListbox();
}
/**
* Subcategory list component
*
* @param aSubCategoryList The subCategoryList to set.
*
* @uml.property name="subCategoryList"
*/
public void setSubCategoryList(UIInput aSubCategoryList) {
   this.subCategoryList = aSubCategoryList;
}
/**
* Subcategory list component
*
* @return Returns the subCategoryList.
*
* @uml.property name="subCategoryList"
*/
public UIInput getSubCategoryList() {
   return subCategoryList;
}

即時事件處理只使用了很少的一點 JavaScript 功能(onclick="submit()" 命令),這 種靈活的 JSF 生命周期的便利可以讓您處理在一個組件中輸入的信息,而不用對整個頁面進 行有效性驗證。在這個例子中,我們已經介紹了 Category 組件的即時處理是如何讓您可以 顯示子目錄,而不用重新加載頁面的。

結束語

下面對這個 JSF 系列的第二篇文章進行總結:在本文中我們使用了一個范例和三個使用 案例來介紹 JSF 請求處理的生命周期,並展示了其組件模型的一些必備特性。我們還介紹了 如何組合使用 JSF 和 Struts Tiles,在 JSF 頁面之間實現更加統一的布局,如何使用 DataModel,以及如何組合使用 JSF 和 JavaScript 進行即時事件的處理。

第 3 部分將繼續本文的介紹,內容涉及一些高級特性,例如類型轉換和有效性驗證。我 們將向您展示如何創建自己定制的 JSF 有效性驗證規則和類型轉換規則,以及在調用時如何 使用內置的規則。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved