本月,Rizon Software 的 CTO Paul Tabor 應邀與我一道解除針對 JSF 的 FUD。在本文中,我們將介紹 JSF 轉換和驗證框架的概念,它比您所想的要容易使用得多,也靈活得多。
首先我們將介紹應用於 JSF 生命周期的轉換和驗證過程,然後展示一個簡單的 JSF 應用程序中的默認轉換和驗證過程。接著將展示如何創建和插入自定義的實現,以應對要求更高的場景。正如 Rick 在以前的文章中所說的,我們會理論與實踐並重,先介紹概念,再用一個實際例子說明這些概念的應用。示例應用程序將涵蓋大多數轉換和驗證用例,雖然只是初級的。
注意,示例應用程序的默認編譯環境是 Maven,不過, 還提供了一個 Ant 腳本。可以單擊本頁頂部或者底部的 Code 圖標下載示例源代碼。為了簡便起見,您會發現,該例子的設置與上一篇文章中的一樣。關於構建環境配置的更多說明,包括在 Ant 環境中而不是在 Maven 環境中編譯和運行示例應用程序的說明,請參閱參考資料。
轉換和驗證
雖然在 JSF Web 應用程序中使用轉換和驗證不一定要理解 JavaServer Faces 生命周期的基礎知識,但是在深入轉換和驗證內容之前,最好對一些基本知識做一回顧。此外,掌握一點 JSF 生命周期技巧可以極大地幫助簡化 Web 應用程序的開發工作。還有助於更好地理解 JSF 的可插入能力。
圖 1 描繪了我們所說的“基本 JSF 生命周期”。 基本 是在暗示這只是一個典型的處理所提交表單值的請求-響應(request-and-response)場景。
圖 1. 基本 JSF 生命周期
顯然,不同的場景對這裡重點描述的生命周期有不同的影響。我們將在本文稍後介紹其中一些場景。現在,只需要注意轉換和驗證過程發生在應用請求值、處理驗證 和呈現響應 階段即可。
我們將在稍後介紹為什麼轉換和驗證會在這些階段出現,但是首先讓我們澄清一個更基本的問題:轉換 是什麼?簡單地說,轉換是確保數據擁有正確的對象或者類型的過程。下面是兩個典型的轉換:
字符串值可以轉換為 java.util.Date。
字符串值可以轉換為 Float。
至於驗證,它用於確保數據包含所期望的內容。下面是兩個典型的驗證:
java.util.Date 的格式為 MM/yyyy。
Float 在 1.0 和 100.0 之間。
關注生命周期階段
轉換和驗證的主要目的是確保在更新模型數據之前已經經過了正確的無害處理。之後,當需要調用應用程序方法用這些些數據實際做一些事情 時,就可以有把握地假定模型的某些狀態。轉換和驗證使您可以側重於業務邏輯,而不是側重於對輸入數據進行繁瑣的資格認定,比如 null 檢驗、長度限定、范圍邊界,等等。
因此,在更新模型數據 生命周期階段中,在組件數據被綁定到 backing bean 模型之前 進行轉換和驗證處理是有道理的。正如圖 1 所示,轉換發生在應用請求值階段,而驗證發生在處理驗證階段。圖 2 突出顯示了這些階段。
圖 2. 要關注的轉換和驗證階段
關於 immediate 屬性
注意,圖 2 中描繪的轉換和驗證過程表示了將 UIInput 組件的 immediate 屬性設置為 false 時的應用程序流程。如果這個屬性設置為 true,那麼轉換和驗證會發生在生命周期更早的時期,即應用請求值階段(參見圖 3)。對使用 immediate 屬性的詳細討論超出了本文的范圍,但是在某些情況下,比如管理動態清單(可能您還記得,本系列的上一篇文章中曾介紹過),它很有用,它甚至可以繞過驗證(在與 UICommand 組件結合使用時)。能想像一個需要完全繞過驗證的應用程序嗎?
圖 3 展示了當 immediate 屬性設置為 true 時,在 JSF 應用程序生命周期中的哪些地方進行轉換和驗證。
圖 3. 將 immediate 屬性設置為 true
實際的例子
下面,我們將用一個示例應用程序展示所討論的概念。本月的示例應用程序將展示 JSF 的轉換和驗證能力。記住,這個示例應用程序非常簡單,沒有追求一些不必要的面面俱到:無論如何,我們的目的不是構建一個在真實世界中使用的應用程序!這個示例應用程序將展示以下幾點:
使用標准 JSF 轉換器轉換表單字段數據。
使用標准 JSF 驗證組件驗證表單字段數據。
如何編寫自定義轉換器和驗證器。
如何在 faces-config.xml 文件中注冊自定義轉換器和驗證器。
如何定制默認錯誤消息。
這個示例應用程序是一個簡單的用戶注冊表單。我們的目標是收集用戶數據,比如姓名、年齡、電子郵箱地址和電話號碼。然後,我們將展示如何利用 JSF 轉換和驗證確保收集的數據對於模型是適合的。
這個應用程序使用了三個 JSP 頁:
index.jsp 將用戶定向到 UserRegistration.jsp。
UserRegistration.jsp 包含應用程序的表單字段。
results.jsp 通知應用程序用戶已經注冊。
我們將首先分析編寫 JSF 轉換過程的選擇。
JSF 轉換
如前所述,轉換是確保數據對象或者類型正確的一個過程,因此,我們將字符串值轉換為其他類型,比如 Date 對象、基本浮點型或者 Float 對象。可以使用自帶的轉換器,也可以編寫自定義的轉換器。
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.jsp -->
<h:inputText id="age" value="#{UserRegistration.user.age}"/>
圖 4. 用戶注冊:年齡的默認轉換
各種情況的轉換器
UserRegistration.user.age 表示一個值綁定屬性,它的類型為 int。對於基本型或者 BigInteger/ BigDecimal 的綁定,JSF 選擇了標准轉換器。不過,還可以通過 <f:converter/> 標簽,利用一個特定的轉換器來增加粒度,如下所示。
<!-- UserRegistration.jsp -->
<h:inputText id="age" value="#{UserRegistration.user.age}">
<f:converter id="javax.faces.Short"/>
</h:inputText>
在圖 5 中,可以看到 JSF 使用標准轉換器的場景。在這種情況下,雖然年齡實際上是一個有效的整數,但轉換仍然會失敗,因為該值不是短整型的。
圖 5. 使用 f:converter 標簽
選擇日期格式樣式
盡管在默認情況下,JSF 可以很好地處理基本型及類似的類型,但是在處理日期數據時,必須指定轉換標簽 <f:convertDateTime/>。這個標簽基於 java.text 包,並使用短、長和自定義樣式。下面是一個例子:
<!-- UserRegistration.jsp -->
<h:inputText id="birthDate" value="#{UserRegistration.user.birthDate}">
<f:convertDateTime pattern="MM/yyyy"/>
</h:inputText>
這個例子展示了如何用 <f:convertDateTime/> 確保用戶的生日可以轉換為格式為 MM/yyyy(月/年)的日期對象。請參閱 JSF 的 java.text.SimpleDataFormat (在 參考資料 中),以獲取模式列表。
其他樣式
除了可以轉換日期和時間格式外,JSF 還提供了處理像百分數或者貨幣數據這類值的特殊轉換器。這個轉換器處理分組(如逗號)、小數、貨幣符號等。例如,以下 <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 中,可以看到一些格式編排不正確的貨幣數據,以及所導致的轉換錯誤。
圖 6. 使用 f:convertNumber 標簽
自定義轉換
如果需要將字段數據轉換為特定於應用程序的值對象,則需要自定義數據轉換,如下面例子所示:
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 接口。
import javax.faces.convert.Converter;
import org.apache.commons.lang.StringUtils;
...
public class PhoneConverter implements Converter {
...
}
第 2 步:實現 getAsObject 方法
這一步將一個字段值轉換為一個 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 步:實現 getAsString 方法
這一步將一個 PhoneNumber 對象轉換為一個字符串。
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 上下文中注冊自定義轉換器
第 4 步可以以兩種方式執行。第一種選擇使用(比如)arcmind.PhoneConverter 的 id 來注冊 PhoneConverter 類。JSP 頁中的 <f:converter/> 標簽會使用這個 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 中使用轉換器標簽?
自然,下一步的執行取決於所選的注冊方法。如果選擇使用 arcmind.PhoneConverter 的 id 來注冊 PhoneConverter 類,那麼就使用 <f:converter/> 標簽,如下所示。
<h:inputText id="phone" value="#{UserRegistration.user.phone}">
<f:converter converterId="arcmind.PhoneConverter" />
</h:inputText>
如果選擇注冊 PhoneConverter 類來自動 處理所有 PhoneNumber,那麼就不需要在 JSP 頁中使用 <f:converter/> 標簽。下面是第 5 步的不帶轉換器標簽的代碼。
<h:inputText id="phone" value="#{UserRegistration.user.phone}">
[Look mom no converter!]
</h:inputText>
這樣,我們已經完成了這個示例應用程序的轉換處理代碼!到目前為止完成的應用程序如下所示。
圖 8. 帶有轉換處理的示例應用程序
JSF 驗證
如前所述,JSF 驗證可以確保應用程序數據包含預期的內容,例如:
java.util.Date 為 MM/yyyy 格式。
Float 在 1.0 和 100.0 之間。
在 JSF 中有 4 種驗證:
自帶驗證組件。
應用程序級驗證。
自定義驗證組件(它實現了 Validator 接口)。
在 backing bean 中的驗證方法(內聯)。
我們將在下面的討論中介紹並展示每一種形式。
JSF 驗證生命周期和組件
圖 9 顯示了用戶注冊表單中名字字段的生命周期案例分析。代碼引用被有意解釋為偽代碼(pseudo-code)。
圖 9. JSF 生命周期中的驗證
下面是 JSF 提供的一組標准驗證組件:
DoubleRangeValidator:組件的本地值必須為數字類型,必須在由最小和/或最大值所指定的范圍內。
LongRangeValidator:組件的本地值必須為數字類型,並且可以轉換為長整型,必須在由最小和/或最大值所指定的范圍內。
LengthValidator:類型必須為字符串,長度必須在由最小和/或最大值所指定的范圍內。
標准驗證
在我們的示例應用程序中,用戶的年齡可以是任意有效的整數(byte、short、int)。因為將年齡設置為(比如說)-2 是無意義的,所以可能要對這個字段添加一些驗證。下面是一些簡單的驗證代碼,用以確保年齡字段中的數據模型完整性:
<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 顯示了由上面標准驗證示例所生成的默認詳細驗證消息。
圖 10. 標准驗證錯誤消息
盡管 JSF 自帶的驗證在許多情況下都可以滿足,但是它有一些局限性。在處理電子郵件驗證、電話號碼、URL、日期等數據時,有時編寫自己的驗證器會更好一些,不過我們將在稍後對此進行討論。
應用程序級驗證
在概念上,應用程序級驗證實際上是業務邏輯驗證。JSF 將表單和/或字段級驗證與業務邏輯驗證分離開。應用程序級驗證主要需要在 backing bean 中添加代碼,用這個模型確定綁定到模型中的數據是否合格。對於購物車,表單級驗證可以驗證輸入的數量是否有效,但是需要使用業務邏輯驗證檢查用戶是否超出了他或者她的信用額度。這是在 JSF 中分離關注點的另一個例子。
例如,假定用戶單擊了綁定到某個操作方法的按鈕,那麼就會在調用應用程序階段調用這個方法(有關的細節,請參見上面的圖 1)。假定在更新模型階段進行了更新,那麼在對模型數據執行任何操縱之前,可以添加一些驗證代碼,根據應用程序的業務規則檢查輸入的數據是否有效。
例如,在這個示例應用程序中,用戶單擊了 Register 按鈕,這個按鈕被綁定到應用程序控制器的 register() 方法。我們可以在 register() 方法中添加驗證代碼,以確定名字字段是否為 null。如果該字段為 null,那麼還可以在 FacesContext 中添加一條消息,指示相關組件返回到當前頁。
其實它現在並不是業務規則邏輯的一個好例子。更好的例子是檢查用戶是否超出了她或者她的信用額度。在該例中,不是檢查字段是否為空,我們可以調用模型對象的方法來確保當前用戶已經不在系統中。
圖 11 描繪了這個過程。
圖 11. 應用程序級驗證
注意在 register() 方法中,消息是如何以 ${formId}:${fieldId} 的形式添加到 FacesContext 中的。圖 12 顯示了消息與組件 id 之間的關系。
圖 12. 驗證消息
應用程序級驗證的優缺點
應用級驗證非常直觀並且容易實現。不過,這種形式的驗證是在其他形式的驗證(標准、自定義、組件)之後發生的。
應用程序級驗證的優點如下:
容易實現。
不需要單獨的類(自定義驗證器)。
不需要頁編寫者指定驗證器。
應用程序級驗證的缺點如下:
在其他形式的驗證(標准、自定義)之後發生。
驗證邏輯局限於 backing bean 方法,使得重用性很有限。
在大型應用程序和/或團隊環境中可能難於管理。
最終,應用程序級驗證只應該用於那些需要業務邏輯驗證的環境中。
自定義驗證組件
對於標准 JSF 驗證器不支持的數據類型,則需要建立自己的自定義驗證組件,其中包括電子郵件地址和郵政編碼。如果需要明確控制顯示給最終用戶的消息,那麼還需要建立自己的驗證器。在 JSF 中,可以創建可在整個 Web 應用程序中重復使用的可插入驗證組件。
創建自定義驗證器的步驟如下,我們將一步步地分析:
創建一個實現了 Validator 接口的類 (javax.faces.validator.Validator)。
實現 validate 方法。
在 faces-confix.xml 文件中注冊自定義驗證。
在 JSP 頁中使用 <f:validator/> 標簽。
下面是創建自定義驗證器的分步示例代碼。
第 1:實現 Validator 接口
第一步是實現 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 步:實現驗證方法
接下來,需要實現 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 中注冊自定義驗證器
您現在應該熟悉在 FacesContext 中注冊自定義驗證器的代碼了。
<validator>
<validator-id>arcmind.zipCodeValidator</validator-id>
<validator-class>com.arcmind.jsfquickstart.validation.ZipCodeValidator</validator-class>
</validator>
第 4 步:在 JSP 中使用 <f:validator/> 標簽
<f:validator/> 標簽聲明使用 zipCodeValidator。<f:attribute/> 標簽將 plus4Optional 屬性設置為 true。注意,它定義了 inputText 組件的屬性,而不是 驗證器的屬性!
<h:inputText id="zipCode" value="#{UserRegistration.user.zipCode}">
<f:validator validatorId="armind.zipCodeValidator"/>
<f:attribute name="plus4Optional" value="true"/>
</h:inputText>
為了讀取 zipCode inputText 組件的 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();
}
總體而言,創建自定義驗證器是相當直觀的,並且可以使該驗證在許多應用程序中重復使用。缺點是必須創建一個類,並在 faces 上下文中管理驗證器注冊。不過,通過創建一個使用這個驗證器的自定義標簽,使其看上去像是一個自帶的驗證,可以進一步實現自定義驗證器。對於常見的驗證問題,如電子郵件驗證,這種方法可以支持這樣一種設計理念,即代碼重用和一致的應用程序行為是最重要的。
backing bean 中的驗證方法
作為創建單獨的驗證器類的替代方法,可以只在 backing bean 的方法中實現自定義驗證,只要這個方法符合 Validator 接口的 validate 方法的參數簽名即可。例如,可以編寫以下方法:
[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 組件值進行自定義驗證。如果電子郵件格式無效,那麼就在相關組件的 faces 上下文中添加消息。考慮到這種驗證方法實際上是 backing bean 的一部分,為什麼通常必須用某個值與相關組件的關聯來評估該值,而不是直接檢查本地 bean 屬性呢?線索就在前面的生命周期圖中。如果現在不能馬上找到答案,也不要擔心,我們將在本文的最後對此加以說明。
默認驗證
注意上面 email 標簽的 required 屬性。利用 required 屬性是一種默認 驗證形式。如果這個屬性是 true,那麼相應的組件必須有一個值。一個重要的說明:如果 required 屬性為 false,那麼就不用對這個標簽/組件指派驗證,這樣,JSF 將跳過對這個組件的驗證,並讓值和組件的狀態保持不變。
圖 13 概述了我們討論過的驗證形式。
圖 13. 驗證視圖
自定義消息
您可能注意到了,JSF 提供的默認轉換和驗證消息非常長,這會讓那些總是輸入無效表單數據的最終用戶感到困惑和惱火。幸運的是,您可以通過創建自己的消息資源綁定來改變 JSF 提供的默認消息。jsf-impl.jar (或類似的文件中)中包含了一個 message.properties 文件,該文件包含圖 14 所示的默認消息。
圖 14. 默認 JSF 轉換和驗證消息
通過創建自己的 message.properties 文件並斷開指定場所的 faces 上下文中綁定的消息資源,您可以更改默認消息,如圖 15 所示。
圖 15. 取消消息資源綁定
關於在 JSF 中創建自定義轉換和驗證消息的更多內容請參前閱 參考資料。
處理 JSF 生命周期
我們在本文前面留下了一些問題讓您考慮,現在可以解決它們了!我們提到的一件事是對 UICommand 按鈕使用 immediate 屬性,比如 commandLink 或者 commandButtons。現在請您考慮希望在什麼樣的場景中跳過驗證。
基本上只要用戶需要輸入數據,就需要對這個數據進行驗證。不過,如果整個數據項是可選的,那麼就不需要進行驗證。一種避免 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 生命周期在所有階段都提供了可靠的基礎設施,始終如一地保證數據模型的完整性。
在下個月中,我們將深入分析如何用 JSF 編寫自已的自定義組件,並結束這一系列。