image.png
数据校验是在平时的编码过程中常做的工作,在系统的各个层可能都要去实现一些校验逻辑,再去做业务处理。这些繁琐的校验与我们的业务代码在一块就会显得臃肿。而且这些校验通常是业务无关的。
Bean Validation 2.0(JSR 380)定义了用于实体和方法验证的元数据模型和 API,Hibernate Validator 是目前最好的实现,下面整理具体使用。

依赖

如果是 Spring Boot2.3 之前的项目,那么 spring-boot-starter-web 中就已经依赖 hibernate-validator 了

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

否则可以添加 hibernate-validator 依赖

1
2
3
4
5
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>

注解介绍

validator 内置注解

@Null
被注释的元素必须为 null
@NotNull
被注释的元素必须不为 null
@AssertTrue
被注释的元素必须为 true
@AssertFalse
被注释的元素必须为 false
@Min(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value)
被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value)
被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max, min)
被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction)
被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past
被注释的元素必须是一个过去的日期
@Future
被注释的元素必须是一个将来的日期
@Pattern(value)
被注释的元素必须符合指定的正则表达式

Hibernate Validator 附加的 constraint

@Email
被注释的元素必须是电子邮箱地址
@Length
被注释的字符串的大小必须在指定的范围内
@NotEmpty
被注释的字符串的必须非空
@Range
被注释的元素必须在合适的范围内
@NotBlank
验证字符串非 null,且长度必须大于 0
注意

  • @NotNull 适用于任何类型被注解的元素必须不能与 NULL
  • @NotEmpty 适用于 String Map 或者数组不能为 Null 且长度必须大于 0
  • @NotBlank 只能用于 String 上面 不能为 null,调用 trim()后,长度必须大于 0

使用

模拟用户注册封装了一个 UserVO
当提交数据的时候如果使用以前的做法就是 IF ELSE 判断参数,使用 validator 则是需要增加注解即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserVO {
/**
* 用户名-邮箱注册
*/
@NotBlank(message = "用户名不能为空")
@Email(message = "邮箱格式不正确")
private String name;

/**
* 密码
*/
@Size(min = 6, message = "密码不能少于6位")
@NotBlank(message = "密码不能为空")
private String password;

/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
private String code;
}

然后需要在 controller 方法体添加@Validated 不加@Validated 校验会不起作用

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class UserAuthController {

@Autowired
private UserAuthService userAuthService;

@PostMapping("/register")
public ApiResponse<?> register(@Valid @RequestBody UserVO user) {
userAuthService.register(user);
return ApiResponse.ok();
}
}

测试

生成一个符合校验的数据

1
2
3
4
5
{
"name": "dzgu@163.com",
"password": 1234567,
"code": "6319"
}

返回结果
image.png
生成一个不符合 name 校验的数据

1
2
3
4
5
{
"name": "dzgu",
"password": 1234567,
"code": "6319"
}

返回结果
image.png
这样是能校验成功,但是有个问题就是返回参数并不理想,前端也并不容易处理返回参数,所以我们添加一下参数校验的全局异常处理(https://www.yuque.com/ugdongzhou/kd5pz2/ecv4fp),然后添加一下全局统一返回参数这样比较规范。

添加全局异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig {

/**
* 处理业务异常
*
* @param e 异常
* @return 自定义数据传输
*/
@ExceptionHandler(value = BizException.class)
public ApiResponse<?> errorHandler(BizException e) {
log.error("业务异常:" + e);
return ApiResponse.fail(e.getCode(), e.getMessage());
}

/**
* 处理参数校验异常
*
* @param e 异常
* @return 接口异常信息
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ApiResponse<?> errorHandler(MethodArgumentNotValidException e) {
List<ObjectError> errors = e.getBindingResult().getAllErrors();
StringBuilder errorMessages = new StringBuilder();
errors.forEach(error -> errorMessages.append(error.getDefaultMessage()).append(";"));
String s = errorMessages.toString();
log.error("参数校验异常:" + s);
return ApiResponse.fail(ExceptionEnum.VALID_ERROR.getCode(), s);

}

/**
* 处理系统异常
*
* @param e 异常
* @return 自定义数据传输
*/
@ExceptionHandler(value = Exception.class)
public ApiResponse<?> errorHandler(Exception e) {
log.error("系统异常:" + e);
return ApiResponse.fail(ExceptionEnum.SYSTEM_ERROR);
}
}

此方法主要捕捉 MethodArgumentNotValidException 异常然后对异常结果进行封装,如果需要在自行添加其他异常处理。
添加完之后我们在看一下运行结果,调用接口返回:
image.png