JSR 380 Java Bean Validation
JSR 380 Java Bean Validation
-
该技术规范规定了用于对 Java Bean 中的属性进行验证的 API,从而避免了传参时的大量 if-else 校验。
-
SpringBoot 中提供了相应的 API,可以通过注解的方式来对属性进行约束,在传参时自动校验,并能在参数校验不通过时返回错误信息
引入依赖
要在 SpringBoot 中引入该 API 包,添加如下 Maven 依赖:
<dependency> |
该依赖中提供了 Hibernate-Validator 的传递依赖,因此不用再引入 Hibernate-Validator。
常用注解
| 注解 | 说明 |
|---|---|
@NotNull |
该属性不能为空 |
@AssertTrue |
该属性需要为 true |
@Size |
用于字符串、集合或数组,限定该属性的大小(字符串长度、集合数组的元素个数),通过 min 和 max 两个属性来指定区间 |
@Min |
该属性值不能小于该注解的 value 属性的值 |
@Max |
该属性值不能大于该注解的 value 属性的值 |
@Email |
该属性值需要为合法的邮箱地址 |
@NotEmpty |
用于字符串、集合和数组,限定该属性不能为 null 或空 |
@NotBlank |
用于字符串,限定该字符串不能为 null,空或全是空格 |
@Positive |
用于数字量,限定该属性严格大于 0 |
@PositiveOrZero |
用于数字量,限定该属性严格大于等于 0 |
@Negative |
用于数字量,限定该属性严格小于 0 |
@NegativeOrZero |
用于数字量,限定该属性严格小于等于 0 |
@Past |
用于日期类型的属性,限定该属性必须为过去时间 |
@PastOrPresent |
用于日期类型的属性,限定该属性必须为过去或当前时间 |
@Future |
用于日期类型的属性,限定该属性必须为将来时间 |
@FutureOrPresent |
用于日期类型的属性,限定该属性必须为将来或当前时间 |
这些注解还可以用于集合的元素,如下:
List< String> preferences;
在传递参数时进行参数校验
给需要校验的属性设置好约束后,我们就可以在 Controller 层的方法参数列表中校验前端的传参,具体做法如下:
|
在 SpringBoot 的 RestController 中,设定哪些参数校验十分简单,只需要在要校验的参数前加上 @Valid 注解即可,此注解会自动启动引导 JSR 380 的实现类—— Hibernate Validator,并验证该参数。
如果参数未通过校验,SpringBoot 会抛出 MethodArgumentNotValidException 异常
异常处理
在验证到参数非法后,SpringBoot 会抛出 MethodArgumentNotValidException 异常,我们需要捕获这个异常并返回错误信息,此时,我们可以新建一个方法专门用于处理参数非法异常,并为这个方法加上 @ExceptionHandler 注解,如下所示:
|
这样,在程序抛出 MethodArgumentNotValidException 异常时,被 @ExceptionHandler(MethodArgumentNotValidException.class) 标注的方法就会自动捕获该异常,并执行方法体中的语句,并将该方法的返回值返回给前端。
参数交叉验证
所谓参数交叉验证,就是指传递的多个参数之间有约束联系,而我们需要验证这些约束联系是否被满足,例如:
- 传递两个数值参数
a和b,其中a必须比b大 - 传递两个日期参数
begin和end,其中begin的时间必须比end的要早
之前介绍的注解,都只适用于单个参数的值的校验,无法完成参数之间交叉验证的需求,于是,我们引入了自定义注解,来实现这一需求。
假如我们需要验证传递来的两个日期参数,一个比另一个早,且两个都晚于当前时间,则我们按如下步骤来实现:
-
在需要交叉验证参数的方法上加上我们的自定义注解,这里为
@ConsistentDateParameters
public void createReservation(LocalDate begin,
LocalDate end, Customer customer) {
// ...
} -
定义我们定义的自定义注解,如下:
public ConsistentDateParameters {
String message() default
"End date must be after begin date and both must be in the future";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}此注解中,有三个必填属性:
属性 说明 message返回的错误信息 groups允许指定约束的分组 payloadBean Validation API 的客户端可以使用它来将自定义有效负载对象分配给约束 -
定义自定义注解参数验证类
public class ConsistentDateParameterValidator
implements ConstraintValidator<ConsistentDateParameters, Object[]> {
public boolean isValid(
Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null || value[1] == null) {
return true;
}
if (!(value[0] instanceof LocalDate)
|| !(value[1] instanceof LocalDate)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
return ((LocalDate) value[0]).isAfter(LocalDate.now())
&& ((LocalDate) value[0]).isBefore((LocalDate) value[1]);
}
}注意:
-
类名需要与自定义注解元注解
@Constraint的validatedBy的属性值一致 -
ConstraintValidator接口有两个泛型需要指定:-
自定义的注解
-
指定该验证类可以验证的数据类型(不确定可以设为 Object[])
-
-
方法
isvalid有两个参数:- 传递的需要验证的参数
- 默认参数
ConstraintValidatorContext context
isValid方法中就包含了实际的验证逻辑,返回值为ture表示验证通过,返回值为false表示验证未通过。注意!
@SupportedValidationTarget(ValidationTarget.PARAMETERS)对ConsistentDateParameterValidator类的批注是必需的。这样做的原因是@ConsistentDateParameter是在方法级别设置的,但约束应应用于方法参数(而不是方法的返回值)提示:Bean 验证规范建议将 null 值视为有效值。如果 null 不是有效值,则应添加 @NotNull 注释。
-
方法返回值校验
有时我们需要检验方法的返回值是否合乎要求,这时我们需要进行方法返回值的校验,方法返回值校验有两种途径:
- 使用自带注解进行校验
- 使用自定义注解进行校验
使用自带注解进行校验
这种方式很简单,只需要在方法之上加上对返回值的约束注解即可,如:
public class ReservationManagement { |
加上注解后,该方法的返回值会:
- 返回的列表不能为 null,且列表中至少包含一个元素
- 返回的列表中的元素不能为 null
使用自定义注解进行校验
与参数交叉验证相似,我们可以按如下步骤来完成对方法返回值的校验:
-
在需要校验返回值的方法上加上我们的自定义注解,如
@ValidReservationpublic class ReservationManagement {
public Reservation getReservationsById(int id) {
return null;
}
} -
定义自定义注解:
public ValidReservation {
String message() default "End date must be after begin date "
+ "and both must be in the future, room number must be bigger than 0";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
} -
定义验证类:
public class ValidReservationValidator
implements ConstraintValidator<ValidReservation, Reservation> {
public boolean isValid(
Reservation reservation, ConstraintValidatorContext context) {
if (reservation == null) {
return true;
}
if (!(reservation instanceof Reservation)) {
throw new IllegalArgumentException("Illegal method signature, "
+ "expected parameter of type Reservation.");
}
if (reservation.getBegin() == null
|| reservation.getEnd() == null
|| reservation.getCustomer() == null) {
return false;
}
return (reservation.getBegin().isAfter(LocalDate.now())
&& reservation.getBegin().isBefore(reservation.getEnd())
&& reservation.getRoom() > 0);
}
}
此注解还可以应用到类的构造器上,来校验实例化的对象是否合乎要求
级联验证
前面我们介绍的验证,都是验证一个简单对象的属性,如:
public class Customer { |
如果我们有一个复杂对象,这个复杂对象的某些属性是其他的自定义对象,当我们要需要验证它时,就需要用到级联验证,如:
public class Reservation { |
我们在需要级联验证的属性对象上也加上了 @Valid 注解,这样在验证 Reservation 对象属性时,不仅会验证 room 属性,还会验证该对象的属性 customer 对象下的全部属性是否合法
public void createNewCustomer( Reservation reservation) { |
该方法同样适合验证方法的返回值是复杂对象时,返回的值是否合法
使用 Spring 进行自动验证
前面我们已经自定义了注解并实现了自定义的校验类,现在,我们可以进行校验了,其中一条实现校验的途径就是使用 Spring 进行自动验证
Spring Validation 提供了与 Hibernate Validator 的集成。
注意:Spring Validation 基于 AOP,并使用 Spring AOP 作为默认实现。因此,验证仅适用于方法,而不适用于构造函数。
如果我们想要使用 Spring 进行自动验证,我们需要完成以下两步:
-
在需要进行参数验证的方法所属的类上加上
@Validated注解
public class ReservationManagement {
public void createReservation( LocalDate begin,
int duration, Customer customer){
// ...
}
public List< Customer> getAllCustomers(){
return null;
}
} -
配置提供一个
MethodValidationPostProcessorbean
public class MethodValidationConfig {
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}SpringBoot 应用不需要这一步,IOC 容器会自动为我们注册一个
MethodValidationPostProcessorbean.
帮助文档:Hibernate Validator 8.0.1.Final - Jakarta Bean 验证参考实现:参考指南 (jboss.org)