Java数据校验(Bean Validation / JSR303)

JSR303是JAVA EE6中的子规范。用于对Java Bean的字段值进行校验,确保输入进来的数据在语义上是正确的,使验证逻辑从业务代码中脱离出来。JSR303是运行时数据验证框架,验证之后验证的错误信息会马上返回。有两个版本JSR303(BeanValidation1.0)和JSR349(BeanValidation1.1)。

javax.validation:validation-api:jar:1.1.0.Final

实现版本:

  • org.hibernate:hibernate-validator:5.2.4.Final
  • org.apache.bval:bval-jsr303:0.5
  • jersery

JSR-303 原生支持的限制

约束类型 说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Past 限制必须是一个过去的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间

定义自己的约束类型

定义注解

message、groups和payload三个属性是必须定义的。

1
2
3
4
5
6
7
8
@Target({ElementType.FIELD, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MoneyValidator.class) //用于限制的注解,验证类为MoneyValidator
public @interface Money {
String message() default "不是金额形式";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

定义验证类

ConstraintValidator使用了泛型,有两个类型参数。第一个类型是对应的initialize方法的参数类型(约束注解类型),第二个类型是对应的isValid方法的第一个参数类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MoneyValidator implements ConstraintValidator<Money,Double> {

private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金额的正则表达式
private Pattern moneyPattern = Pattern.compile(moneyReg);

/**
* Initializes the validator in preparation for
* {@link #isValid(Object, ConstraintValidatorContext)} calls.
* The constraint annotation for a given constraint declaration
* is passed.
* <p/>
* This method is guaranteed to be called before any use of this instance for
* validation.
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
@Override
public void initialize(Money constraintAnnotation) {

}

/**
* Implements the validation logic.
* The state of {@code value} must not be altered.
* <p/>
* This method can be accessed concurrently, thread-safety must be ensured
* by the implementation.
*
* @param value object to validate
* @param context context in which the constraint is evaluated
* @return {@code false} if {@code value} does not pass the constraint
*/
@Override
public boolean isValid(Double value, ConstraintValidatorContext context) {
if (value == null)
return true;
return moneyPattern.matcher(value.toString()).matches();
}
}

Hibernate Validator

Hibernate Validator附加的constraint(hibernate-validator和validation-api)

@Email 被注释的元素必须是电子邮箱地址
@Length 字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

验证对象类型

字段约束

当约束被定义在字段上的时候, 这个字段的值是通过字段访问策略来获取并验证的. 也就是说Bean Validation的实现者会直接访问这个实例变量而不会调用属性的访问器(getter) 即使这个方法存在。静态字段或者属性是不会被校验的

属性约束

必须遵守JavaBeans规范,且定义在getter上,不能定义在setter上

类约束

约束继承

验证子类时所有基类中的约束也都会被使用

对象

@Valid注解类中的对象属性

Spring MVC Validator接口

定义Validator。 Supports方法用于判断当前的Validator实现类是否支持校验当前需要校验的实体类,只有当supports方法的返回结果为true的时候,该Validator接口实现类的validate方法才会被调用来对当前需要校验的实体类进行校验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class UserValidator implements Validator {
/**
* Can this {@link Validator} {@link #validate(Object, Errors) validate}
* instances of the supplied {@code clazz}?
* <p>This method is <i>typically</i> implemented like so:
* <pre class="code">return Foo.class.isAssignableFrom(clazz);</pre>
* (Where {@code Foo} is the class (or superclass) of the actual
* object instance that is to be {@link #validate(Object, Errors) validated}.)
*
* @param clazz the {@link Class} that this {@link Validator} is
* being asked if it can {@link #validate(Object, Errors) validate}
* @return {@code true} if this {@link Validator} can indeed
* {@link #validate(Object, Errors) validate} instances of the
* supplied {@code clazz}
*/
@Override
public boolean supports(Class<?> clazz) {
return User.class.equals(clazz);
}

/**
* Validate the supplied {@code target} object, which must be
* of a {@link Class} for which the {@link #supports(Class)} method
* typically has (or would) return {@code true}.
* <p>The supplied {@link Errors errors} instance can be used to report
* any resulting validation errors.
*
* @param target the object that is to be validated (can be {@code null})
* @param errors contextual state about the validation process (never {@code null})
* @see ValidationUtils
*/
@Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty.");
User user = (User) obj;
if (null == user.getPassword() || "".equals(user.getPassword()))
errors.rejectValue("password", null, "Password is empty.");
}
}

使用Validator进行验证。在SpringMVC中我们可以使用DataBinder来设定当前Controller需要使用的Validator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
@Slf4j
public class ValidController {

@InitBinder
public void initBinder(DataBinder binder) {
binder.setValidator(new UserValidator());
}

@GetMapping("login")
public String login(@Valid User user, BindingResult result) {
if (result.hasErrors())
return "redirect:user/login";
return "redirect:/";
}

}

必须使用@Valid标注我们需要校验的参数user,否则Spring不会对它进行校验。另外我们的处理器方法必须给定包含Errors的参数,这可以是Errors本身,也可以是它的子类BindingResult,使用了Errors参数就是告诉Spring关于表单对象数据校验的错误将由我们自己来处理,否则Spring会直接抛出异常,而且这个参数是必须紧挨着@Valid参数的,即必须紧挨着需要校验的参数,这就意味着我们有多少个@Valid参数就需要有多少个对应的Errors参数,它们是一一对应的。

如果我们希望一个Validator对所有的Controller都起作用的话,我们可以通过WebBindingInitializer的initBinder方法来设定了。另外,在SpringMVC的配置文件中通过mvc:annotation-driven的validator属性也可以指定全局的Validator。

补充:Spring可以通过@Resource或@AutoWired注解向ConstraintValidator注入对象。

参考资料:
http://blog.sina.com.cn/s/blog_a3d2fd2d0101hyu7.html
http://haohaoxuexi.iteye.com/blog/1812584
http://www.cnblogs.com/pixy/p/5306567.html
http://beanvalidation.org/1.1/spec/
http://docs.jboss.org/hibernate/beanvalidation/spec/1.1/api/