SpringBoot 的请求参数校验注解
Validation 和 validator 包提供了一系列校验用注解,帮助我们在 RESTful 服务请求中实现期望的数据校验,其注解的功能包括但不限于入参的存在性判断、非空判断、数值取值范围限定、特定含义数据格式校验、校验失败提示信息等。Maven 依赖在 SpringBoot 2.3 版本之前的项目中,主要需要添加的依赖包括以下两个。其中 spring-boot-starter-web 包含
Validation 和 validator 包提供了一系列校验用注解,帮助我们在 RESTful 服务请求中实现期望的数据校验,其注解的功能包括但不限于入参的存在性判断、非空判断、数值取值范围限定、特定含义数据格式校验、校验失败提示信息等。
Maven 依赖
在 SpringBoot 2.3 版本之前的项目中,主要需要添加的依赖包括以下两个。其中 spring-boot-starter-web
包含了 spring-boot-starter-validation
这个 maven 包,而里面则添加了 javax-validation
和 hibernate-validator
两个包含检验注解的依赖包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
而在 SpringBoot 2.3 之后的版本则需要单独引入 spring-boot-starter-validation
这个 maven 包。
常用注解说明
Maven 包:javax.validation.constraints:validation-api-2.0.1.Final
注解 | 作用数据类型 | 说明 |
---|---|---|
@Null | 任何类型 | 对象必须为空 |
@NotNull | 任何类型 | 对象不为空 |
@NotBlank | 字符串 | 对象不为空,字符串去掉前后空格后长度不为0 |
@NotEmpty | 字符串、集合、数组 | 对象不为空,且字符串长度不为0,集合、数组大小不为0 |
@AssertTrue | 布尔型 | 必须为true;null值有效,Boolean通过校验,boolean不可 |
@AssertFalse | 布尔型 | 必须为false;null 可通过校验 |
@Min(number) | 整型数 | 数值必须大于或等于指定的最小值 |
@Max(number) | 整型数 | 数值必须小于或等于指定的最大值 |
@DecimalMin(decimal) | 浮点型 | 数值必须大于或等于指定的最小值,内部使用BigDecimal定义数值对象;为 null 是校验通过;默认包含边界值 |
@DecimalMax(decimal) | 浮点型 | 数值必须小于或等于指定的最大值,内部使用BigDecimal定义数值对象;为 null 是校验通过;默认包含边界值 |
@Positive | 整型数 | 数值必须为正整数 |
@PositiveOrZero | 整型数 | 数值必须为正整数或0 |
@Negative | 整型数 | 数值必须为负整数 |
@NegativeOrZero | 整型数 | 数值必须为负整数或0 |
@Digits | 数值型或者字符串 | 作为数值其构成必须合法 |
@Digits(integer=, fraction=) | 数值型 | 数值必须符合指定的整数精度和小数精度 |
@Size(min=, max=) | 字符串、集合、数组 | 对象的大小在指定区间内;为 null 是校验通过 |
@Past | Date或者Calendar对象 | 必须是一个过去的日期 |
@PastOrPresent | Date或者Calendar对象 | 必须是一个过去或者当前的日期 |
@Future | Date或者Calendar对象 | 必须是一个将来的日期 |
@FutureOrPresent | Date或者Calendar对象 | 必须是一个将来或者当前的日期 |
@Pattern | 字符串 | 必须是规则正确的正则表达式 |
字符串 | 必须是Email类型;可以通过指定regexp和flags来自定义email格式;为null时算作通过验证 |
关于 @AssertTrue 与 @AssertFalse 注解对 null 的校验说明,需要区分基本类型(boolean)与装箱类型(Boolean)两种情况。参数为null,校验时则将参数看作其数据类型对应的默认值。boolean 型的默认值为 false,因此 @AssertTure 验证失败;Boolean 型则仍为 null,根据 javadoc 的说明 “null elements are considered valid”,因此都认为是校验通过。
javax.validation.constraints
包仅仅定义了上面的一系列注解,真正的实现逻辑在 org.hibernate.validator
包中,在 SpringBoot 项目启动时一般都会自动引入 hibernate 包。
Maven 包:org.hibernate.validator.constraints:hibernate-validator-6.0.18.Final
注解 | 作用数据类型 | 说明 |
---|---|---|
@Length(min=, max=) | 字符串 | 字符串的长度在指定区间内 |
@Range(min=, max=) | 数值型 | 数值必须在指定闭区间内 |
@CreditCardNumber | 字符串 | 必须是通过Luhn校验和测试的信用卡号码 |
@URL | 字符串 | 必须是URL地址 |
@UniqueElements | 集合类 | 校验集合中的值都是唯一的,null 视为有效成员 |
@Validated实现字段分组校验
在请求参数中添加了上述注解后,还需要在接口接收的参数前加上 @Validated 注解,这些校验的注解才会生效,不加的话则是无效的。
在 controller 接口被调用时,上述注解对应的入参将会进行校验,但这种校验一旦接口被调用则是必须执行的,而不能根据调用调用业务的不同而进行分组处理。例如,在执行添加操作时,一般是多个参数都要求非空,个别参数需要满足其含义的约束;而在执行删除操作时,通常只需要一个起标识作用的 id 或者 seq 非空,其他参数一般都可以为空。为了以一种比较友好的代码逻辑实现这种需求,这里就可以引入分组校验的功能。
首先,可以根据业务的需要定义一些分组接口:
public abstract interface InsertGroup {}
public abstract interface UpdateGroup {}
public abstract interface DeleteGroup {}
然后,在参数使用注解校验的地方加入响应的分组,说明只有采用指定分组时,才会对参数进行校验:
@NotBlank(message="seq不能为空", groups = {DeleteGroup.class})
private String seq;
@NotBlank(message="seq不能为空", groups = {DeleteGroup.class})
@Length(message="标题长度要求在{min}和{max}之间", min = 5, max = 20, groups = {InsertGroup.class, UpdateGroup.class})
private String title;
最后,在 controller 接口的请求参数中通过 @Validated 指定校验使用的分组:
@PostMapping("/add")
public Response add(@RequestBody @Validated({InsertGroup.class}) Request req) {
return new Response.success();
}
@PostMapping("/update")
public Response update(@RequestBody @Validated({UpdateGroup.class}) Request req) {
return new Response.success();
}
@PostMapping("/delete")
public Response delete(@RequestBody @Validated({DeleteGroup.class}) Request req) {
return new Response.success();
}
@Valid
@Valid
注解自 Spring 中就开始使用,我们常常用它进行方法级别或者成员属性级别的校验。与 @Validated
相比,它常用于触发嵌套属性的验证,而不支持分组验证。这里的嵌套属性,指的是在请求体内部,将 @Valid 注解使用在一个类对象上,以触发对这个对象内部每一个嵌套属性的注解校验。举个例子:
public class ApplyForm {
// ...
@Valid
@NotNull
private PersonalInfo personalInfo;
// ...
}
自定义校验注解
参照 @NullBlank 注解,定义了一个如下的自定义注解:
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(DemoLength.List.class)
@Constraint(validateBy = {DemoLengthValidator.class})
public @interface DemoLength {
long min() default 1;
long max() default 10;
String message() default "DemoLength validator annotation.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface List {
DemoLength[] value();
}
}
- 这是一个用于校验参数长度的检验注解,其可指定长度最小值、最大值、以及提示信息
@Constraint(validateBy = {DemoLengthValidator.class})
指定自定义的注解校验类
public class DemoLengthValidator implements ConstraintValidator<DemoLength, Object> {
private long min;
private long max;
@Override
public void initialize(DemoLength constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if (o == null) {
return true;
}
if (o instanceof String) {
int length ((String) o).length();
return min <= length && length <= max;
} else if (o instanceof Integer || o instanceof Long) {
long val = ((Number) o).longValue();
return min <= length && length <= max;
}
return false;
}
}
注解校验类实现了 ConstraintValidator
接口,接口需要指定注解类以及注解应用的参数类型,这里的 DemoLength
注解即可用于判断字符串长度,也可用于判断数值大小,因此参数类型可以指定为 Object
。实现接口主要是为了实现两个方法,顾名思义,initialize
用于初始化注解的配置信息,isValid
则是具体的校验判断逻辑,其中这里将 null 视为有效参数。
注解的校验逻辑
之所以在 Controller 层的请求体参数使用校验注解即能完成校验,是因为 Spring 启动时会为 Controller 类添加一个拦截器 MethodValidationInterceptor
,每当有请求进来的时候,拦截器会对亲求进行拦截,然后判断请求体或者方法是否带有 @Valid
或者 @Validated
注解。如果存在,那么请求会被 AOP 拦截且执行参数校验:校验通过,则可以继续执行后续的业务逻辑;校验不通过,则抛出 ConstraintViolationException
异常,携带配置的特定提示信息返回给请求方。具体源码可见 MethodValidationInterceptor
类。
更多推荐
所有评论(0)