
温馨提示
请收藏再看。此文篇幅太长,你短时间看不完;此文干货太多,错过太可惜。
示例代码可以私信我获取。
收获
- 讲解详细:能让你掌握使用 hibernate-validator 及类似校验工具的各种使用姿势
- 内容全面:可以当做知识字典来查询
what
注意:hibernate-validator 与 持久层框架 hibernate 没有什么关系,hibernate-validator 是 hibernate 组织下的一个https://github.com/hibernate/hibernate-validator。
hibernate-validator 是 JSR 380(Bean Validation 2.0)、JSR 303(Bean Validation 1.0)规范的实现。
JSR 380 - Bean Validation 2.0 定义了一个实体和方法验证的元数据模型和 API。
JavaEE(改名为:Jakarta EE)中制定了 validation 规范,即:javax.validation-api(现为 jakarta.validation-api,jar 包的名字改变,包里面的包名、类名未变,因此使用方式不变)包,spring-boot-starter-web、spring-boot-starter-webflux 包都已引入此依赖,直接使用即可。
有点类似于 slf4j 与 logback(log4j2)的关系,使用的时候,代码中使用 javax.validate 提供的接口规范功能,加载的时候,根据 SPI 规范加载对应的规范实现类。
它和 hibernate 没什么关系,放心大胆的使用吧。
why
hibernate-validator 官方有如下说明:
以前的校验如下:

使用 hibernate-validator 后,校验逻辑如下:

controller、service、dao 层相同的校验逻辑可以使用同一个数据校验模型。
how
标识注解
@Valid(规范、常用)
标记用于验证级联的属性、方法参数或方法返回类型。
在验证属性、方法参数或方法返回类型时,将验证在对象及其属性上定义的约束。
此行为是递归应用的。
@Validated(spring)
spring 提供的扩展注解,可以方便的用于分组校验
22 个约束注解
下面除了列出的参数,每个约束都有参数 message,groups 和 payload。这是 Bean Validation 规范的要求。
其中,message 是提示消息,groups 可以根据情况来分组。
以下每一个注解都可以在相同元素上定义多个。
@AssertFalse
检查元素是否为 false,支持数据类型:boolean、Boolean
@AssertTrue
检查元素是否为 true,支持数据类型:boolean、Boolean
@DecimalMax(value=, inclusive=)
inclusive:boolean,默认 true,表示是否包含,是否等于value:当 inclusive=false 时,检查带注解的值是否小于指定的最大值。当 inclusive=true 检查该值是否小于或等于指定的最大值。参数值是根据 bigdecimal 字符串表示的最大值。支持数据类型:BigDecimal、BigInteger、CharSequence、(byte、short、int、long 和其封装类)
@DecimalMin(value=, inclusive=)
支持数据类型:BigDecimal、BigInteger、CharSequence、(byte、short、int、long 和其封装类)inclusive:boolean,默认 true,表示是否包含,是否等于value:当 inclusive=false 时,检查带注解的值是否大于指定的最大值。当 inclusive=true 检查该值是否大于或等于指定的最大值。参数值是根据 bigdecimal 字符串表示的最小值。
@Digits(integer=, fraction=)
检查值是否为最多包含 integer 位整数和 fraction 位小数的数字支持的数据类型:BigDecimal, BigInteger, CharSequence, byte, short, int, long 、原生类型的封装类、任何 Number 子类。
检查指定的字符序列是否为有效的电子邮件地址。可选参数 regexp 和 flags 允许指定电子邮件必须匹配的附加正则表达式(包括正则表达式标志)。支持的数据类型:CharSequence
@Max(value=)
检查值是否小于或等于指定的最大值支持的数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@Min(value=)
检查值是否大于或等于指定的最大值支持的数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@NotBlank
检查字符序列是否为空,以及去空格后的长度是否大于 0。与 @NotEmpty 的不同之处在于,此约束只能应用于字符序列,并且忽略尾随空格。支持数据类型:CharSequence
@NotNull
检查值是否不为 null支持数据类型:任何类型
@NotEmpty
检查元素是否为 null 或 空支持数据类型:CharSequence, Collection, Map, arrays
@Size(min=, max=)
检查元素个数是否在 min(含)和 max(含)之间支持数据类型:CharSequence,Collection,Map, arrays
@Negative
检查元素是否严格为负数。零值被认为无效。支持数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@NegativeOrZero
检查元素是否为负或零。支持数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@Positive
检查元素是否严格为正。零值被视为无效。支持数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@PositiveOrZero
检查元素是否为正或零。支持数据类型:BigDecimal, BigInteger, byte, short, int, long, 原生类型的封装类, CharSequence 的任意子类(字符序列表示的数字), Number 的任意子类, javax.money.MonetaryAmount 的任意子类
@Null
检查值是否为 null支持数据类型:任何类型
@Future
检查日期是否在未来支持的数据类型:java.util.Date, java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.MinguoDate, java.time.chrono.ThaiBuddhistDate如果 http://www.joda.org/joda-time/ API 在类路径中,ReadablePartial 和ReadableInstant 的任何实现类
@FutureOrPresent
检查日期是现在或将来支持数据类型:同@Future
@Past
检查日期是否在过去支持数据类型:同@Future
@PastOrPresent
检查日期是否在过去或现在支持数据类型:同@Future
@Pattern(regex=, flags=)
根据给定的 flag 匹配,检查字符串是否与正则表达式 regex 匹配支持数据类型:CharSequence
实现示例
@Size
从上文可知,规范中,@Size 支持的数据类型有:CharSequence,Collection,Map, arrayshibernate-validator 中的实现如下:

针对 CharSequence、Collection、Map 都有一个实现,由于 arrays 有多种可能,提供了多个实现。其中,SizeValidatorForCollection.java 如下:
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraints.Size;
@SuppressWarnings("rawtypes")
// as per the JLS, Collection> is a subtype of Collection, so we need to explicitly reference
// Collection here to support having properties defined as Collection (see HV-1551)
public class SizeValidatorForCollection implements ConstraintValidator {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
private int min;
private int max;
@Override
public void initialize(Size parameters) {
min = parameters.min();
max = parameters.max();
validateParameters();
}
@Override
public boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {
if ( collection == null ) {
return true;
}
int length = collection.size();
return length >= min && length <= max;
}
private void validateParameters() {
if ( min < 0 ) {
throw LOG.getMinCannotBeNegativeException();
}
if ( max < 0 ) {
throw LOG.getMaxCannotBeNegativeException();
}
if ( max < min ) {
throw LOG.getLengthCannotBeNegativeException();
}
}
}
实现逻辑就是按照规范的说明来实现的。
实战
声明 Java Bean 约束
可以用以下方式声明约束:
- 字段级别约束
@NotNull
private String manufacturer;
- 属性级别约束
@NotNull
public String getManufacturer(){
return manufacturer;
}
- 容器级别约束
private Map fuelConsumption = new HashMap<>();
- 类级别约束
- 在这种情况下,验证的对象不是单个属性,而是完整的对象。如果验证依赖于对象的多个属性之间的相关性,则类级约束非常有用。
- 如:汽车中,乘客数量不能大于座椅数量,否则超载
@ValidPassengerCount
public class Car {
private int seatCount;
private List passengers;
//...
}
- 约束继承
- 当一个类继承/实现另一个类时,父类声明的所有约束也会应用在子类继承的对应属性上。
- 如果方法重写,约束注解将会聚合,也就是此方法父类和子类声明的约束都会起作用。
- 级联验证
- Bean Validation API 不仅允许验证单个类实例,也支持级联验证。
- 只需使用 @Valid 修饰对象属性的引用,则对象属性中声明的所有约束也会起作用。
- 如以下示例,当验证 Car 实例时,Person 对象中的 name 字段也会验证。
public class Car {
@NotNull
@Valid
private Person driver;
//...
}
public class Person {
@NotNull
private String name;
//...
}
声明方法约束
参数约束
通过向方法或构造函数的参数添加约束注解来指定方法或构造函数的前置条件,官方示例如下:
public RentalStation(@NotNull String name){}
public void rentCar(@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays){}
返回值约束
通过在方法体上添加约束注解来给方法或构造函数指定后置条件,官方示例如下:
public class RentalStation {
@ValidRentalStation
public RentalStation() {
//...
}
@NotNull
@Size(min = 1)
public List getCustomers() {
//...
return null;
}
}
此示例指定了三个约束:
- 任何新创建的 RentalStation 对象都必须满足 @validRentalStation 约束
- getCustomers() 返回的客户列表不能为空,并且必须至少包含 1 个元素
- getCustomers() 返回的客户列表不能包含空对象
级联约束
类似于 JavaBeans 属性的级联验证,@Valid 注解可用于标记方法参数和返回值的级联验证。
类似于 javabeans 属性的级联验证(参见第 2.1.6 节“对象图”),@valid 注释可用于标记可执行参数和级联验证的返回值。当验证用@valid 注释的参数或返回值时,也会验证在参数或返回值对象上声明的约束。而且,也可用在容器元素中。
public class Garage {
public boolean checkCars(@NotNull List cars) {
//...
return false;
}
}
继承验证
当在继承体系中声明方法约束时,必须了解两个规则:
- 方法调用方要满足前置条件不能在子类型中得到加强
- 方法调用方要保证后置条件不能再子类型中被削弱
这些规则是由子类行为概念所决定的:在使用类型 T 的任何地方,也能在不改变程序行为的情况下使用 T 的子类。
当两个类分别有一个同名且形参列表相同的方法,而另一个类用一个方法重写/实现上述两个类的同名方法时,这两个父类的同名方法上不能有任何参数约束,因为不管怎样都会与上述规则冲突。示例:
public interface Vehicle {
void drive(@Max(75) int speedInMph);
}
public interface Car {
void drive(int speedInMph);
}
public class RacingCar implements Car, Vehicle {
@Override
public void drive(int speedInMph) {
//...
}
}
分组约束
请求组
注意:上述的 22 个约束注解都有 groups 属性。当不指定 groups 时,默认为 Default 分组。
JSR 规范支持手动校验,不直接支持使用注解校验,不过 spring 提供了分组校验注解扩展支持,即:@Validated,参数为 group 类集合
分组继承
在某些场景下,需要定义一个组,它包含其它组的约束,可以用分组继承。如:
public class SuperCar extends Car {
@AssertTrue(
message = "Race car must have a safety belt