官方网址: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
等注解的属性。
我们也可以自定义分组:
- 定义接口,代表分组
- 给
@NotNull
等注解加上分组 - 校验时指定使用哪些分组。
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();
一个通用的工具类
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());
}
}
}