首先我們將介紹應用于 JSF 生命周期的轉換和驗證過程,然后展示一個簡單的 JSF 應用程序中的默認轉換和驗證過程。接著將展示如何創建和插入自定義的實現,以應對要求更高的場景。正如 Rick 在以前的文章中所說的,我們會理論與實踐并重,先介紹概念,再用一個實際例子說明這些概念的應用。示例應用程序將涵蓋大多數轉換和驗證用例,雖然只是初級的。
注意,示例應用程序的默認編譯環境是 Maven,不過, 還提供了一個 Ant 腳本??梢詥螕舯卷擁敳炕蛘叩撞康? Code 圖標下載示例源代碼。為了簡便起見,您會發現,該例子的設置與上一篇文章中的一樣。關于構建環境配置的更多說明,包括在 Ant 環境中而不是在 Maven 環境中編譯和運行示例應用程序的說明,請參閱 參考資料 。
轉換和驗證
雖然在 JSF Web 應用程序中使用轉換和驗證不一定要理解 JavaServer Faces 生命周期的基礎知識,但是在深入轉換和驗證內容之前,最好對一些基本知識做一回顧。此外,掌握一點 JSF 生命周期技巧可以極大地幫助簡化 Web 應用程序的開發工作。還有助于更好地理解 JSF 的可插入能力。
圖 1 描繪了我們所說的“基本 JSF 生命周期”。 基本 是在暗示這只是一個典型的處理所提交表單值的請求-響應(request-and-response)場景。
顯然,不同的場景對這里重點描述的生命周期有不同的影響。我們將在本文稍后介紹其中一些場景。現在,只需要注意轉換和驗證過程發生在 應用請求值 、 處理驗證 和 呈現響應 階段即可。
我們將在稍后介紹為什么轉換和驗證會在這些階段出現,但是首先讓我們澄清一個更基本的問題: 轉換 是什么?簡單地說,轉換是確保數據擁有正確的對象或者類型的過程。下面是兩個典型的轉換:
-
字符串值可以轉換為
java.util.Date
。 - 字符串值可以轉換為 Float。
至于 驗證 ,它用于確保數據包含所期望的內容。下面是兩個典型的驗證:
- java.util.Date 的格式為 MM/yyyy。
- Float 在 1.0 和 100.0 之間。
關注生命周期階段
轉換和驗證的主要目的是確保在更新模型數據之前已經經過了正確的
無害處理
。之后,當需要調用應用程序方法用這些些數據實際
做一些事情
時,就可以有把握地假定模型的某些狀態。轉換和驗證使您可以側重于業務邏輯,而不是側重于對輸入數據進行繁瑣的資格認定,比如 null 檢驗、長度限定、范圍邊界,等等。
因此,在 更新模型數據 生命周期階段中,在組件數據被綁定到 backing bean 模型 之前 進行轉換和驗證處理是有道理的。正如圖 1 所示,轉換發生在應用請求值階段,而驗證發生在處理驗證階段。圖 2 突出顯示了這些階段。
關于 immediate 屬性
注意,圖 2 中描繪的轉換和驗證過程表示了將
UIInput
組件的
immediate
屬性設置為
false
時的應用程序流程。如果這個屬性設置為
true
,那么轉換和驗證會發生在生命周期更早的時期,即應用請求值階段(參見圖 3)。對使用 immediate 屬性的詳細討論超出了本文的范圍,但是在某些情況下,比如管理動態清單(可能您還記得,本系列的上一篇文章中曾介紹過),它很有用,它甚至可以繞過驗證(在與
UICommand
組件結合使用時)。能想像一個需要完全繞過驗證的應用程序嗎?
圖 3 展示了當
immediate
屬性設置為
true
時,在 JSF 應用程序生命周期中的哪些地方進行轉換和驗證。
實際的例子
下面,我們將用一個示例應用程序展示所討論的概念。本月的示例應用程序將展示 JSF 的轉換和驗證能力。記住,這個示例應用程序非常簡單,沒有追求一些不必要的面面俱到:無論如何,我們的目的不是構建一個在真實世界中使用的應用程序!這個示例應用程序將展示以下幾點:
- 使用標準 JSF 轉換器轉換表單字段數據。
- 使用標準 JSF 驗證組件驗證表單字段數據。
- 如何編寫自定義轉換器和驗證器。
- 如何在 faces-config.xml 文件中注冊自定義轉換器和驗證器。
- 如何定制默認錯誤消息。
這個示例應用程序是一個簡單的用戶注冊表單。我們的目標是收集用戶數據,比如姓名、年齡、電子郵箱地址和電話號碼。然后,我們將展示如何利用 JSF 轉換和驗證確保收集的數據對于模型是適合的。
這個應用程序使用了三個 JSP 頁:
- index.jsp 將用戶定向到 UserRegistration.jsp。
- UserRegistration.jsp 包含應用程序的表單字段。
- results.jsp 通知應用程序用戶已經注冊。
我們將首先分析編寫 JSF 轉換過程的選擇。
JSF 轉換
如前所述,轉換是確保數據對象或者類型正確的一個過程,因此,我們將字符串值轉換為其他類型,比如
Date
對象、基本浮點型或者
Float
對象??梢允褂米詭У霓D換器,也可以編寫自定義的轉換器。
JSF 提供了許多標準數據轉換器。也可以通過實現
Converter
接口插入自定義轉換器,但是這些將在后面進行介紹。下表顯示了 JSF 進行簡單數據轉換所使用的轉換器
id
及其對應的實現類。大多數數據轉換是自動發生的。
javax.faces.BigDecimal
|
javax.faces.convert.BigDecimalConverter
|
javax.faces.BigInteger
|
javax.faces.convert.BigIntegerConverter
|
javax.faces.Boolean
|
javax.faces.convert.BooleanConverter
|
javax.faces.Byte
|
javax.faces.convert.ByteConverter
|
javax.faces.Character
|
javax.faces.convert.CharacterConverter
|
javax.faces.DateTime
|
javax.faces.convert.DateTimeConverter
|
javax.faces.Double
|
javax.faces.convert.DoubleConverter
|
javax.faces.Float
|
javax.faces.convert.FloatConverter
|
圖 4 展示了用戶年齡的默認轉換。JSF 標簽配置如下:
|
各種情況的轉換器
UserRegistration.user.age
表示一個值綁定屬性,它的類型為
int
。對于基本型或者
BigInteger
/
BigDecimal
的綁定,JSF 選擇了標準轉換器。不過,還可以通過
<f:converter/>
標簽,利用一個特定的轉換器來增加粒度,如下所示。
|
在圖 5 中,可以看到 JSF 使用標準轉換器的場景。在這種情況下,雖然年齡實際上是一個有效的整數,但轉換仍然會失敗,因為該值不是短整型的。
選擇日期格式樣式
盡管在默認情況下,JSF 可以很好地處理基本型及類似的類型,但是在處理日期數據時,必須指定轉換標簽
<f:convertDateTime/>
。這個標簽基于
java.text
包,并使用短、長和自定義樣式。下面是一個例子:
|
這個例子展示了如何用
<f:convertDateTime/>
確保用戶的生日可以轉換為格式為 MM/yyyy(月/年)的日期對象。請參閱 JSF 的
java.text.SimpleDataFormat
(在
參考資料
中),以獲取模式列表。
其他樣式
除了可以轉換日期和時間格式外,JSF 還提供了處理像百分數或者貨幣數據這類值的特殊轉換器。這個轉換器處理分組(如逗號)、小數、貨幣符號等。例如,以下
<f:convertNumber/>
的用法就是處理貨幣的一種技巧:
|
在圖 6 中,可以看到一些格式編排不正確的貨幣數據,以及所導致的轉換錯誤。
自定義轉換
如果需要將字段數據轉換為特定于應用程序的值對象,則需要自定義數據轉換,如下面例子所示:
- String 轉換為 PhoneNumber 對象 (PhoneNumber.areaCode、PhoneNumber.prefix、 ...)。
- String 轉換為 Name 對象 (Name.first、Name.last)。
- String 轉換為 ProductCode 對象 (ProductCode.partNum、ProductCode.rev、 ...)。
要創建自定義轉換器,必須完成以步驟:
-
實現
Converter
接口(也就是javax.faxes.convert.Converter
)。
-
實現
getAsObject
方法,它將一個字段(字符串)轉換為一個對象(例如,PhoneNumber
)。
-
實現
getAsString
方法,它將一個對象(如PhoneNumber
)轉換為一個字符串。
-
在
Faces
上下文中注冊自定義轉換器。
-
用
<f:converter/>
標簽在 JSP 中插入這個轉換器。
您可以自己看到如何在 JSF 應用程序生命周期中加入這些步驟。在圖 7 中,JSF 在應用請求值階段調用自定義轉換器的
getAsObject
方法。轉換器必須在這里將請求字符串轉換為所需的對象類型,然后返回這個對象,將它存儲在相應的 JSF 組件中。如果該值被返回呈現在視圖中,那么 JSF 將在呈現響應階段調用
getAsString
方法。這意味著轉換器還要負責將對象數據轉換回字符串表示形式。
圖 7. 自定義轉換器 getAsObject 和 getAsString 方法
創建自定義轉換器
我們將使用一個案例分析來展示
Converter
接口、
getAsObject
和
getAsString
方法的實現,同時還將展示如何在
Faces
上下文中注冊這個轉換器。
這個案例分析的目的是將一個單字段字符串值轉換為一個
PhoneNumber
對象。我們將一步一步地完成這個轉換過程。
第 1 步:實現 Converter 接口
這一步實現
Converter
接口。
|
第 2 步:實現 getAsObject 方法
這一步將一個字段值轉換為一個
PhoneNumber
對象。
|
第 3 步:實現 getAsString 方法
這一步將一個
PhoneNumber
對象轉換為一個字符串。
|
第 4 步:在 faces 上下文中注冊自定義轉換器
第 4 步可以以兩種方式執行。第一種選擇使用(比如)
arcmind.PhoneConverter
的 id 來注冊
PhoneConverter
類。JSP 頁中的
<f:converter/>
標簽會使用這個 id。下面是第 4 步的選項 1 的代碼:
|
另一種方法是注冊
PhoneConverter
類來自動處理所有
PhoneNumber
對象,如下所示。
|
第 5 步:在 JSP 中使用轉換器標簽?
自然,下一步的執行取決于所選的注冊方法。如果選擇使用
arcmind.PhoneConverter
的 id 來注冊
PhoneConverter
類,那么就使用
<f:converter/>
標簽,如下所示。
|
如果選擇注冊
PhoneConverter
類來
自動
處理所有
PhoneNumber
,那么就不需要在 JSP 頁中使用
<f:converter/>
標簽。下面是第 5 步的不帶轉換器標簽的代碼。
|
這樣,我們已經完成了這個示例應用程序的轉換處理代碼!到目前為止完成的應用程序如下所示。
JSF 驗證
如前所述,JSF 驗證可以確保應用程序數據包含預期的內容,例如:
- java.util.Date 為 MM/yyyy 格式。
- Float 在 1.0 和 100.0 之間。
在 JSF 中有 4 種驗證:
- 自帶驗證組件。
- 應用程序級驗證。
-
自定義驗證組件(它實現了
Validator
接口)。 - 在 backing bean 中的驗證方法(內聯)。
我們將在下面的討論中介紹并展示每一種形式。
JSF 驗證生命周期和組件
圖 9 顯示了用戶注冊表單中名字字段的生命周期案例分析。代碼引用被有意解釋為偽代碼(pseudo-code)。
下面是 JSF 提供的一組標準驗證組件:
-
DoubleRangeValidator
:組件的本地值必須為數字類型,必須在由最小和/或最大值所指定的范圍內。
-
LongRangeValidator
:組件的本地值必須為數字類型,并且可以轉換為長整型,必須在由最小和/或最大值所指定的范圍內。
-
LengthValidator
:類型必須為字符串,長度必須在由最小和/或最大值所指定的范圍內。
標準驗證
在我們的示例應用程序中,用戶的年齡可以是任意有效的整數(byte、short、int)。因為將年齡設置為(比如說)
-2
是無意義的,所以可能要對這個字段添加一些驗證。下面是一些簡單的驗證代碼,用以確保年齡字段中的數據模型完整性:
|
完成年齡字段后,可能希望指定對名字字段的長度加以限制。可以像這樣編寫這個驗證:
|
圖 10 顯示了由上面標準驗證示例所生成的默認詳細驗證消息。
盡管 JSF 自帶的驗證在許多情況下都可以滿足,但是它有一些局限性。在處理電子郵件驗證、電話號碼、URL、日期等數據時,有時編寫自己的驗證器會更好一些,不過我們將在稍后對此進行討論。
應用程序級驗證
在概念上,應用程序級驗證實際上是業務邏輯驗證。JSF 將表單和/或字段級驗證與業務邏輯驗證分離開。應用程序級驗證主要需要在 backing bean 中添加代碼,用這個模型確定綁定到模型中的數據是否合格。對于購物車,表單級驗證可以驗證輸入的數量是否有效,但是需要使用業務邏輯驗證檢查用戶是否超出了他或者她的信用額度。這是在 JSF 中分離關注點的另一個例子。
例如,假定用戶單擊了綁定到某個操作方法的按鈕,那么就會在調用應用程序階段調用這個方法(有關的細節,請參見上面的 圖 1 )。假定在更新模型階段進行了更新,那么在對模型數據執行任何操縱之前,可以添加一些驗證代碼,根據應用程序的業務規則檢查輸入的數據是否有效。
例如,在這個示例應用程序中,用戶單擊了
Register
按鈕,這個按鈕被綁定到應用程序控制器的
register()
方法。我們可以在
register()
方法中添加驗證代碼,以確定名字字段是否為 null。如果該字段為 null,那么還可以在
FacesContext
中添加一條消息,指示相關組件返回到當前頁。
其實它現在并不是業務規則邏輯的一個好例子。更好的例子是檢查用戶是否超出了她或者她的信用額度。在該例中,不是檢查字段是否為空,我們可以調用模型對象的方法來確保當前用戶已經不在系統中。
圖 11 描繪了這個過程。
注意在
register()
方法中,消息是如何以
${formId}:${fieldId}
的形式添加到
FacesContext
中的。圖 12 顯示了消息與組件 id 之間的關系。
應用程序級驗證的優缺點
應用級驗證非常直觀并且容易實現。不過,這種形式的驗證是在其他形式的驗證(標準、自定義、組件)之后發生的。
應用程序級驗證的優點如下:
- 容易實現。
- 不需要單獨的類(自定義驗證器)。
- 不需要頁編寫者指定驗證器。
應用程序級驗證的缺點如下:
- 在其他形式的驗證(標準、自定義)之后發生。
- 驗證邏輯局限于 backing bean 方法,使得重用性很有限。
- 在大型應用程序和/或團隊環境中可能難于管理。
最終,應用程序級驗證只應該用于那些需要業務邏輯驗證的環境中。
自定義驗證組件
對于標準 JSF 驗證器不支持的數據類型,則需要建立自己的自定義驗證組件,其中包括電子郵件地址和郵政編碼。如果需要明確控制顯示給最終用戶的消息,那么還需要建立自己的驗證器。在 JSF 中,可以創建可在整個 Web 應用程序中重復使用的可插入驗證組件。
MyFaces 是一個 JSF 的開放源代碼實現,它提供了許多額外的驗證器,其中包括一些 JSF 中所沒有的驗證器。要了解 MyFaces 的內容,請參閱 參考資料 。 |
創建自定義驗證器的步驟如下,我們將一步步地分析:
-
創建一個實現了
Validator
接口的類 (javax.faces.validator.Validator
)。
-
實現
validate
方法。
-
在 faces-confix.xml 文件中注冊自定義驗證。
-
在 JSP 頁中使用
<f:validator/>
標簽。
下面是創建自定義驗證器的分步示例代碼。
第 1:實現 Validator 接口
第一步是實現
Validator
接口。
|
第 2 步:實現驗證方法
接下來,需要實現
validate
方法。
|
第 3 步:在 FacesContext 中注冊自定義驗證器
您現在應該熟悉在
FacesContext
中注冊自定義驗證器的代碼了。
|
第 4 步:在 JSP 中使用 <f:validator/> 標簽
<f:validator/>
標簽聲明使用
zipCodeValidator
。
<f:attribute/>
標簽將
plus4Optional
屬性設置為
true
。注意,它定義了
inputText
組件的屬性,而
不是
驗證器的屬性!
|
為了讀取
zipCode
inputText
組件的
plus4Optional
屬性,請完成以下步驟::
|
總體而言,創建自定義驗證器是相當直觀的,并且可以使該驗證在許多應用程序中重復使用。缺點是必須創建一個類,并在 faces 上下文中管理驗證器注冊。不過,通過創建一個使用這個驗證器的自定義標簽,使其看上去像是一個自帶的驗證,可以進一步實現自定義驗證器。對于常見的驗證問題,如電子郵件驗證,這種方法可以支持這樣一種設計理念,即代碼重用和一致的應用程序行為是最重要的。
backing bean 中的驗證方法
作為創建單獨的驗證器類的替代方法,可以只在 backing bean 的方法中實現自定義驗證,只要這個方法符合
Validator
接口的
validate
方法的參數簽名即可。例如,可以編寫以下方法:
|
之后,可通過如下所示的
validator
屬性在 JSF 中使用這個方法:
|
JSF 用
validateEmail
方法對綁定到
user.email
模型屬性的
inputText
組件值進行自定義驗證。如果電子郵件格式無效,那么就在相關組件的 faces 上下文中添加消息。考慮到這種驗證方法實際上是 backing bean 的一部分,為什么通常必須用某個值與相關組件的關聯來評估該值,而不是直接檢查本地 bean 屬性呢?線索就在前面的生命周期圖中。如果現在不能馬上找到答案,也不要擔心,我們將在本文的最后對此加以說明。
默認驗證
注意上面
email
標簽的
required
屬性。利用
required
屬性是一種
默認
驗證形式。如果這個屬性是
true
,那么相應的組件必須有一個值。一個重要的說明:如果
required
屬性為
false
,那么就不用對這個標簽/組件指派驗證,這樣,JSF 將跳過對這個組件的驗證,并讓值和組件的狀態保持不變。
圖 13 概述了我們討論過的驗證形式。
自定義消息
您可能注意到了,JSF 提供的默認轉換和驗證消息非常長,這會讓那些總是輸入無效表單數據的最終用戶感到困惑和惱火。幸運的是,您可以通過創建自己的消息資源綁定來改變 JSF 提供的默認消息。jsf-impl.jar (或類似的文件中)中包含了一個 message.properties 文件,該文件包含圖 14 所示的默認消息。
通過創建自己的 message.properties 文件并斷開指定場所的 faces 上下文中綁定的消息資源,您可以更改默認消息,如圖 15 所示。
關于在 JSF 中創建自定義轉換和驗證消息的更多內容請參前閱 參考資料 。
處理 JSF 生命周期
我們在本文前面留下了一些問題讓您考慮,現在可以解決它們了!我們提到的一件事是對
UICommand
按鈕使用
immediate 屬性
,比如
commandLink
或者
commandButtons
?,F在請您考慮希望在什么樣的場景中跳過驗證。
基本上只要用戶需要輸入數據,就需要對這個數據進行驗證。不過,如果整個數據項是可選的,那么就不需要進行驗證。一種避免 JSF 生命周期的驗證階段的方法是利用
UICommand
組件的
immediate
屬性,該屬性可以在處理驗證階段
之前
的應用請求值階段期間(而不是在處理驗證階段
之后
的調用應用程序階段)強制調用這個操作。
immediate
屬性允許您通過標準瀏覽規則控制頁流程,并繞過驗證。可以針對特定的場景實現這項技術,比如帶有可選步驟和/或表單的在線向導(如當用戶單擊
Skip
按鈕以進入下一視圖),或者在用戶因為某種原因而取消某個表單的情況下。
我們在本文中留下的第二個問題是:既然驗證方法實際上是 backing bean 的一部分,那么為什么通常必須利用組件關聯來判斷它的值。請參閱前面的 JSF 應用程序生命周期 ,看看您能否找到答案。
這里的密訣是:盡管
validateEmail
嵌入驗證方法是實際的 backing bean 的一部分,但是該方法必須通過組件關聯來引用這,而不是直接訪問本地屬性來引用值。由于驗證發生在組件值綁定到模型
之前
(在更新模型值階段),所以模型處于未知狀態。因此,必須編寫嵌入自定義驗證邏輯,就像使用一個自定義
Validator
對象處理驗證一樣。這也解釋了維護相同方法簽名的需求。
這些尚待解決的枝節問題有什么意義呢,當然,它們最終將我們帶回 JSF 應用程序生命周期。將這些問題匯總在一起,就能體現充分理解生命周期的重要性 —— 向后、向前或由內向外,這樣您就可以在需要的時候操縱它。
結束語
在本文中我們討論了相當多的 JSF 轉換和驗證的基本內容。事實上,我們討論了在自己的應用程序中使用這些過程需要知道的大部分內容(至少對這個版本的 JSF 而言)!
當然,我們不可能討論到 所有內容 。例如,您可能想要了解 MyFaces (請參閱 參考資料 )中 JSF 沒有提供、或者這里沒有討論到的驗證器組件。此外,雖然我們討論了大多數常用的轉換和驗證技術,但還有一些沒有包含在內。例如,在編寫自定義組件時,可以在組件的解碼/編碼過程中直接處理轉換和/或驗證(取決于組件的類型及其功能),但是我們只能將對自定義組件開發的更深入討論留到以后進行了。
其他要牢記的是轉換和驗證不一定會很好地協同工作。轉換將字符串轉換為對象,而大多數標準驗證是對字符串進行的。因此,在同時使用自定義轉換和驗證必須格外小心。例如,
PhoneNumber
對象不能與長度驗證器一起使用。在這種情況下,要么編寫自定義驗證器,要么在自定義轉換器中添加一個特別的驗證邏輯。我們偏向后一種方法,因為它讓我們可以將自定義轉換器(自帶驗證邏輯)與特定的對象類型相關聯,并讓 JSF 處理這種對象類型。JSF 自動為我們做這項工作,不需要在 JSP 中包含任何特定的轉換器 id。(當然,有人會稱它為懶惰編程,它也不是對所有用例都適用的最佳解決方案。)
我們認為本月文章中的討論再次聲明了以下這點,即 JSF 提供了一種靈活的、強大的可插入式 Web 應用程序開發框架。除了標準轉換器和驗證器之外,JSF 還可以促進同時滿足應用程序和框架開發人員的要求的自定義實現。最終,要由您來確定選擇何種轉換和驗證策略。JSF 使您能夠在原型制造階段很快、很容易地上手(標準轉換器、驗證器、內部驗證等),并在以后的開發階段移植到更復雜的生產解決方案中(自定義對象、自定義消息等)。JSF 生命周期在所有階段都提供了可靠的基礎設施,始終如一地保證數據模型的完整性。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
