本月,Rizon Software 的 CTO Paul Tabor 應(yīng)邀與我一道解除針對 JSF 的 FUD。在本文中,我們將介紹 JSF 轉(zhuǎn)換和驗證框架的概念,它比您所想的要容易使用得多,也靈活得多。
首先我們將介紹應(yīng)用于 JSF 生命周期的轉(zhuǎn)換和驗證過程,然后展示一個簡單的 JSF 應(yīng)用程序中的默認(rèn)轉(zhuǎn)換和驗證過程。接著將展示如何創(chuàng)建和插入自定義的實現(xiàn),以應(yīng)對要求更高的場景。正如 Rick 在以前的文章中所說的,我們會理論與實踐并重,先介紹概念,再用一個實際例子說明這些概念的應(yīng)用。示例應(yīng)用程序?qū)⒑w大多數(shù)轉(zhuǎn)換和驗證用例,雖然只是初級的。
注意,示例應(yīng)用程序的默認(rèn)編譯環(huán)境是 Maven,不過, 還提供了一個 Ant 腳本??梢詥螕舯卷擁敳炕蛘叩撞康?Code 圖標(biāo)下載示例源代碼。為了簡便起見,您會發(fā)現(xiàn),該例子的設(shè)置與上一篇文章中的一樣。關(guān)于構(gòu)建環(huán)境配置的更多說明,包括在 Ant 環(huán)境中而不是在 Maven 環(huán)境中編譯和運行示例應(yīng)用程序的說明,請參閱參考資料。
轉(zhuǎn)換和驗證
雖然在 JSF Web 應(yīng)用程序中使用轉(zhuǎn)換和驗證不一定要理解 JavaServer Faces 生命周期的基礎(chǔ)知識,但是在深入轉(zhuǎn)換和驗證內(nèi)容之前,最好對一些基本知識做一回顧。此外,掌握一點 JSF 生命周期技巧可以極大地幫助簡化 Web 應(yīng)用程序的開發(fā)工作。還有助于更好地理解 JSF 的可插入能力。
圖 1 描繪了我們所說的“基本 JSF 生命周期”。 基本 是在暗示這只是一個典型的處理所提交表單值的請求-響應(yīng)(request-and-response)場景。
圖 1. 基本 JSF 生命周期
顯然,不同的場景對這里重點描述的生命周期有不同的影響。我們將在本文稍后介紹其中一些場景?,F(xiàn)在,只需要注意轉(zhuǎn)換和驗證過程發(fā)生在應(yīng)用請求值、處理驗證 和呈現(xiàn)響應(yīng) 階段即可。
我們將在稍后介紹為什么轉(zhuǎn)換和驗證會在這些階段出現(xiàn),但是首先讓我們澄清一個更基本的問題:轉(zhuǎn)換 是什么?簡單地說,轉(zhuǎn)換是確保數(shù)據(jù)擁有正確的對象或者類型的過程。下面是兩個典型的轉(zhuǎn)換:
字符串值可以轉(zhuǎn)換為 java.util.Date。 字符串值可以轉(zhuǎn)換為 Float。
至于驗證,它用于確保數(shù)據(jù)包含所期望的內(nèi)容。下面是兩個典型的驗證:
java.util.Date 的格式為 MM/yyyy。 Float 在 1.0 和 100.0 之間。
關(guān)注生命周期階段
轉(zhuǎn)換和驗證的主要目的是確保在更新模型數(shù)據(jù)之前已經(jīng)經(jīng)過了正確的無害處理。之后,當(dāng)需要調(diào)用應(yīng)用程序方法用這些些數(shù)據(jù)實際做一些事情 時,就可以有把握地假定模型的某些狀態(tài)。轉(zhuǎn)換和驗證使您可以側(cè)重于業(yè)務(wù)邏輯,而不是側(cè)重于對輸入數(shù)據(jù)進(jìn)行繁瑣的資格認(rèn)定,比如 null 檢驗、長度限定、范圍邊界,等等。
因此,在更新模型數(shù)據(jù) 生命周期階段中,在組件數(shù)據(jù)被綁定到 backing bean 模型之前 進(jìn)行轉(zhuǎn)換和驗證處理是有道理的。正如圖 1 所示,轉(zhuǎn)換發(fā)生在應(yīng)用請求值階段,而驗證發(fā)生在處理驗證階段。圖 2 突出顯示了這些階段。
圖 2. 要關(guān)注的轉(zhuǎn)換和驗證階段
關(guān)于 immediate 屬性
注意,圖 2 中描繪的轉(zhuǎn)換和驗證過程表示了將 UIInput 組件的 immediate 屬性設(shè)置為 false 時的應(yīng)用程序流程。如果這個屬性設(shè)置為 true,那么轉(zhuǎn)換和驗證會發(fā)生在生命周期更早的時期,即應(yīng)用請求值階段(參見圖 3)。對使用 immediate 屬性的詳細(xì)討論超出了本文的范圍,但是在某些情況下,比如管理動態(tài)清單(可能您還記得,本系列的上一篇文章中曾介紹過),它很有用,它甚至可以繞過驗證(在與 UICommand 組件結(jié)合使用時)。能想像一個需要完全繞過驗證的應(yīng)用程序嗎?
圖 3 展示了當(dāng) immediate 屬性設(shè)置為 true 時,在 JSF 應(yīng)用程序生命周期中的哪些地方進(jìn)行轉(zhuǎn)換和驗證。
圖 3. 將 immediate 屬性設(shè)置為 true
實際的例子
下面,我們將用一個示例應(yīng)用程序展示所討論的概念。本月的示例應(yīng)用程序?qū)⒄故?JSF 的轉(zhuǎn)換和驗證能力。記住,這個示例應(yīng)用程序非常簡單,沒有追求一些不必要的面面俱到:無論如何,我們的目的不是構(gòu)建一個在真實世界中使用的應(yīng)用程序!這個示例應(yīng)用程序?qū)⒄故疽韵聨c:
使用標(biāo)準(zhǔn) JSF 轉(zhuǎn)換器轉(zhuǎn)換表單字段數(shù)據(jù)。 使用標(biāo)準(zhǔn) JSF 驗證組件驗證表單字段數(shù)據(jù)。 如何編寫自定義轉(zhuǎn)換器和驗證器。 如何在 faces-config.xml 文件中注冊自定義轉(zhuǎn)換器和驗證器。 如何定制默認(rèn)錯誤消息。
這個示例應(yīng)用程序是一個簡單的用戶注冊表單。我們的目標(biāo)是收集用戶數(shù)據(jù),比如姓名、年齡、電子郵箱地址和電話號碼。然后,我們將展示如何利用 JSF 轉(zhuǎn)換和驗證確保收集的數(shù)據(jù)對于模型是適合的。
這個應(yīng)用程序使用了三個 JSP 頁:
index.jsp 將用戶定向到 UserRegistration.jsp。 UserRegistration.jsp 包含應(yīng)用程序的表單字段。 results.jsp 通知應(yīng)用程序用戶已經(jīng)注冊。
我們將首先分析編寫 JSF 轉(zhuǎn)換過程的選擇。
JSF 轉(zhuǎn)換
如前所述,轉(zhuǎn)換是確保數(shù)據(jù)對象或者類型正確的一個過程,因此,我們將字符串值轉(zhuǎn)換為其他類型,比如 Date 對象、基本浮點型或者 Float 對象。可以使用自帶的轉(zhuǎn)換器,也可以編寫自定義的轉(zhuǎn)換器。
JSF 提供了許多標(biāo)準(zhǔn)數(shù)據(jù)轉(zhuǎn)換器。也可以通過實現(xiàn) Converter 接口插入自定義轉(zhuǎn)換器,但是這些將在后面進(jìn)行介紹。下表顯示了 JSF 進(jìn)行簡單數(shù)據(jù)轉(zhuǎn)換所使用的轉(zhuǎn)換器 id 及其對應(yīng)的實現(xiàn)類。大多數(shù)數(shù)據(jù)轉(zhuǎn)換是自動發(fā)生的。
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 展示了用戶年齡的默認(rèn)轉(zhuǎn)換。JSF 標(biāo)簽配置如下:
<!-- UserRegistration.jsp --> <h:inputText id="age" value="#{UserRegistration.user.age}"/>
圖 4. 用戶注冊:年齡的默認(rèn)轉(zhuǎn)換
各種情況的轉(zhuǎn)換器
UserRegistration.user.age 表示一個值綁定屬性,它的類型為 int。對于基本型或者 BigInteger/ BigDecimal 的綁定,JSF 選擇了標(biāo)準(zhǔn)轉(zhuǎn)換器。不過,還可以通過 <f:converter/> 標(biāo)簽,利用一個特定的轉(zhuǎn)換器來增加粒度,如下所示。
<!-- UserRegistration.jsp --> <h:inputText id="age" value="#{UserRegistration.user.age}"> <f:converter id="javax.faces.Short"/> </h:inputText>
在圖 5 中,可以看到 JSF 使用標(biāo)準(zhǔn)轉(zhuǎn)換器的場景。在這種情況下,雖然年齡實際上是一個有效的整數(shù),但轉(zhuǎn)換仍然會失敗,因為該值不是短整型的。
圖 5. 使用 f:converter 標(biāo)簽
選擇日期格式樣式
盡管在默認(rèn)情況下,JSF 可以很好地處理基本型及類似的類型,但是在處理日期數(shù)據(jù)時,必須指定轉(zhuǎn)換標(biāo)簽 <f:convertDateTime/>。這個標(biāo)簽基于 java.text 包,并使用短、長和自定義樣式。下面是一個例子:
<!-- UserRegistration.jsp --> <h:inputText id="birthDate" value="#{UserRegistration.user.birthDate}"> <f:convertDateTime pattern="MM/yyyy"/> </h:inputText>
這個例子展示了如何用 <f:convertDateTime/> 確保用戶的生日可以轉(zhuǎn)換為格式為 MM/yyyy(月/年)的日期對象。請參閱 JSF 的 java.text.SimpleDataFormat (在 參考資料 中),以獲取模式列表。
其他樣式
除了可以轉(zhuǎn)換日期和時間格式外,JSF 還提供了處理像百分?jǐn)?shù)或者貨幣數(shù)據(jù)這類值的特殊轉(zhuǎn)換器。這個轉(zhuǎn)換器處理分組(如逗號)、小數(shù)、貨幣符號等。例如,以下 <f:convertNumber/> 的用法就是處理貨幣的一種技巧:
<!-- UserRegistration.jsp --> <h:inputText id="salary" value="#{UserRegistration.user.salary}"> <f:convertNumber maxFractionDigits="2" groupingUsed="true" currencySymbol="$" maxIntegerDigits="7" type="currency"/> </h:inputText>
在圖 6 中,可以看到一些格式編排不正確的貨幣數(shù)據(jù),以及所導(dǎo)致的轉(zhuǎn)換錯誤。
圖 6. 使用 f:convertNumber 標(biāo)簽
自定義轉(zhuǎn)換
如果需要將字段數(shù)據(jù)轉(zhuǎn)換為特定于應(yīng)用程序的值對象,則需要自定義數(shù)據(jù)轉(zhuǎn)換,如下面例子所示:
String 轉(zhuǎn)換為 PhoneNumber 對象 (PhoneNumber.areaCode、PhoneNumber.prefix、 ...)。 String 轉(zhuǎn)換為 Name 對象 (Name.first、Name.last)。 String 轉(zhuǎn)換為 ProductCode 對象 (ProductCode.partNum、ProductCode.rev、 ...)。
要創(chuàng)建自定義轉(zhuǎn)換器,必須完成以步驟:
實現(xiàn) Converter 接口(也就是 javax.faxes.convert.Converter)。
實現(xiàn) getAsObject 方法,它將一個字段(字符串)轉(zhuǎn)換為一個對象(例如,PhoneNumber)。
實現(xiàn) getAsString 方法,它將一個對象(如 PhoneNumber)轉(zhuǎn)換為一個字符串。
在 Faces 上下文中注冊自定義轉(zhuǎn)換器。
用 <f:converter/> 標(biāo)簽在 JSP 中插入這個轉(zhuǎn)換器。
您可以自己看到如何在 JSF 應(yīng)用程序生命周期中加入這些步驟。在圖 7 中,JSF 在應(yīng)用請求值階段調(diào)用自定義轉(zhuǎn)換器的 getAsObject 方法。轉(zhuǎn)換器必須在這里將請求字符串轉(zhuǎn)換為所需的對象類型,然后返回這個對象,將它存儲在相應(yīng)的 JSF 組件中。如果該值被返回呈現(xiàn)在視圖中,那么 JSF 將在呈現(xiàn)響應(yīng)階段調(diào)用 getAsString 方法。這意味著轉(zhuǎn)換器還要負(fù)責(zé)將對象數(shù)據(jù)轉(zhuǎn)換回字符串表示形式。
圖 7. 自定義轉(zhuǎn)換器 getAsObject 和 getAsString 方法
創(chuàng)建自定義轉(zhuǎn)換器
我們將使用一個案例分析來展示 Converter 接口、getAsObject 和 getAsString 方法的實現(xiàn),同時還將展示如何在 Faces 上下文中注冊這個轉(zhuǎn)換器。
這個案例分析的目的是將一個單字段字符串值轉(zhuǎn)換為一個 PhoneNumber 對象。我們將一步一步地完成這個轉(zhuǎn)換過程。
第 1 步:實現(xiàn) Converter 接口
這一步實現(xiàn) Converter 接口。
import javax.faces.convert.Converter; import org.apache.commons.lang.StringUtils; ... public class PhoneConverter implements Converter { ... }
第 2 步:實現(xiàn) getAsObject 方法
這一步將一個字段值轉(zhuǎn)換為一個 PhoneNumber 對象。
public class PhoneConverter implements Converter { ... public Object getAsObject(FacesContext context, UIComponent component, String value) { if (StringUtils.isEmpty(value)){ return null;} PhoneNumber phone = new PhoneNumber(); String [] phoneComps = StringUtils.split(value," ,()-"); String countryCode = phoneComps[0]; phone.setCountryCode(countryCode); if ("1".equals(countryCode)){ String areaCode = phoneComps[1]; String prefix = phoneComps[2]; String number = phoneComps[3]; phone.setAreaCode(areaCode); phone.setPrefix(prefix); phone.setNumber(number); }else { phone.setNumber(value); } return phone; } }
第 3 步:實現(xiàn) getAsString 方法
這一步將一個 PhoneNumber 對象轉(zhuǎn)換為一個字符串。
public class PhoneConverter implements Converter { ... public String getAsString(FacesContext context, UIComponent component, Object value) { return value.toString(); } } public class PhoneNumber implements Serializable { ... public String toString(){ if (countryCode.equals("1")){ return countryCode + " " + areaCode + " " + prefix + " " + number; }else{ return number; } } }
第 4 步:在 faces 上下文中注冊自定義轉(zhuǎn)換器
第 4 步可以以兩種方式執(zhí)行。第一種選擇使用(比如)arcmind.PhoneConverter 的 id 來注冊 PhoneConverter 類。JSP 頁中的 <f:converter/> 標(biāo)簽會使用這個 id。下面是第 4 步的選項 1 的代碼:
<converter> <converter-id>arcmind.PhoneConverter</converter-id> <converter-class>com.arcmind.converters.PhoneConverter</converter-class> </converter>
另一種方法是注冊 PhoneConverter 類來自動處理所有 PhoneNumber 對象,如下所示。
<converter> <converter-for-class>com.arcmind.value.PhoneNumber</converter-for-class> <converter-class>com.arcmind.converters.PhoneConverter</converter-class> </converter>
第 5 步:在 JSP 中使用轉(zhuǎn)換器標(biāo)簽?
自然,下一步的執(zhí)行取決于所選的注冊方法。如果選擇使用 arcmind.PhoneConverter 的 id 來注冊 PhoneConverter 類,那么就使用 <f:converter/> 標(biāo)簽,如下所示。
<h:inputText id="phone" value="#{UserRegistration.user.phone}"> <f:converter converterId="arcmind.PhoneConverter" /> </h:inputText>
如果選擇注冊 PhoneConverter 類來自動 處理所有 PhoneNumber,那么就不需要在 JSP 頁中使用 <f:converter/> 標(biāo)簽。下面是第 5 步的不帶轉(zhuǎn)換器標(biāo)簽的代碼。
<h:inputText id="phone" value="#{UserRegistration.user.phone}"> [Look mom no converter!] </h:inputText>
這樣,我們已經(jīng)完成了這個示例應(yīng)用程序的轉(zhuǎn)換處理代碼!到目前為止完成的應(yīng)用程序如下所示。
圖 8. 帶有轉(zhuǎn)換處理的示例應(yīng)用程序
JSF 驗證
如前所述,JSF 驗證可以確保應(yīng)用程序數(shù)據(jù)包含預(yù)期的內(nèi)容,例如:
java.util.Date 為 MM/yyyy 格式。 Float 在 1.0 和 100.0 之間。
在 JSF 中有 4 種驗證:
自帶驗證組件。 應(yīng)用程序級驗證。 自定義驗證組件(它實現(xiàn)了 Validator 接口)。 在 backing bean 中的驗證方法(內(nèi)聯(lián))。
我們將在下面的討論中介紹并展示每一種形式。
JSF 驗證生命周期和組件
圖 9 顯示了用戶注冊表單中名字字段的生命周期案例分析。代碼引用被有意解釋為偽代碼(pseudo-code)。
圖 9. JSF 生命周期中的驗證
下面是 JSF 提供的一組標(biāo)準(zhǔn)驗證組件:
DoubleRangeValidator:組件的本地值必須為數(shù)字類型,必須在由最小和/或最大值所指定的范圍內(nèi)。
LongRangeValidator:組件的本地值必須為數(shù)字類型,并且可以轉(zhuǎn)換為長整型,必須在由最小和/或最大值所指定的范圍內(nèi)。
LengthValidator:類型必須為字符串,長度必須在由最小和/或最大值所指定的范圍內(nèi)。
標(biāo)準(zhǔn)驗證
在我們的示例應(yīng)用程序中,用戶的年齡可以是任意有效的整數(shù)(byte、short、int)。因為將年齡設(shè)置為(比如說)-2 是無意義的,所以可能要對這個字段添加一些驗證。下面是一些簡單的驗證代碼,用以確保年齡字段中的數(shù)據(jù)模型完整性:
<h:inputText id="age" value="#{UserRegistration.user.age}"> <f:validateLongRange maximum="150" minimum="0"/> </h:inputText>
完成年齡字段后,可能希望指定對名字字段的長度加以限制。可以像這樣編寫這個驗證:
<h:inputText id="firstName" value="#{UserRegistration.user.firstName}"> <f:validateLength minimum="2" maximum="25" /> </h:inputText>
圖 10 顯示了由上面標(biāo)準(zhǔn)驗證示例所生成的默認(rèn)詳細(xì)驗證消息。
圖 10. 標(biāo)準(zhǔn)驗證錯誤消息
盡管 JSF 自帶的驗證在許多情況下都可以滿足,但是它有一些局限性。在處理電子郵件驗證、電話號碼、URL、日期等數(shù)據(jù)時,有時編寫自己的驗證器會更好一些,不過我們將在稍后對此進(jìn)行討論。
應(yīng)用程序級驗證
在概念上,應(yīng)用程序級驗證實際上是業(yè)務(wù)邏輯驗證。JSF 將表單和/或字段級驗證與業(yè)務(wù)邏輯驗證分離開。應(yīng)用程序級驗證主要需要在 backing bean 中添加代碼,用這個模型確定綁定到模型中的數(shù)據(jù)是否合格。對于購物車,表單級驗證可以驗證輸入的數(shù)量是否有效,但是需要使用業(yè)務(wù)邏輯驗證檢查用戶是否超出了他或者她的信用額度。這是在 JSF 中分離關(guān)注點的另一個例子。
例如,假定用戶單擊了綁定到某個操作方法的按鈕,那么就會在調(diào)用應(yīng)用程序階段調(diào)用這個方法(有關(guān)的細(xì)節(jié),請參見上面的圖 1)。假定在更新模型階段進(jìn)行了更新,那么在對模型數(shù)據(jù)執(zhí)行任何操縱之前,可以添加一些驗證代碼,根據(jù)應(yīng)用程序的業(yè)務(wù)規(guī)則檢查輸入的數(shù)據(jù)是否有效。
例如,在這個示例應(yīng)用程序中,用戶單擊了 Register 按鈕,這個按鈕被綁定到應(yīng)用程序控制器的 register() 方法。我們可以在 register() 方法中添加驗證代碼,以確定名字字段是否為 null。如果該字段為 null,那么還可以在 FacesContext 中添加一條消息,指示相關(guān)組件返回到當(dāng)前頁。
其實它現(xiàn)在并不是業(yè)務(wù)規(guī)則邏輯的一個好例子。更好的例子是檢查用戶是否超出了她或者她的信用額度。在該例中,不是檢查字段是否為空,我們可以調(diào)用模型對象的方法來確保當(dāng)前用戶已經(jīng)不在系統(tǒng)中。
圖 11 描繪了這個過程。
圖 11. 應(yīng)用程序級驗證
注意在 register() 方法中,消息是如何以 ${formId}:${fieldId} 的形式添加到 FacesContext 中的。圖 12 顯示了消息與組件 id 之間的關(guān)系。
圖 12. 驗證消息
應(yīng)用程序級驗證的優(yōu)缺點
應(yīng)用級驗證非常直觀并且容易實現(xiàn)。不過,這種形式的驗證是在其他形式的驗證(標(biāo)準(zhǔn)、自定義、組件)之后發(fā)生的。
應(yīng)用程序級驗證的優(yōu)點如下:
容易實現(xiàn)。 不需要單獨的類(自定義驗證器)。 不需要頁編寫者指定驗證器。
應(yīng)用程序級驗證的缺點如下:
在其他形式的驗證(標(biāo)準(zhǔn)、自定義)之后發(fā)生。 驗證邏輯局限于 backing bean 方法,使得重用性很有限。 在大型應(yīng)用程序和/或團(tuán)隊環(huán)境中可能難于管理。
最終,應(yīng)用程序級驗證只應(yīng)該用于那些需要業(yè)務(wù)邏輯驗證的環(huán)境中。
自定義驗證組件
對于標(biāo)準(zhǔn) JSF 驗證器不支持的數(shù)據(jù)類型,則需要建立自己的自定義驗證組件,其中包括電子郵件地址和郵政編碼。如果需要明確控制顯示給最終用戶的消息,那么還需要建立自己的驗證器。在 JSF 中,可以創(chuàng)建可在整個 Web 應(yīng)用程序中重復(fù)使用的可插入驗證組件。
MyFaces 是一個 JSF 的開放源代碼實現(xiàn),它提供了許多額外的驗證器,其中包括一些 JSF 中所沒有的驗證器。要了解 MyFaces 的內(nèi)容,請參閱參考資料。
創(chuàng)建自定義驗證器的步驟如下,我們將一步步地分析:
創(chuàng)建一個實現(xiàn)了 Validator 接口的類 (javax.faces.validator.Validator)。
實現(xiàn) validate 方法。
在 faces-confix.xml 文件中注冊自定義驗證。
在 JSP 頁中使用 <f:validator/> 標(biāo)簽。
下面是創(chuàng)建自定義驗證器的分步示例代碼。
第 1:實現(xiàn) Validator 接口
第一步是實現(xiàn) Validator 接口。
import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; ... public class ZipCodeValidator implements Validator{ private boolean plus4Required; private boolean plus4Optional; /** Accepts zip codes like 85710 */ private static final String ZIP_REGEX = "[0-9]{5}"; /** Accepts zip code plus 4 extensions like "-1119" or " 1119" */ private static final String PLUS4_REQUIRED_REGEX = "[ |-]{1}[0-9]{4}"; /** Optionally accepts a plus 4 */ private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?"; ... }
第 2 步:實現(xiàn)驗證方法
接下來,需要實現(xiàn) validate 方法。
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException { /* Create the correct mask */ Pattern mask = null; /* more on this method later */ initProps(component); if (plus4Required){ mask = Pattern.compile(ZIP_REGEX + PLUS4_REQUIRED_REGEX); } else if (plus4Optional){ mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX); } else if (plus4Required && plus4Optional){ throw new IllegalStateException("Plus 4 is either optional or required"); } else { mask = Pattern.compile(ZIP_REGEX); } /* Get the string value of the current field */ String zipField = (String)value; /* Check to see if the value is a zip code */ Matcher matcher = mask.matcher(zipField); if (!matcher.matches()){ FacesMessage message = new FacesMessage(); message.setDetail("Zip code not valid"); message.setSummary("Zip code not valid"); message.setSeverity(FacesMessage.SEVERITY_ERROR); throw new ValidatorException(message); } }
第 3 步:在 FacesContext 中注冊自定義驗證器
您現(xiàn)在應(yīng)該熟悉在 FacesContext 中注冊自定義驗證器的代碼了。
<validator> <validator-id>arcmind.zipCodeValidator</validator-id> <validator-class>com.arcmind.jsfquickstart.validation.ZipCodeValidator</validator-class> </validator>
第 4 步:在 JSP 中使用 <f:validator/> 標(biāo)簽
<f:validator/> 標(biāo)簽聲明使用 zipCodeValidator。<f:attribute/> 標(biāo)簽將 plus4Optional 屬性設(shè)置為 true。注意,它定義了 inputText 組件的屬性,而不是 驗證器的屬性!
<h:inputText id="zipCode" value="#{UserRegistration.user.zipCode}"> <f:validator validatorId="armind.zipCodeValidator"/> <f:attribute name="plus4Optional" value="true"/> </h:inputText>
為了讀取 zipCodeinputText 組件的 plus4Optional 屬性,請完成以下步驟::
private void initProps(UIComponent component) { Boolean optional = Boolean.valueOf((String) component.getAttributes(). get("plus4Optional")); Boolean required = Boolean.valueOf((String) component.getAttributes(). get("plus4Required")); plus4Optional = optional==null ? plus4Optional : optional.booleanValue(); plus4Required = required==null ? plus4Optional : required.booleanValue(); }
總體而言,創(chuàng)建自定義驗證器是相當(dāng)直觀的,并且可以使該驗證在許多應(yīng)用程序中重復(fù)使用。缺點是必須創(chuàng)建一個類,并在 faces 上下文中管理驗證器注冊。不過,通過創(chuàng)建一個使用這個驗證器的自定義標(biāo)簽,使其看上去像是一個自帶的驗證,可以進(jìn)一步實現(xiàn)自定義驗證器。對于常見的驗證問題,如電子郵件驗證,這種方法可以支持這樣一種設(shè)計理念,即代碼重用和一致的應(yīng)用程序行為是最重要的。
backing bean 中的驗證方法
作為創(chuàng)建單獨的驗證器類的替代方法,可以只在 backing bean 的方法中實現(xiàn)自定義驗證,只要這個方法符合 Validator 接口的 validate 方法的參數(shù)簽名即可。例如,可以編寫以下方法:
[SomeBackingBean.java] public void validateEmail(FacesContext context, UIComponent toValidate, Object value) { String email = (String) value; if (email.indexOf(@) == -1) { ((UIInput)toValidate).setValid(false); FacesMessage message = new FacesMessage("Invalid Email"); context.addMessage(toValidate.getClientId(context), message); } }
之后,可通過如下所示的 validator 屬性在 JSF 中使用這個方法:
<h:inputText id="email" value="#{UserRegistration.user.email}" validator="#{UserRegistration.validateEmail}" required="true"> </h:inputText>
JSF 用 validateEmail 方法對綁定到 user.email 模型屬性的 inputText 組件值進(jìn)行自定義驗證。如果電子郵件格式無效,那么就在相關(guān)組件的 faces 上下文中添加消息??紤]到這種驗證方法實際上是 backing bean 的一部分,為什么通常必須用某個值與相關(guān)組件的關(guān)聯(lián)來評估該值,而不是直接檢查本地 bean 屬性呢?線索就在前面的生命周期圖中。如果現(xiàn)在不能馬上找到答案,也不要擔(dān)心,我們將在本文的最后對此加以說明。
默認(rèn)驗證
注意上面 email 標(biāo)簽的 required 屬性。利用 required 屬性是一種默認(rèn) 驗證形式。如果這個屬性是 true,那么相應(yīng)的組件必須有一個值。一個重要的說明:如果 required 屬性為 false,那么就不用對這個標(biāo)簽/組件指派驗證,這樣,JSF 將跳過對這個組件的驗證,并讓值和組件的狀態(tài)保持不變。
圖 13 概述了我們討論過的驗證形式。
圖 13. 驗證視圖
自定義消息
您可能注意到了,JSF 提供的默認(rèn)轉(zhuǎn)換和驗證消息非常長,這會讓那些總是輸入無效表單數(shù)據(jù)的最終用戶感到困惑和惱火。幸運的是,您可以通過創(chuàng)建自己的消息資源綁定來改變 JSF 提供的默認(rèn)消息。jsf-impl.jar (或類似的文件中)中包含了一個 message.properties 文件,該文件包含圖 14 所示的默認(rèn)消息。
圖 14. 默認(rèn) JSF 轉(zhuǎn)換和驗證消息
通過創(chuàng)建自己的 message.properties 文件并斷開指定場所的 faces 上下文中綁定的消息資源,您可以更改默認(rèn)消息,如圖 15 所示。
圖 15. 取消消息資源綁定
關(guān)于在 JSF 中創(chuàng)建自定義轉(zhuǎn)換和驗證消息的更多內(nèi)容請參前閱 參考資料。
處理 JSF 生命周期
我們在本文前面留下了一些問題讓您考慮,現(xiàn)在可以解決它們了!我們提到的一件事是對 UICommand 按鈕使用 immediate 屬性,比如 commandLink 或者 commandButtons?,F(xiàn)在請您考慮希望在什么樣的場景中跳過驗證。
基本上只要用戶需要輸入數(shù)據(jù),就需要對這個數(shù)據(jù)進(jìn)行驗證。不過,如果整個數(shù)據(jù)項是可選的,那么就不需要進(jìn)行驗證。一種避免 JSF 生命周期的驗證階段的方法是利用 UICommand 組件的 immediate 屬性,該屬性可以在處理驗證階段之前 的應(yīng)用請求值階段期間(而不是在處理驗證階段 之后 的調(diào)用應(yīng)用程序階段)強制調(diào)用這個操作。
immediate 屬性允許您通過標(biāo)準(zhǔn)瀏覽規(guī)則控制頁流程,并繞過驗證。可以針對特定的場景實現(xiàn)這項技術(shù),比如帶有可選步驟和/或表單的在線向?qū)Вㄈ绠?dāng)用戶單擊 Skip 按鈕以進(jìn)入下一視圖),或者在用戶因為某種原因而取消某個表單的情況下。
我們在本文中留下的第二個問題是:既然驗證方法實際上是 backing bean 的一部分,那么為什么通常必須利用組件關(guān)聯(lián)來判斷它的值。請參閱前面的 JSF 應(yīng)用程序生命周期,看看您能否找到答案。
這里的密訣是:盡管 validateEmail 嵌入驗證方法是實際的 backing bean 的一部分,但是該方法必須通過組件關(guān)聯(lián)來引用這,而不是直接訪問本地屬性來引用值。由于驗證發(fā)生在組件值綁定到模型之前(在更新模型值階段),所以模型處于未知狀態(tài)。因此,必須編寫嵌入自定義驗證邏輯,就像使用一個自定義 Validator 對象處理驗證一樣。這也解釋了維護(hù)相同方法簽名的需求。
這些尚待解決的枝節(jié)問題有什么意義呢,當(dāng)然,它們最終將我們帶回 JSF 應(yīng)用程序生命周期。將這些問題匯總在一起,就能體現(xiàn)充分理解生命周期的重要性 —— 向后、向前或由內(nèi)向外,這樣您就可以在需要的時候操縱它。
結(jié)束語
在本文中我們討論了相當(dāng)多的 JSF 轉(zhuǎn)換和驗證的基本內(nèi)容。事實上,我們討論了在自己的應(yīng)用程序中使用這些過程需要知道的大部分內(nèi)容(至少對這個版本的 JSF 而言)!
當(dāng)然,我們不可能討論到 所有內(nèi)容。例如,您可能想要了解 MyFaces (請參閱 參考資料)中 JSF 沒有提供、或者這里沒有討論到的驗證器組件。此外,雖然我們討論了大多數(shù)常用的轉(zhuǎn)換和驗證技術(shù),但還有一些沒有包含在內(nèi)。例如,在編寫自定義組件時,可以在組件的解碼/編碼過程中直接處理轉(zhuǎn)換和/或驗證(取決于組件的類型及其功能),但是我們只能將對自定義組件開發(fā)的更深入討論留到以后進(jìn)行了。
其他要牢記的是轉(zhuǎn)換和驗證不一定會很好地協(xié)同工作。轉(zhuǎn)換將字符串轉(zhuǎn)換為對象,而大多數(shù)標(biāo)準(zhǔn)驗證是對字符串進(jìn)行的。因此,在同時使用自定義轉(zhuǎn)換和驗證必須格外小心。例如,PhoneNumber 對象不能與長度驗證器一起使用。在這種情況下,要么編寫自定義驗證器,要么在自定義轉(zhuǎn)換器中添加一個特別的驗證邏輯。我們偏向后一種方法,因為它讓我們可以將自定義轉(zhuǎn)換器(自帶驗證邏輯)與特定的對象類型相關(guān)聯(lián),并讓 JSF 處理這種對象類型。JSF 自動為我們做這項工作,不需要在 JSP 中包含任何特定的轉(zhuǎn)換器 id。(當(dāng)然,有人會稱它為懶惰編程,它也不是對所有用例都適用的最佳解決方案。)
我們認(rèn)為本月文章中的討論再次聲明了以下這點,即 JSF 提供了一種靈活的、強大的可插入式 Web 應(yīng)用程序開發(fā)框架。除了標(biāo)準(zhǔn)轉(zhuǎn)換器和驗證器之外,JSF 還可以促進(jìn)同時滿足應(yīng)用程序和框架開發(fā)人員的要求的自定義實現(xiàn)。最終,要由您來確定選擇何種轉(zhuǎn)換和驗證策略。JSF 使您能夠在原型制造階段很快、很容易地上手(標(biāo)準(zhǔn)轉(zhuǎn)換器、驗證器、內(nèi)部驗證等),并在以后的開發(fā)階段移植到更復(fù)雜的生產(chǎn)解決方案中(自定義對象、自定義消息等)。JSF 生命周期在所有階段都提供了可靠的基礎(chǔ)設(shè)施,始終如一地保證數(shù)據(jù)模型的完整性。
在下個月中,我們將深入分析如何用 JSF 編寫自已的自定義組件,并結(jié)束這一系列。
參考資料 加入本文的 論壇. (您也可以通過點擊文章頂部或者底部的論壇 鏈接參加討論.)
您可以參閱本文在 developerWorks 全球站點上的 英文原文。
單擊本頁頂部或者底部的 Code 圖標(biāo),以下載本文的源代碼。
不要相信那些針對 JSF 的 FUD —— 也不要錯過 懷疑論者的 JSF 系列中的任何一篇文章。
請參閱 JSF 主頁,下載 JavaServer Faces API、自定義標(biāo)簽庫和相關(guān)文檔。
可以從 Apache Maven Project 頁面 下載 Maven。
關(guān)于 Ant 和 Maven 的詳細(xì)安裝和編譯指令,請參閱本系列的 JSF 參考資料。
請參閱 MyFaces 主頁上介紹的、JSF 目前尚未提供的驗證器組件。
Jackwind Li Guojie 撰寫的“用 JavaServer Faces 進(jìn)行 UI 開發(fā)”(developerWorks,2003 年 9 月)最先介紹了這項技術(shù)。
Roland Barcia 撰寫的由 5 部分組成的“Developing JSF Applications using WebSphere Studio V5.1.1" 教程(developerWorks, 2004 年 1 月),是對使用 JSF 進(jìn)行編程的一篇實用性介紹文章。
在前沿文章 “ 集成 Struts、Tiles 和 JavaServer Faces”(developerWorks,2003 年 9 月)中,Srikanth Shenoy 和 Nithin Mallya 向您介紹了如何集成 Struts、Tiles 和 JavaServer Faces 的特性。
David Geary 撰寫的 Core JavaServer Faces 一書(Prentice Hall, 2004 年 6 月),是對 JavaServer Faces 技術(shù)進(jìn)行的最好的書籍形式的介紹,其中包括關(guān)于創(chuàng)建自定義消息的討論。
您可能還想查看 Sun Microsystems 的 詳細(xì) JSF 教程。
在 developerWorks 的 Java 技術(shù)專區(qū) 中,可以找到關(guān)于 Java 編程各個方面的文章。
請參閱 Developer Bookstore,以獲得技術(shù)書籍的完整清單,其中包括數(shù)百本 Java 相關(guān)主題的書籍。
還請參閱 Java 技術(shù)專區(qū)教程頁,以獲得 developerWorks 的免費 Java 教程的完整清單。