#Java# 文章列表 Java 安装 Java:第一个程序 Hello World Java:建议使用 UTF-8 编写 Java 代码 Java:package 包命名规范 使用 Intellij IDEA 创建 Java 项目 Java 布尔类型 Java 处理日期和时间 Java 正则表达式 Java finalize 方法 Java:空值 null Java 如何触发垃圾回收 Java ThreadLocal Java InheritableThreadLocal Java Integer之间的比较 Java 动态代理 Java 匿名类 Java 枚举 Java 如何静态导入 import static println Java 引用级别:强引用、软引用、弱引用、幽灵引用 Java try finally return 解惑 Java WeakHashMap Java ReferenceQueue 怎么写 Java 示例代码? Java 匿名类双大括号初始化 什么是 Java Bean Java 多行字符串 Java 快速生成 List Java 快速生成 Map Java 将异常堆栈转换为 String JDK SPI 的使用和源码分析 Java Map 中的 key 和 value 能否为 null ? Java List 和 数组的互相转换 Java 获取环境变量 Java 获取和设置系统属性 Java:如何获取当前进程的 PID ? Java 字符串左侧 右侧补充空格或者其他字符 Java 线程 Java:如何获取文本文件内容 Java:读取资源文件内容 Java:使用 JavaFx 构建 GUI Java:Class 类 Java:使用 instanceof 判断对象类型 一个自定义的 Java 工具类 Java:获取当前函数所属类的类名 Java:获取当前执行的函数名 Java:使用 String 的 split 函数拆分字符串 Java:获取字符的 Unicode 编号(代码点) Java:获取当前工作目录 Java:使用 Class 对象的 isArray 方法判断对象是否为数组 使用 Java 生成 CSV 文件 Java Mockito 测试框架快速入门 JUnit 入门 JUnit 单测隔离 Java JOOR 反射库 Java alibaba transmittable-thread-local 库:让 ThreadLocal 跨线程传播 Java 日志组件 slf4j 的使用和源码分析 Java Lombok 库:为你减少样板代码 Java:使用 cglib 实现动态代理 Java Hibernate validator 校验框架 Java 使用 Hessian2 序列化和反序列化 H2 数据库快速入门 Java:使用 Gson 库处理 JSON 数据 Java 集成 groovy 构建规则引擎 Java 13:安装 Java 13 新特性:文本块(多行字符串) 卸载 MacOS 上安装的 Java Java:执行 sql 文件 Java JDK 有哪些发行版 ? java拾遗:String和数组 java拾遗:由反转数组想到System.out的实现机制 java拾遗:如何读取properties文件内容 Java并发概念汇总 java拾遗:System.out.println()是什么? java拾遗:通过示例理解位运算 使用“庖丁解牛”进行中文分词 DBUtils简明教程 试用velocity模板引擎 Java:将字符串哈希为数字 kafka SnappyError no native library is found 问题

Java Hibernate validator 校验框架


#Java#


官方网址:http://hibernate.org/validator/

基础示例

若使用 gradle 构建 Java 项目,请在 build.gradle 中增加以下依赖:

compile group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.0.11.Final'
compile group: 'org.glassfish', name: 'javax.el', version: '3.0.1-b09'
compile group: 'org.projectlombok', name: 'lombok', version: '1.18.0'  // 不是必须,用来减少 Java Bean 的模板代码
compile group: 'junit', name: 'junit', version: '4.12'  // 正常应该用 testCompile,这里用 compile 编写示例更方便

示例1

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Data
public class Person {

    @NotNull
    private String name;

    @Min(1)
    private int age;
}

如果name是null,hibernate validator 校验器会识别到,并给出错误信息。 如果age小于1,hibernate validator 校验器会识别到,并给出错误信息。

测试:

import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class PersonTest {

    @Test
    public void test01() {
        // 生成校验器
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator(); 

        Person person = new Person();

        // 校验 person
        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        // 输出校验结果
        System.out.println("错误数量: " + constraintViolations.size());
        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

    @Test
    public void test02() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Person person = new Person();
        person.setName("letian");
        person.setAge(18);

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

}

执行 test01,输出:

错误数量: 2
最小不能小于1
不能为null

执行 test02,输出:

错误数量: 0

示例2:自定义错误消息

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Data
public class Person {

    @NotNull(message = "name不能为null")
    private String name;

    @Min(value = 1, message = "age不能小于1")
    private int age;
}
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class PersonTest {

    @Test
    public void test01() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Person person = new Person();
        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());
        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

}

执行结果如下:

错误数量: 2
name不能为null
age不能小于1

示例3:不要使用原始类型,使用包装类型

原始类型的默认值(比如int的0)可能有业务含义,而包装类型的默认值(例如Integer的null)可以避免这个问题。

我们将上面示例中的 int 改成 Integer:

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class Person {

    @NotBlank(message = "name不能为空")
    private String name;

    @Min(value = 1, message = "age不能小于1")
    @NotNull(message = "age不能为null")
    private Integer age;
}
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class PersonTest {

    @Test
    public void test01() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Person person = new Person();
        person.setName("  ");
        person.setAge(0);

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

}

执行结果:

错误数量: 2
age不能小于1
name不能为空

示例4:级联校验

如果 Java Bean 的一个属性是另外一个 Java Bean,如果要同时校验该 Java Bean,就是级联校验。

import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

@Data
public class Address implements Serializable {

    @NotBlank(message = "city 不能为空")
    private String city;

}
import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Data
public class Person implements Serializable {

    @NotBlank(message = "name 不能为空")
    private String name;

    @Valid // 这个注解很重要
    @NotNull(message = "address 不能为空")
    private Address address;
}
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class PersonTest {

    @Test
    public void test01() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Person person = new Person();
        person.setName("letian");

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

    @Test
    public void test02() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Person person = new Person();
        person.setName("letian");
        person.setAddress(new Address());

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

}

不管有没有@Vaild注解 Person 的 address 属性,test01 的结果都是:

错误数量: 1
address 不能为空

如果没有@Valid注解 Person 的 address 属性,test02 的结果是:

错误数量: 0

如果有@Valid注解 Person 的 address 属性,test02 的结果是:

错误数量: 1
city 不能为空

示例5:校验分组

@NotNull@NotBlank 的默认分组是是javax.validation.groups.Default接口。

validator.validate( person ); 校验 person 对象中属于 Default 分组的@NotNull@NotBlank等注解的属性。

我们也可以自定义分组:

  1. 定义接口,代表分组
  2. @NotNull等注解加上分组
  3. 校验时指定使用哪些分组。
public interface NameCheckGroup {
}
import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

@Data
public class Person {

    @NotBlank(message = "name不能为空", groups = { NameCheckGroup.class })
    private String name;

    @Min(value = 1, message = "age不能小于1")
    private int age;
}
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import java.util.Set;

public class PersonTest {

    // 校验默认分组的数据
    @Test
    public void test01() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Person person = new Person();
        person.setName("  ");
        person.setAge(0);

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

    // 校验 NameCheckGroup 分组的数据
    @Test
    public void test02() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Person person = new Person();
        person.setName("  ");
        person.setAge(0);

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person, NameCheckGroup.class);

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

    // 校验 NameCheckGroup 分组和默认分组的数据
    @Test
    public void test03() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Person person = new Person();
        person.setName("  ");
        person.setAge(0);

        Set<ConstraintViolation<Person>> constraintViolations = validator.validate( person, NameCheckGroup.class, Default.class);

        System.out.println("错误数量: " + constraintViolations.size());

        for (ConstraintViolation<Person> v: constraintViolations) {
            System.out.println(v.getMessage());
        }
    }

}

这三个测试中测试的 person 对象内容都是相同的,但校验时使用的分组不同。

test01 执行结果:

错误数量: 1
age不能小于1

test02 执行结果:

错误数量: 1
name不能为空

test03 执行结果:

错误数量: 2
name不能为空
age不能小于1

注解说明

@NotNull

注解的元素不能为 null。

@NotBlank

注解的属性是 CharSequence 类型(String 继承自 CharSequence,也可以),必须包含非空白字符。 这意味着,也不是 null 。

@NotEmpty

注解的元素不能是 null,也不能是空。

元素类型是 CharSequence 、Array,则长度不能是 0。 元素类型是 Collection、Map,则size不能是0。

@Min

注解的元素的值必须大于等于指定的最小值。

支持的类型: BigDecimal、BigInteger、byte、Byte、short、Short、long、Long、int、Integer。

@Max

注解的元素的值必须小于等于指定的最大值。

支持的类型: BigDecimal、BigInteger、byte、Byte、short、Short、long、Long、int、Integer。

@Size

注解的元素大小必须在指定的范围内。

支持的类型:

  • CharSequence : 使用 length 代表大小
  • Array : 使用 length 代表大小
  • Collection: 使用 size 代表大小
  • Map: 使用 size 代表大小

更多注解

还有很多注解,比如 @Negative、@Email、 @DecimalMax 等,这些都在一个 package 中定义,翻看代码即可。

解决 javax.validation.ValidationException: HV000183: Unable to load 'javax.el.ExpressionFactory'

在上面的示例中,校验器是用下面的方式初始化的:

// 生成校验器
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator(); 

若无 org.glassfish 依赖,那么会报错 javax.validation.ValidationException: HV000183: Unable to load 'javax.el.ExpressionFactory'。

如果要在无 org.glassfish 依赖的情况下不报错,可以用下面的方式生成校验器:

import javax.validation.Validation;
import javax.validation.Validator;
import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;

private static final Validator validator =
  Validation.byDefaultProvider()
    .configure()
    .messageInterpolator(new ParameterMessageInterpolator())
    .buildValidatorFactory()
    .getValidator();

具体讨论可参考 https://stackoverflow.com/questions/24386771/javax-validation-validationexception-hv000183-unable-to-load-javax-el-express

一个通用的工具类

import org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

public class ValidatorUtil {

     private final static Validator validator = Validation.byDefaultProvider()
             .configure()
             .messageInterpolator(new ParameterMessageInterpolator())
             .buildValidatorFactory()
             .getValidator();

    /**
     * 对象内部的字段校验
     *
     * @param obj
     * @param <T>
     */
     public static <T> void validate(T obj) {
         if (obj == null) {
             throw new RuntimeException("不能为null");
         }
         Set<ConstraintViolation<T>> constraintViolations = validator.validate(obj);
         for(ConstraintViolation<T> item : constraintViolations) {
             throw new RuntimeException(item.getMessage());
         }
     }

}


( 本文完 )