参数校验
通过注解来校验输入的数据。
在 JSR-303
标准中定义了校验的接口, hibernate-validator
提供了实现。
本文环境:
- Spring Boot 2.7.6
- Spring-boot-starter-validation: 2.7.6
# 快速开始
# 1、依赖引入
<!-- validation -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!-- hibernate-validator-starter-for-spring-boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2
3
4
5
6
7
8
9
10
# 2、属性添加校验
class User {
@NotNull
private int id;
@NotBlank(message = "不能为空")
private String name;
@NotEmpty
private List<String>
//... getter setter constructor
}
2
3
4
5
6
7
8
9
10
在属性上加上注解 @NotNull
表示这个字段不能为空指针。 @NotBlank
表示字符串至少含一个非空字符,@NotEmpty
表示集合至少包含一个非空元素。
# 3、使用@Valid校验
在控制器方法的参数前,加上 @Valid
表示要校验这个输入。当输入的数据都符合条件时,会直接开始执行控制器代码,如果参数校验错误,
则会抛出异常 MethodArgumentNotValidException
, 走默认的流程响应异常,如何处理异常在下文。
RestController
public class ErrorController {
@PostMapping("/users")
public Response<String> addUsers(
@Valid @RequestBody User user) {
return Response.success("ok");
}
}
2
3
4
5
6
7
8
9
也可以通过在添加个形参 BindingResult
来接受参数校验的结果,此时即使参数校验不通过,也不会抛出异常,而是会将异常信息存储在 BindingResult
中,
然后开始直接执行控制器的代码,如何处理 BindingResult 在下文中。
# 4、常用注解
# 校验失败处理
校验失败处理通常是先获得错误的信息,然后在决定接下来的执行流程。通常是将错误信息封装成返回体返回。
# 1、校验异常处理
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, String> handleValidationException(MethodArgumentNotValidException ex) {
HashMap<String, String> errorMap = new HashMap<String, String>();
ex.getBindingResult().getFieldErrors().forEach((item) -> {
errorMap.put(item.getField(), item.getDefaultMessage());
});
return errorMap;
}
}
2
3
4
5
6
7
8
9
10
11
12
# 2、BindingResult处理
public class BindingResultUtil {
static public void processBindingResult(BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
StringBuilder stringBuilder = new StringBuilder();
bindingResult.getFieldErrors().forEach((item)->{
stringBuilder.append(item.getField()).append(":").append(item.getDefaultMessage()).append(",");
});
// 自定义的 ServiceException
throw new ServiceException(
ResponseCodeEnum.PARAMETER_ERROR.getCode(),
stringBuilder.toString());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分组校验
分组校验是在同一个类的基础上,通过分组的方式,产生不一样的属性校验组合。
最常见的场景就是新增和更新,可以公用一个类型来接受用户的输入,但是略有不同。比如新增的时候不需要数据的 id
, 但是更新的时候需要。这个时候可以
定义两个类来分别接受,也可以公用同一个类,使用 分组校验
的方式来定义在某个分组下,属性的不同校验方式。
# 1、 通过空接口来定义分组
定义接口获得两个分组: AddGroup
和 UpdateGroup
.
public interface AddGroup{}
public interface UpdateGroup{}
2
# 2、校验注解加上分组
public class User {
@NotBlank(message = "id不能为空", groups = {UpdateGroup.class})
private String id;
@NotBlank(message = "name不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String name;
@NotBlank(groups = {AddGroup.class})
private String age;
}
2
3
4
5
6
7
8
9
# 3、执行校验
@Valid
并不支持分组校验,Spring 框架提供的 @Validated
支持分组校验。注解需要提供分组的信息来激活分组。
@RestController
public class ErrorController {
@PostMapping("/users")
public Response<String> addUsers(
@Validated(AddGroup.class) @RequestBody User user) {
return Response.success("ok");
}
}
2
3
4
5
6
7
8
9
# 自定义校验注解
# 1、引入接口依赖
自定义注解,即实现 JSR-303
接口,需要先引入接口依赖。
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
2
3
4
# 2、 编写自定义校验注解
@Documented
//指定校验器,可以指定多个校验器,会自动更具校验字段类型匹配不同的校验器
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, PARAMETER, TYPE_USE})
public @interface ListValue {
// 必须字段1: 提示信息。 在类路径下定义 ValidationMessages.properties, key=value, key作为 default中的值
String message() default "{xx.xxx.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals default { };
}
2
3
4
5
6
7
8
9
10
11
必要的三个字段: message, groups, payload.其余形式基本固定。
# 3、 编写自定义的校验器
class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
@Override
// 初始化方法
public void initialize(ListValue constraintAnnotation) {
int[] vals = constrainAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
implement ConstraintValidator<T,P>, T是注解类型, P是被注解的属性的类型。
如果想这个注解支持多种数据类型,就要为每种类型定义一个校验器。然后在关联的时候传入这些校验器。
# 4、 关联
在第二步定义校验注解的位置,将校验器与校验注解关联,表示用此校验器来校验被注解的属性是否符合规则。
@Constraint(validatedBy = {ListValueConstraintValidator.class})
# 其他
# 1、校验注解区别
@Valid
和@Validated
都可以用来执行校验,但是 @Valid
不支持分组校验,@Validated
支持分组校验。
# 2、支持校验多种数据类型的注解
定义多个校验器,然后在关联的时候,给注解传入多个校验器类即可。