JSF生命週期總結

JSF實現使用一個控制器servlet 來處理請求,然後執行 JSF 生命週期.如圖顯示了JSF 生命週期中的事件處理。 


上圖是正常的JSF組件的生命週期.一共12個.

2.2  請求處理生命週期

已經談過JSF如何使用組件、事件、監聽器和其他一些優雅的概念來簡化Web開發。這也正是此節是關於處理請求的原因。爲了使你理解框架是如何掩藏底層對 Servlet API的處理,我們將分析Faces如何處理每一個請求。這將幫助你構建更好的應用,因爲你知道確實發生了什麼,並且知道會在什麼時候發生。如果你是前端 開發人員並且想要避免這些細節,你可以跳過此節。必要時隨時回來參考這些內容。

在這一節將描述JSF如何處理Faces 自身產生的請求。換句話說,請求是由含有JSF 組件的頁面產生的,響應也應當含有JSF組件(完全可以返回包含有JSF 組件的頁面,即使初始請求並不是由JSF產生的;見第14章關於不同的請求處理情形的更多信息)。
 
圖2-4是一個狀態圖,展示了JSF處理來自於客戶端的請求時發生的事情——JSF請求處理生命週期。這一過程開始於JSF servlet 接收到一個請求(記住,JSF 構建於Servlet API之上)。表2-2總結了所有的處理階段。共有6個主要的階段,而事件則在它們中的大部分之後進行處理。

在大多數階段後,JSF將事件廣播到各種激活的監聽器(事件可以與某特定的階段相關聯)。事件監聽器執行應用邏輯或者操作組件;也可以直接跳到最後階段, 呈現響應階段。監聽器也可以跳到最後階段並自己呈現響應。這在其需要返回二進制數據,執行重定向或者返回其他與JSF無關的內容,比如XML文檔或普通 HTML時更可能這樣做。

有四個階段會產生消息:應用請求值、處理驗證、更新模型值和調用應用階段。不管是否有消息產生,都在呈現響應階段發送響應給用戶,除非監聽器、呈現器或者組件自身發送響應。

圖2-4    請求處理生命週期。虛線的流是可選的。JSF在處理每個請求時,要經歷多個階段。每個階段後都要調用事件監聽器。監聽器可以繼續如常,報告錯誤並跳到呈現響應階段,或者自己產生響應

整個過程背後的理想狀況是到達調用應用階段時,已存在一個完全組裝好的組件樹,所有的驗證都已完成,而所有的後臺bean或者模型對象全都進行了更新。簡 言之,JSF做了大量的工作:處理了有關請求的大量細節工作,並將它轉換爲由組件和事件構成的高階視圖。它甚至更新了相關組件的屬性。 

表2-2  JSF在處理到來的請求時經歷多個階段

階    段


說    明


產生的事件

恢復視圖

(restore view)


爲選定的視圖找到或者創建組件樹。在此階段,某些組件,如HtmlCommandButton,將產生動作事件(或者其他類型事件)


階段事件

應用請求

(apply request value)


更新組件的值,使之等於請求中發送的值,可能需要使用轉換器。如果出現錯誤將添加轉換錯誤。也可以從請求參數中產生事件


階段事件、數據模型事件、動作事件

處理驗證

(process validation)


每一個組件進行自我驗證(可以包含外部驗證器)。要報告驗證錯誤消息


階段事件、數據模型事件、值改變事件

更新模型值

(update model value)


更新與組件相關的後臺bean或者模型對象的值。要報告轉換錯誤


階段事件、數據模型事件

調用應用

(invoke application)


調用註冊的動作監聽器。默認的動作監聽器也可執行由命令組件(如HtmlCommandButton)引用的動作方法並且選擇下一個要顯示視圖


階段事件、動作事件

呈現響應

(render response)


使用當前的顯示技術(如JSP)顯示選定的視圖


階段事件

爲了更加清楚,我們使用Hello, world!示例來幫助解釋生命週期。特別要分析檢查產生第1章圖1-8中輸入的請求。實際的HTTP請求如代碼清單2-1所示。

代碼清單2-1  在Hello, world!應用顯示圖1-8之前,瀏覽器發送的HTTP 請求

這裏不深入探討HTTP 請求的細節,但是其中有幾行與我們的討論相關:

請求提交表單數據至相關URI /jia-hello-world/faces/hello.jsp。

Referer是指產生請求的頁面。注意它與接收該請求的頁面相同:/jia-hello-world/ faces/hello.jsp。

cookie被servlet容器用來將此請求映射到特定的會話。此例中,JSF用servlet 會話來存儲當前視圖(視圖的狀態也可以存儲在客戶維護的值中,比如隱藏字段)。

這是最重要的部分—這是JSF所處理的實際參數(&號用來分隔參數,而%3A則轉義爲一個冒號:)。第一個參數稱爲 welcomeForm:helloInput,值爲64,它是輸入到瀏覽器中的值。第二個參數稱爲 welcomeForm:redisplayCommand,值爲Redisplay。最後一個參數稱爲welcomeForm,值爲 welcomeForm。我們將在下一節看到如何處理這些參數。

一旦JSF接收到請求,它就創建和組裝 javax.faces.context.FacesContext的一個實例。這個類表示請求的當前狀態,請求具有到底層servlet請求對象之各個 方面的鉤子。這也是事件監聽器獲得當前視圖的句柄、添加消息、記錄事件等的地方。 JSF正是將這個對象作爲請求處理生命週期的基礎。我們將在以下幾個小節中描述各個階段。

2.2.1  階段1:恢復視圖

視圖表示組成特定頁面的所有組件。它被保存在客戶端(通常存儲在隱藏字段中)或服務器中(通常在會話中)。這個例子中,它被保存在服務器中,這是默認做 法。每個視圖由一棵組件樹組成,並具有唯一的標識符。這個視圖標識符和請求的額外路徑信息相同。所以,對於被代碼清單2-1中的所引用的路徑信息 /jia-hello-world/faces/hello.jsp來說,視圖標識符是/hello.jsp——即servlet名稱後面的內容。

因爲這個請求是當用戶點擊hello.jsp中的按鈕時產生的,因此發送此請求的頁面和接收它的頁面是相同的。頁面提交給自己的過程稱爲是回送 (postback)。如果已習慣使用Struts之類的框架 ——它們將應用代碼分離到對應特定URL的Action類中,對此你可能會有些陌生。在這些框架中,URL通常很短,就像一個粗粒度的特定事件,告訴框架 “執行這個動作”。

JSF事件表示較細粒度的事件,諸如“用戶執行此命令”或“用戶改變此控件的值”。關於這些事件,最重要的是它們關聯到被請求的最後一個頁面。當JSF應 用從現有的頁面接收到請求時,它必須知道是哪個頁面發出這個請求,以便它能夠識別用戶產生了哪個事件,並將事件與發送頁面上的組件相關聯。

這就是恢復視圖階段的主要工作——找出當前的視圖並對其應用用戶的輸入。這時,它將從用戶的會話中查找視圖。如果服務器中的視圖無效(用戶之前沒有訪問過 該頁面),框架將捨棄當前視圖(如果有的話),並且基於請求的視圖標識符創建一個新視圖。一旦視圖被創建或者獲得,它便被存儲在當前的 FacesContext中

第1章的代碼清單1-1列出了Hello, world!應用程序中的hello.jsp的代碼。現在來看看錶示此頁面的組件樹(見圖2-5)。

圖2-5  hello.jsp產生的組件樹

你可以看到,頁面被表達爲一棵簡單的組件樹。視圖開始於UIViewRoot組件,它是該頁面上其他組件的容器。它有一個兒子,即標識符爲 welcomeForm的HtmlForm組件。此表單有多個兒子,包括稱爲helloInput的HtmlInputText組件和兩個分別稱爲 redisplayCommand和goodbyeCommand的 HtmlCommandButton組件。它還有一個子組件HtmlOutputLabel,該組件又有一個兒子HtmlOutputText組件。

恢復視圖也確保了組件的值,與樹中的組件相關聯的事件監聽器、驗證器或者轉換器,都被恢復。這裏,HtmlInputText組件有個LongRange 驗證器與之關聯,它也隨同組件在視圖中被恢復。另外,redisplayCommand有個action 屬性,而goodbyeCommand有個actionListener屬性,它們都在視圖中被恢復。

如果組件樹中的組件都綁定到後臺bean 屬性,則bean的屬性和組件的實例就要彼此保持同步。在這個例子中,HelloBean的controlPanel屬性將與視圖中名爲 controlPanel的HtmlPanelGrid組件保持同步。這樣允許後臺bean監聽器方法在處理事件時,在代碼中可以操作組件。

視圖語言也是在這個階段設置的,它基於發送到瀏覽器的HTTP 請求中的值。如果這是個回送請求,JSF 將進行下一階段的處理。然而,如果是初始請求(用戶首次請求這個頁面),JSF 將跳到呈現響應階段,因爲沒有用戶輸入需要處理。

2.2.2  階段2:應用請求值

每個接受輸入的UI組件都有代表用戶原始數據的被提交值(submitted value)。在應用請求值階段,框架基於請求中的參數設置被提交值。這一過程稱爲解碼

在hello.jsp中,每個組件都指定了一個組件標識符,如:

這就用標識符helloInput聲明瞭一個 HelloInputText組件。當JSF 將這個組件編碼爲HTML時,發送給瀏覽器的標識符就是,由該組件的標識符加上其父組件的標識符作爲前綴組成的。發送給瀏覽器的標識符,通常是輸入元素的 id屬性,稱爲客戶端標識符。例如,此HtmlInputText組件將有個客戶端標識符爲welcomeForm:helloInput,因爲它是名爲 welcomeForm的HtmlForm 的子組件(如圖2-5所示)。

如我們在清單2-1所見的HTTP 請求,兩個參數中有一個是helloInput,值爲"64"。在這個階段,JSF查詢每個組件並對其進行解碼。組件(或其呈現器)首先通過查找與其客戶 端標識符名稱相同的參數來完成這個任務。一旦找到參數,組件的值便設置爲參數的值。所以,這時,HtmlInputText 組件將設置其被提交值爲"64"。

每個具有可編輯值的輸入控件都有個 immediate屬性。如果這個屬性爲true,驗證將在本階段進行而不是要等到處理驗證階段。驗證的處理過程是一樣的,見下一節的詳細描述(在這個例 子中,沒有控件的immediate屬性設置爲true)。動作源,比如按鈕或者超鏈接,也有immediate屬性,如果該屬性被設置爲true,它將 在本階段觸發動作事件。早期處理這些事件是很方便的,例如,你可能會有個可以忽略表單中所有值的取消按鈕,或者有個超鏈接僅接受特定控件的值(這時,這些 控件的immediate屬性就該設置爲true)。

在請求中發送的另一個參數是"welcomeForm",值爲"welcomeForm"。這個參數存在時,HtmlForm 組件都設置其submitted屬性爲true。這允許你根據用戶是否提交表單執行不同的邏輯(一個視圖可有多個表單)。

在這一階段,解碼代碼也可添加事件或者基於請求執行其他操作。在這個示例應用中,在此階段,其他參數, 如"welcomeForm:redisplayCommand",被匹配客戶端標識符的 HtmlCommandButton控件轉換爲動作事件。一旦動作事件被創建,它便被添加到FacesContext中,以便隨後在調用應用階段做進一步 處理。

這個階段執行後,所有已有的事件都會廣播給激活的事件監聽器。任何呈現器、組件或者監聽器都可以截斷生命週期,直接跳到呈現響應階段,或者終止處理(如果它們自己呈現整個響應)。否則,每個輸入控件都將自行解碼,並最終具有基於當前請求的最新值。這正如此例所爲。

2.2.3  階段3:處理驗證

在處理驗證階段,JSF遍歷組件樹並檢查每個組件,看是否每個組件的被提交值都可以接受。因爲每個輸入組件的被提交值都在應用請求值階段進行了更新,這時 組件已經有了來自用戶的最新數據。驗證發生前,被提交值將首先由註冊到該組件的轉換器或者默認轉換器進行轉換。然後驗證直接由組件進行或者委託給一個或者 多個其他驗證器來進行。

如果轉換和驗證對提交了值的所有組件都成功,將到生命週期的下一階段。否則,控制將跳到呈現響應階段,隨驗證和轉換錯誤消息而完成。

Hello, world! 有一個LongRange驗證器關聯到HtmlInputText組件,而且組件的required屬性設置爲true:

這樣,視圖被創建時,驗證器實例也被創建,並且關聯到HtmlInputText組件。

因爲UI 組件的required 屬性設置爲true,它將首先檢查該組件的被提交值是否爲空。這裏,值爲"64",所以無疑是非空的。接下來,被提交值轉換成 helloBean.numControls屬性的類型,這是一個整數。然後調用LongRange驗證器來檢查其父組件的值是否有效。在這裏,有效意味 着值介於1~500(包含)之間。因爲64 確實是在1~500之間,所以該組件被視爲有效。

一旦完成對組件的被提交值的驗證,其本地值便會根據轉換過的提交值進行設置,這裏就是整數64。如果本地值被改變,組件也產生值改變事件。

在這裏,值改變事件(以及其他任何與此階段相關的事件)被觸發並且被相關的監聽器所處理。這些監聽器可以直接輸出響應或者跳到呈現響應階段。

如果所有的被提交值都視爲有效,JSF將進入生命週期的下一階段的。

2.2.4  階段4:更新模型值

現在我們已經確保組件的所有本地值都已被更新且是有效的,並具有正確的類型,這就可以開始處理相關的後臺bean 或模型對象了。因爲對象通過JSF EL表達式與組件相關聯,所以要計算表達式的值,並且根據組件的本地值更新屬性。再次來看看HtmlInputText的聲明:

可以看到組件的value屬性是表達式"# {helloBean.numControls}"。JSF將使用該表達式查找保存在關鍵字helloBean下的實例,它將依次查找每個上下文範圍—— 從請求、會話、到應用(見2.4.1節關於不同範圍的變量的詳細信息)。這裏,bean被存儲在會話中。一旦它被找到,組件將設置其特定的屬性值(這裏是 numControls)爲組件的本地值,當前是整數值64。

一旦完成此階段,框架將向激活的監聽器廣播所有事件。並且,監聽器總是可以跳到呈現響應階段或者自己產生返回響應。

最重要的是要記住,我們已經基於用戶的輸入更新了組件的值,驗證了這些值,並更新了相關的bean而沒有編寫任何應用代碼。這就是JSF的真正威力——它會自動完成了大量的UI 處理任務,因爲除了編寫討厭重複的代碼外,還有很多東西要做

2.2.5  階段5:調用應用

現在必需的後臺bean和模型對象都已更新,就可以開始涉及具體業務了。在調用應用階段,JSF 向所有激活的監聽器廣播此階段的所有事件。前面的例子中,針對redisplayCommand HtmlCommandButton控件產生了動作事件。所以在此階段,JSF 將發送動作事件到所有註冊到該組件的動作事件監聽器。這裏有兩個:在恢復視圖階段恢復的動作監聽器和默認的動作監聽器(自動爲每個組件註冊的默認監聽 器)。

下面是redisplayCommand的聲明:

組件的actionListener 屬性是JSF EL 表達式"#{helloBean.addControls}"。在此階段,JSF將求解表達式並且執行保存在會話中的helloBean實例的addControls方法。下面就是該方法的代碼:

這段代碼創建了numControls實例並將其添加到controlPanel,而後者是頁面中的HtmlPanelGrid實例(其兒子必須首先被清 空,否則每次此方法執行後其兒子的列表將不斷增長)。回想在恢復視圖階段,視圖和HelloBean的controlPanel屬性之間的綁定便已建立。 而且,numControls 屬性在更新模型值階段也被更新了。這個動作監聽器方法執行後,JSF 將調用默認的動作監聽器。該監聽器將控制委託給,註冊到產生該事件的組件中的其他動作方法,然後基於動作方法的邏輯結果選擇下個頁面。在這裏,爲 goodbyeCommand 註冊了動作方法,而爲redisplayCommand則沒有,而它正是產生該事件的組件。所以默認的動作監聽器將只是簡單地顯示當前視圖。

如在2.1.6節中討論的,動作監聽器或動作方法可以執行大量的操作,比如自己呈現響應、添加應用消息、產生事件或者執行應用邏輯。它們也可以直接跳到呈現響應階段以避免任何額外的應用事件處理。動作方法與導航系統集成,所以它們可以決定下一個要裝入的頁面。

需要指出的是,即使所有這些處理都在發生,這裏也是應用代碼的生長之處——通常你根本不需要擔心請求本身。一旦所有的監聽器都執行,JSF便可以向用戶顯示響應了。

2.2.6  階段6:呈現響應

到此,所有由框架進行的處理和應用邏輯都已經發生。剩下的就是發送響應給用戶,這也是呈現響應階段的主要目標。本階段的第二個目標是保存視圖的狀態,以便 用戶再次請求時可以在恢復視圖階段恢復該視圖。視圖要在本階段保存,是因爲通常視圖保存在客戶端,所以它也是發送給用戶的響應的一部分。此時,JSF在服 務器保存視圖,所以視圖最有可能保存在用戶的會話之中。

記住,JSF並不與特定的顯示技術相關。因此,我們有多種方法用來呈現響應:

l    僅使用視圖中的控件的編碼方法的輸出;

l    將編碼方法的輸出與產生標記的應用代碼集成;

l    將編碼方法的輸出與保存在靜態模板中的標記集成;

l    將編碼方法的輸出與動態源,比如JSP,集成。
 

所有的JSF都要求實現最後一種方法,可以將其濃縮爲,將請求轉發到視圖標識符所表示的源。在例子中,標識符是"/hello.jsp",所以調用的頁面 將僅是重新顯示。JSF 實現可以自由實現其他方式,以便它們可以與其他顯示技術相集成。見附錄A,那裏描述了不用JSP時,如何在其他技術中使用JSF 的例子。

在每個組件的編碼階段,都要調用轉換器以將組件的值轉換成供顯示的字符串。所以在此例中,HtmlInputText組件的整數值64將轉換成字符串。

至此結束。呈現響應階段是JSF 請求處理生命週期的最後階段。一旦它結束了,Web容器將結果字節通過物理傳輸發回給用戶,然後在用戶的瀏覽器中顯示。本例的輸出,即在瀏覽器中顯示的結果,如圖1-8所示。

現在,你已經能完全理解JSF 是如何工作的,接下來,我們來討論開發JSF應用的另一個重要方面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章