本文将介绍如何在 SpringBoot 中优雅地进行参数校验。
一、普通做法
最普通的做法就是通过代码进行校验,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public String addUser(User user) { if (user == null || user.getId() == null || user.getAccount() == null || user.getPassword() == null || user.getEmail() == null) { return "对象或者对象字段不能为空"; } if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) { return "不能输入空字符串"; } if (user.getAccount().length() < 6 || user.getAccount().length() > 11) { return "账号长度必须是6-11个字符"; } if (user.getPassword().length() < 6 || user.getPassword().length() > 16) { return "密码长度必须是6-16个字符"; } if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) { return "邮箱格式不正确"; }
···
}
|
这种做法有许多缺点:
- 十分繁琐
- 同一个对象的校验代码需要在多个方法中重复书写
- 参数校验逻辑与业务逻辑耦合
二、Validator 简单实用
1. 引入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
|
在 SpringBoot 2.3 之前,validation 包集成在 web 包中
2. 检验实体类
(1) 添加校验规则
在实体类中添加校验规则,如下:
1 2 3 4 5 6 7 8 9
| public class LoginParam {
@Length(min = 5, max = 10, message = "用户名长度应在5-10之间") private String username;
@NotNull(message = "密码不能为空") private String password;
}
|
(2) 开启校验
在接口方法的参数处添加 @Valid
或 @Validated
开启校验,如下:
1 2 3 4
| @PostMapping("/login") public String login(@RequestBody @Validated LoginParam loginParam) { ··· }
|
3. 校验基本类型
(1) 添加检验规则
在接口方法的参数上进行校验规则注解,如下:
1 2 3 4
| @GetMapping("/test") public String test(@NotNull String email) { return "haha"; }
|
(2) 开启校验
在接口类上添加 @Validated
注解,如下:
1 2 3 4 5 6 7 8 9 10
| @Validated @RestController public class AdminAccountController {
@GetMapping("/test") public String test(@NotNull String email) { return "haha"; }
}
|
4. 校验结果
如果参数无法通过校验,将会抛出异常,其中:
- 如果校验实体类失败,并且该实体类用
@RequestBody
注解,会抛出 org.springframework.web.bind.MethodArgumentNotValidException
- 如果校验实体类失败,并且该实体类没有用
@RequestBody
注解,会抛出 org.springframework.validation.BindException
- 如果校验基本类型失败,会抛出 javax.validation.ConstraintViolationException
三、关于 @Valid 和 @Validated
在进行参数校验时,既可以使用 @Valid
也可以使用 @Validated
,两者的区别在于:
@Valid
在 javax 包下;@Validated
在 spring 包下
@Valid
支持嵌套验证;@Validated
不支持
@Valid
不支持分组检验;@Validated
支持分组检验
分组检验:在不同的分组下具有不同的校验规则
四、Validator 配合 BindingResult
如果我们希望传递参数校验的错误信息,可以通过 BindingResult 实现,具体方法为:
如下:
1 2 3 4 5 6 7 8
| public String login(@RequestBody @Validated LoginParam loginParam, BindingResult bindingResult) { if (bindingResult.hasErrors()) { for (ObjectError error : bindingResult.getAllErrors()) { return error.getDefaultMessage(); } } return adminAccountService.login(loginParam); }
|
五、Validator 配置全局异常处理
1. 全局异常处理类
具体请看:
Spring Boot 全局异常处理
2. 校验异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @RestControllerAdvice public class ExceptionAdvice {
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class, ConstraintViolationException.class}) public ResultVO validateFailedException(Exception e) { String msg = null; if (e instanceof MethodArgumentNotValidException) { MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e; msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); } else if (e instanceof BindException) { BindException ex = (BindException) e; msg = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage(); } else if (e instanceof ConstraintViolationException) { ConstraintViolationException ex = (ConstraintViolationException) e; msg = ((ConstraintViolation<?>) (ex.getConstraintViolations().toArray()[0])).getMessage(); } return ResultVO.error(ResultCode.VALIDATE_FAILED, msg); } }
|
六、快速失败
默认情况下,Validator 始终会校验所有字段,可以通过配置开启 Fali Fast
模式,有一项校验失败,便终止校验、抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Configuration public class ValidatorConfig {
@Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); } }
|
参考