数据校验是一个比较常见的工作,在日常的开发中贯穿于代码的各个层次,虽然简单,但是却是一个繁琐的事。一般来说我们的第一反应肯定是使用if判断参数,然后校验不通过打日志抛异常,可能还会根据业务来归纳各种各样的Util或者Rule。虽然这种做法可以达到效果,但是代码散乱,可读性可维护性差。所以有什么办法可以解决掉这些烦人的if操作呢?
参数校验作为一个痛点,Java业界推出了Bean Validation。
Bean Validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本。
它定义了一套元数据模型和API对JavaBean实现校验,默认是以注解作为元数据,可以通过XML重写或者拓展元数据,通常来说注解的方式可以实现比较简单逻辑的校验,而复杂校验就需要通过XML来描述。可以说Bean Validation是JavaBean的一个拓展,也就是说它布局于哪一层的代码,不局限于Web应用还是端应用。

Bean Validation 2.0 关注点

1.使用Bean Validation的最低Java版本为Java 8
2.支持容器的校验,通过TYPE_USE类型的注解实现对容器内容的约束:List<@Email String>
3.支持日期/时间的校验,@Past和@Future
4.拓展元素数据:@Email,@NotEmpty,@NotBlank,@Positive, @PositiveOrZero,@Negative,@NegativeOrZero,@PastOrPresent和@FutureOrPresent

Bean Validation的实现

Bean Validation在2.0之前有两个官方认可的实现:Hibernate Validator和Apache BVal,但如果你想用2.0版本的话,基本上只有Hibernate Validator,而这里我使用的是Hibernate Validator,其他实现不做展开。

使用

安装依赖:

根据jsr380规范,validation-api依赖库包含标准validation api:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.0.Final</version>
</dependency>

验证API参考实现

Hibernate Validator是验证规范的参考实现,我们需要增加下面依赖:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.2.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>6.0.2.Final</version>
</dependency>

需要注意的是,hibernate-validator与Hibernate持久化功能完全独立,这里并没有引入持久化方面的库。
表达式依赖
jsr380提供了变量插入功能,允许表达式在错误信息中混合使用。为了解析表达式,我们必须增加下面依赖:表达式语言API及其实现,GlassFish提供了参考实现:

<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.1-b09</version>
</dependency>

如果没有增加表达式库,会报错,信息如下:

HV000183: Unable to load ‘javax.el.ExpressionFactory’. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead

注意如果你是Tomcat7的话,在tomcat的lib下也有一个el-api.jar,该版本的jar包版本较低,直接运行会产生冲突报错,它会优先使用tomcat里的jar包而不是项目引入的,所以需要用项目引入的jar包来替换保持版本一致,或者升级tomcat版本到8即可解决问题。如果是通过RPM打包的方式可以写一个后置脚本来替换jar包,可以省去手动替换包。

小Demo

User类:

@Data
public class User {
    private Address address;
    @NotBlank
    private String name;
    private String gender;
    @Positive
    private int age;
    private List<@Email String> emails;
}

Main函数:

User user = new User();
        user.setAddress(new Address(new Country("China")));
        user.setGender("man");
        user.setAge(-1);
        user.setEmails(Arrays.asList("sevenlin@gmail.com", "sevenlin.com"));

        Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory()
                .getValidator()
                .validate(user);
        List<String> message = result.stream()
                .map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .collect(Collectors.toList());
        message.forEach(System.out::println);

输出结果:

name 不能为空: null
emails[1].<list element> 不是一个合法的电子邮件地址: sevenlin.com
age must be greater than 0: -1

参考:
https://zhuanlan.zhihu.com/p/42818634
https://blog.csdn.net/neweastsun/article/details/79602322
http://imushan.com/2018/01/18/java/language/%E6%8E%8C%E6%8F%A1Java-Bean-Validation/