數據驗證是應用程序健壯性的體現,在實際項目中也是必不可少的環節。Play內置了驗證器(Validation)的支持,並提供了非常靈活的使用方法。在Play項目中可以很簡單地對數據,模型對象(可能需要持久化)以及HTTP表單進行驗證。
1.1 使用Play驗證器#
出於數據驗證的考慮,框架爲每個請求綁定了驗證器。應用代碼中可以通過以下三種方式對數據進行驗證:
1.在控制器的Action方法中,直接使用驗證器對數據進行相應的驗證:
public static void hello(String name) {
validation.required(name); //驗證name參數是否被賦值
...
}
2.在控制器的Action方法的參數中,使用註解進行數據驗證:
public static void hello(@Required String name){
...
}
3.定義域模型時直接爲屬性添加驗證註解,Action中就可以通過@Valid註解對該POJO參數進行驗證了:
public class User extends Model {
@Required
public String name; //爲User的name屬性添加@Required驗證
...
}
//直接使用@Valid註解對POJO參數進行驗證
public static void save(@Valid User user) {
...
}
框架爲每個請求綁定了一個驗證器,同個驗證器可以校驗多個數據。
當驗證不通過時,驗證器會將錯誤以play.data.validation.Error的形式保存,因此每個驗證器以集合的形式維護了一系列的error對象。每個error對象有key和message兩個屬性:
- key:該屬性幫助我們標識引起錯誤的元素。key的值可以任意設定,默認與驗證的數據同名。
- message:驗證消息,用於描述驗證不通過的錯誤信息。message可以是純文本或指向消息包(message bundle)的key(通常爲了支持國際化)。
文字的描述可能有些空洞,接下來讓我們看看如何使用驗證器校驗簡單的HTTP參數:
public static void hello(String name) {
validation.required(name);
...
}
以上這段程序代碼檢查了name參數是否被賦值。如果沒有,相應的錯誤就會被添加到當前的error集合中。如果有多個數據需要驗證,可以重複該操作:
public static void hello(String name, Integer age) {
validation.required(name); //驗證name是否被賦值
validation.required(age); //驗證age是否被賦值
validation.min(age, 0); //驗證age不小於0
...
}
1.2 驗證消息(error對象中的message屬性)#
在驗證結束後我們可以檢查是否有error產生,並將驗證消息打印出來:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
for(Error error : validation.errors()) {
System.out.println(error.message());
}
}
}
假定name和age都沒有賦值,控制檯將會打印以下信息:
Required
Required
這是因爲在$PLAY_HOME/resources/messages文件中定義了默認的消息:
validation.required=Required
Play提供了三種方式自定義驗證消息:
- 在項目的conf/messages文件中自定義消息,覆蓋默認的message消息。
- 直接將自定義的驗證消息作爲參數傳遞給驗證器,覆蓋默認的message消息。
- 定義帶參數的驗證消息,參數通常爲error對象的key值。
本地化的驗證消息
我們可以配置應用程序的conf/messages文件,採用統一的驗證消息匹配error對象的message消息,這是最簡單的覆蓋消息的寫法:
validation.required = Please enter a value
帶參數的驗證消息
%s佔位符將被替換成error對象的key:
validation.required=%s is required
結合以上例子,輸出結果爲:
name is required
age is required
框架默認將被驗證的參數的名字作爲error對象的key值進行傳遞,但也有其他方法修改error對象的key值。比如,針對上例中hello方法(Action)的name參數進行本地化處理:
name = Customer name
結果就會變成:
Customer name is required
age is required
也可以使用error.message(String key)方法直接覆蓋error對象的key值:
Error error = validation.required(name).error;
if(error != null) {
System.out.println(error.message("Customer name"));
}
Play內置了很多驗證方法,也提供了不同的消息參數。比如match驗證,在消息表達式中定義了字符串類型的參數,與前面介紹的%s的佔位符不同,其規定的參數索引爲2:
validation.match=Must match %2$s
類似的,range驗證定義了兩個整數型參數,以2和3作爲索引:
validation.range=Not in the range %2$d through %3$d
讀者可以在$PLAY_HOME/resources/messages文件中查看其他的驗證以及所含的參數。
讀者可能會有疑問,爲什麼這些參數的索引都是從2開始的。因爲match,range,minSize等驗證都是需要比較的,框架規定將索引爲1的參數設置爲比較源。
自定義本地化驗證消息
Play內置驗證器的message消息,是在$PLAY_HOME/resources/messages文件中定義的。我們也可以在項目中定義自己的驗證消息:
validation.required.em = You must enter the %s!
在Action方法中,就可以採用自己定義的驗證消息機制,手動進行驗證:
validation.required(manualKey).message("validation.required.em");
我們也可以把自己定義的驗證消息機制作爲註解中message的參數使用:
public static void hello(@Required(message="validation.required.em") String name) {
...
}
同理,JavaBean的屬性中也可以使用這種驗證技術(註解在JavaBean的屬性前):
public class Person extends Model {
@Required(message = "validation.required.em")
public String name;
...
}
public static void hello(@Valid Person person) {
...
}
更靈活的自定義方式
Play還提供了一種非常靈活的方式實現驗證消息的自定義,直接在代碼中爲驗證器標註message消息:
validation.required(manualKey).message("Give us a name!");
在註解中的使用方法:
public static void save(@Required(message = "Give us a name!") String name) {
...
}
在JavaBean中的使用方法:
public class Person extends Model {
@Required(message = "Give us a name!")
public String name;
...
}
public static void save(@Valid Person person) {
...
}
1.3 模板中顯示錯誤信息#
如果需要將驗證的錯誤信息在視圖模板中顯示,Play提供的#{ifErrors}標籤和error對象可以輕鬆完成這些工作。在hello Action中進行數據驗證,並使用默認的hello.html模板顯示:
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
render(name, age);
}
編輯view/Application/hello.html模板內容,使用#{ifErrors}標籤顯示驗證結果信息:
#{ifErrors}
<h1>Oops...</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{else}
Hello ${name}, you are ${age}.
#{/else}
顯然,以上的代碼並不能滿足真實應用的需求。因爲在實際項目中,如果驗證失敗了就應該顯示原來的表單,並提示用戶請再次輸入。針對這個需求,我們需要定義兩個Action:一個用於顯示錶單,另一個用於處理POST,數據的驗證將發生在後一個操作。當驗證有錯誤發生時,保存錯誤信息並重定向到第一個Action。Play提供的validation.keep()方法可以在下一個請求中保持錯誤信息的集合。以下是真實項目示例:
public class Application extends Controller {
public static void index() {
render();
}
public static void hello(String name, Integer age) {
validation.required(name);
validation.required(age);
validation.min(age, 0);
if(validation.hasErrors()) {
params.flash(); // 將HTTP參數保存在Flash作用域中
validation.keep(); // 在下一個請求中保持錯誤信息的集合
index();
}
render(name, age);
}
}
修改view/Application/index.html模板:
#{ifErrors}
<h1>Oops…</h1>
#{errors}
<li>${error}</li>
#{/errors}
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
爲了提供更好的用戶體驗,我們可以將錯誤信息顯示在未通過驗證的字段旁:
#{ifErrors}
<h1>Oops…</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="name" value="${flash.name}" />
<span class="error">#{error 'name' /}</span>
</div>
<div>
Age: <input type="text" name="age" value="${flash.age}" />
<span class="error">#{error 'age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
1.4 註解方式#
註解封裝於play.data.validation包中,它採用與每個驗證對象一一對應的方式,實現有選擇性的,更加直觀的數據約束。在使用數據驗證的時候,只需要對控制器中的方法參數添加註解即可:
public static void hello(@Required String name, @Required @Min(0) Integer age) {
if(validation.hasErrors()) {
params.flash(); // 將HTTP參數保存在Flash作用域中
validation.keep(); // 在下一個請求中保持錯誤信息的集合
index();
}
render(name, age);
}
1.5 模型對象的驗證#
在Play中通常採用註解的形式爲模型對象添加數據驗證。重寫前面所介紹的例子,爲User實體的name屬性添加@Required驗證註解,age屬性添加@Required和@Min(0)驗證註解:
package models;
public class User {
@Required
public String name;
@Required
@Min(0)
public Integer age;
}
接着修改hello Action方法,使用@Valid註解對模型對象進行數據驗證:
public static void hello(@Valid User user) {
if(validation.hasErrors()) {
params.flash(); // 將HTTP參數保存在flash作用域中
validation.keep(); // 在下一個request中保持錯誤信息的集合
index();
}
render(user);
}
最後修改index.html表單,如果驗證有錯,在頁面上顯示提示信息:
#{ifErrors}
<h1>Oops...</h1>
#{/ifErrors}
#{form @Application.hello()}
<div>
Name: <input type="text" name="user.name" value="${flash['user.name']}" />
<span class="error">#{error 'user.name' /}</span>
</div>
<div>
Age: <input type="text" name="user.age" value="${flash['user.age']}" />
<span class="error">#{error 'user.age' /}</span>
</div>
<div>
<input type="submit" value="Say hello" />
</div>
#{/form}
(表1 內置驗證)
名稱 | 驗證內容 | 名稱 | 驗證內容 |
---|---|---|---|
驗證E-mail地址是否合法 | equals | 驗證兩個值是否相等 | |
future | 驗證是否爲相對未來的時間 | isTrue | 驗證String或者Boolean類型變量是否爲true |
max | 驗證數值大小是否大於給定的值 | maxSize | 驗證字符串長度是否大於給定的值 |
min | 驗證數值大小是否小於給定的值 | minSize | 驗證字符串長度是否小於給定的值 |
match | 驗證是否匹配給定的正則表達式 | past | 與future相對,驗證是否爲相對過去的時間 |
range | 驗證是否在給定的兩個數值範圍內 | required | 驗證是否爲空 |
url | 驗證是否爲合法的URL | phone | 驗證是否爲合法的電話號碼 |
ipv4Address | 驗證是否爲符合ipv4規則的IP地址 | ipv6Address |
驗證是否爲符合ipv6規則的IP地址 |
其中,required、min以及range驗證器在前幾節介紹的示例中已經有所涉及。本小節將着重介紹其中的email、match與phone驗證器。除此之外的幾類驗證器都非常簡單實用,就不再做過多的敘述了。
使用email驗證器可以對給定的E-mail地址是否正確進行驗證,其message key爲validation.email。下面通過示例演示如何使用email驗證器對給定的E-mail地址進行檢測。
validation.email(address);
對變量address進行email驗證,如果address不是有效的E-mail地址,驗證器會將錯誤以play.data.validation.Error的形式保存。email驗證同樣也支持註解用法,讀者在定義model時可以爲模型的屬性添加@Email註解:
@Email String address
match
match驗證器是相對比較特別的驗證器,該驗證器可以使用正則表達式(用於描述或者匹配一系列符合某個句法規則的字符串的單個字符串)作爲校驗規則。在使用match驗證器時,需要制定充當驗證規則的正則表達式字符串作爲參數,注意空字符串將會被認爲是合法的,其message key爲validation.match。下面通過示例演示如何使用match驗證器對給定的字符串進行檢測。
validation.match(abbreviation, "[A-Z]{3}"); // TLA
對變量abbreviation進行match驗證,如果abbreviation不能匹配正則表達式,驗證器會將錯誤以play.data.validation.Error的形式保存。match驗證同樣也支持註解用法,讀者在定義model時可以爲模型的屬性添加@match註解:
@Match("[A-Z]{3}") String abbreviation
phone
phone驗證器可以對電話號碼進行校驗,驗證是否合法。在使用phone驗證器時需要注意,空字符串也將被認爲是合法的,其message key爲 validation.phone。下面通過示例演示如何使用phone驗證器對給定的電話號碼進行檢測。
validation.phone(value);
對變量value進行phone驗證,如果value不是合法的電話號碼,驗證器會將錯誤以play.data.validation.Error的形式保存。phone驗證同樣也支持註解用法,讀者在定義model時可以爲模型的屬性添加@phone註解:
@Phone String phone
- +是可選的,代表國家碼。
- CCC是可選的,代表前3位國家代碼,需要注意的是其後必須要有一個分隔符。
- (SSSSSS)是可選的,代表6位地區碼。
- 9999999999是必須,表示電話號碼,最高爲20位(已覆蓋了所知的情況和未來可能的號碼)。
- x是可選的,表示擴展,也可以寫成“ext”或“extension”。
- EEEE是可選的,代表擴展碼,最多4位。
- 分隔符可以使用空格、‘-’、‘.’或是‘/’其中的一個,並且可以在號碼的任意地方使用。
下面是不同國家電話號碼的樣例,提供給讀者們參考:
- 中國:+86 (10)69445464
- 美國:(305) 613 09 58 ext 101
- 法國:+33 1 47 37 62 24 x3
- 德國:+49-4312 / 777 777
- 英國:(020) 1234 1234
1.7 自定義驗證#
自定義的驗證器可以通過@CheckWith註解進行綁定:
public class User {
@Required
@CheckWith(MyPasswordCheck.class)
public String password;
static class MyPasswordCheck extends Check {
public boolean isSatisfied(Object user, Object password) {
return notMatchPreviousPasswords(password);
}
}
}
自定義驗證器時不要忘記加載play.data.validation包。