springboot项目-参数校验

通常我们在controller层接收前端传递的参数,通过springmvc进行参数绑定。但是我们无法确定参数传递的正确性,所以需要编写参数校验的代码,判断NULL,空格,是否为数字,正负,字符串长度等等,假如每个接口都编写这样的代码,会导致项目在垂直角度上存在大量冗余的代码。影响阅读和维护。
校验数据的习惯是正确的,我们无法保证每个用户都按照规定格式进行输入,有必要进行校验处理。
为了更好的关注核心的业务逻辑,减少参数校验侵入方法内部,因此有了 JRS-303 提案。

JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。

数据校验的2个工具:

  1. javax.validation.constraints
  2. 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);
    }

测试:

springboot 执行js springboot jsch_Java

  • 结果
{
    "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校验参数