数据验证是应用程序健壮性的体现,在实际项目中也是必不可少的环节。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包。