springboot项目-参数校验
通常我们在controller层接收前端传递的参数,通过springmvc进行参数绑定。但是我们无法确定参数传递的正确性,所以需要编写参数校验的代码,判断NULL,空格,是否为数字,正负,字符串长度等等,假如每个接口都编写这样的代码,会导致项目在垂直角度上存在大量冗余的代码。影响阅读和维护。
校验数据的习惯是正确的,我们无法保证每个用户都按照规定格式进行输入,有必要进行校验处理。
为了更好的关注核心的业务逻辑,减少参数校验侵入方法内部,因此有了 JRS-303 提案。
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
数据校验的2个工具:
- javax.validation.constraints
- Hibernate Validator
- 区别:validation bean 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现。Hibernate Validator是一个hibernate独立的包,可以直接引用,他实现了validation bean同时有做了扩展,比较强大。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
Bean Validation 2.0 中的 constraint
注解 | 详细信息 |
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Negative | 被注释的元素必须是一个严格的负数,举例,0 被视为无效值,因为不是负数 |
@NegativeOrZero | 被注释的元素必须是一个严格的负数或者 0 |
@Positive | 被注释的元素必须是一个严格的正数,举例,0 被视为无效值,因为不是正数 |
@PositiveOrZero | 被注释的元素必须是一个严格的正数或者 0 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个带小数的数,integer 为整数位最大值,fraction 为小数位最大值 |
@Past | 被注释的元素必须是一个过去的日期 |
@PastOrPresent | 被注释的元素必须是一个过去的日期或者现在日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@FutureOrPresent | 被注释的元素必须是一个将来的日期或者现在日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
@NotEmpty | 被注释的元素必须不为 null 或者不为空,可校验字符、集合、Map |
@NotBlank | 被注释的元素必须不为空字符串 |
@Email | 被注释的元素必须是电子邮箱地址 |
Hibernate Validator 附加的 constraint
注解 | 详细信息 |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@Range | 被注释的元素必须在合适的范围内 |
简单的使用案例:
@Data
public class UserRegisterForm {
@NotNull
private String username;
@NotBlank
private String password;
@Email(message = "使用正确的email")
private String email;
}
如果前端传递的数据没有按照规定的格式要求,在参数校验时会抛出异常。通过message
以制定错误消息。
通过使用@Valid
注解让校验注解生效。
@PostMapping("/register")
public ResponseVo registerUser(@Valid @RequestBody UserRegisterForm userRegisterForm) {
log.info("用户注册:[{}]", userRegisterForm.getUsername());
User user = new User();
BeanUtils.copyProperties(userRegisterForm, user);
return userService.register(user);
}
测试:
- 结果
{
"timestamp": "2020-12-13T06:55:36.651+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.userRegisterForm.password",
"NotBlank.password",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"userRegisterForm.password",
"password"
],
"arguments": null,
"defaultMessage": "password",
"code": "password"
}
],
"defaultMessage": "不能为空",
"objectName": "userRegisterForm",
"field": "password",
"rejectedValue": " ",
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"Email.userRegisterForm.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"userRegisterForm.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "请使用正确的email",
"objectName": "userRegisterForm",
"field": "email",
"rejectedValue": "1111111.com",
"bindingFailure": false,
"code": "Email"
}
],
"message": "Validation failed for object='userRegisterForm'. Error count: 2",
"path": "/user/register"
}
- 快捷使用:直接在接受的参数上使用检验的注解,但是要注意必须使用
@Validated
注解来使其生效
@Slf4j
@Validated // 使用 spring 的 Validated 注解标注此 Controller 是需要执行校验的,
@RestController
@RequestMapping("/demo-1")
public class Demo1Controller {
@GetMapping("/query-1")
public HttpStatus query1(@NotBlank(message = "不能为空") String name) {
log.info("name is {}", name);
return HttpStatus.OK;
}
}
异常捕获
通常使用了参数校验后,可以看到返回的异常信息比较多,我们可以通过全局异常处理来处理,提炼出重要的错误信息返回给前端。MethodArgumentNotValidException
类:为参数异常的主要类,通过捕获该类,提取BindingResult
类来得到相关的信息。
参考代码如下:
@RestControllerAdvice
@Slf4j
public class RuntimeExceptionHandler {
/**
* 参数校验异常进行统一异常处理
* @param e
* @return
*/
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public ResponseVo notValidExceptionHandler(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
log.warn("参数:【{}】异常:【{}】", bindingResult.getFieldError().getField(),
bindingResult.getFieldError().getDefaultMessage());
return ResponseVo.error(ResponseEnum.PARAM_ERROR, bindingResult);
}
}
结果类:
@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class ResponseVo<T> {
private Integer status;
private String msg;
private T data;
public ResponseVo(Integer status, String msg) {
this.status = status;
this.msg = msg;
}
public ResponseVo(Integer status, T data) {
this.status = status;
this.data = data;
}
public static <T> ResponseVo<T> error(ResponseEnum responseEnum, BindingResult bindingResult) {
return new ResponseVo(responseEnum.getCode(),
Objects.requireNonNull(bindingResult.getFieldError()).getField()
+ " " + bindingResult.getFieldError().getDefaultMessage());
}
}
以上为通常使用的方式,更多操作类似:分组校验,层级校验的使用可以查看该文章
Bean Validation 校验实践
SpringBoot使用Validation校验参数