初探Springboot 参数校验

2 阅读5分钟

前言

工作中我们经常会遇到验证字段是否必填,或者字段的值是否在给定范围之内等等类似的问题,如果说是一两个字段的验证还好,验证的字段很多的话,代码就会被大量的if语句包围。通常来说,这些关于字段的判断应该和业务逻辑分开来,可能我们想到的第一个解决方案就是通过AOP,这也能解决我们的问题的。但实际上大可不必,作为一个成熟的语言,Java已经给我们提供解决方案了,那就是Bean Validation

Bean Validation

JSR-303是Java EE 6中的一项子规范,名为Bean Validation,这是Bean Validation 1.0 版本,目前已发展到到3.0版本,名为Jakarta Bean Validation 3.0Bean Validation提供了一个数据验证的框架,用于对Java Bean中的字段的值进行验证。它使得基本的验证逻辑可以从业务代码中脱离出来,成为一个独立的验证层。

JSR-303的官方参考实现是Hibernate ValidatorHibernate Validator提供了JSR 303规范中所有内置约束的实现,除此之外还有一些附加的约束。

这种验证机制是运行时的,也就是说,在验证之后,如果数据不符合指定的约束,那么会立即返回错误信息。

总之,JSR-303 为Java应用程序提供了一种方便、灵活且强大的数据验证方式。

注解

JSR-303 提供了一系列注解,用于在Java中进行数据校验。这些注解主要用于对实体类的属性进行约束,以确保数据的有效性。

在这里插入图片描述

以下是一些常用的JSR-303 validation注解:

  1. @NotNull:用于对象的校验,确保对象不为null
  2. @NotBlank:验证对象是否不为空,相比@NotNull会去掉首尾空格,对象类型为CharSequence
  3. @NotEmpty: 验证对象(如数组、Collection集合、MapString)是否不为NULL并且长度或者大小不为空
  4. @Size:用于验证对象(如数组、Collection集合、MapString)的长度或大小是否在给定的范围之内。
  5. @Pattern:验证字符串是否匹配指定的正则表达式,null值被认为是有效的格式。
  6. @Email:验证是否符合电子邮件格式。
  7. @Min:验证数字是否大于等于指定值,
  8. @Max:验证数字是否小于等于指定值。
  9. @AssertTrue:验证Boolean对象是否为true。
  10. @AssertFalse:验证Boolean对象是否为false。
  11. @NotBlank:验证CharSequence 对象非null,且长度必须大于0。
  12. @DecimalMin(value):被注解的对象必须是一个数字,其值必须大于等于指定的最小值,对象类型可以为 BigDecimalBigIntegerCharSequence
  13. @DecimalMax:被注解的对象必须是一个数字,其值必须小于等于指定的最大值。
  14. @Digits(integer,fraction):被注解的元素必须是一个数字,其值必须在指定的整数和小数部分的最大位数的范围之内。
  15. @Past:被注解的元素必须是一个过去的日期。
  16. @Future:被注解的元素必须是一个将来的日期。
  17. @FutureOrPresent:被注解的元素必须是现在或将来的一个瞬间、日期或时间。
  18. @PositiveOrZero:被注解的元素必须为正数或零。
  19. @Positive:被注解的元素必须是正数(不包括0)。
  20. @NegativeOrZero:被注解的元素必须为负数或零。
  21. @Negative:被注解的元素必须是负数(不包括0)。
  22. @Null:被注解的元素必须是NULL

Hibernate Validator 附加的约束注解: Hibernate Validator 8.0.1官方链接 感兴趣的可以去看看。

Hibernate Validator 8.0.1

实践出真知

下面通过代码演示一下Springboot 中字段验证的使用。

1. 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2. 创建Bean 用于校验

@Data
public class UserBean {

    
    @NotEmpty
    private String username;

    @Min(value = 18)
    private Integer age;

   @Email(message = "邮箱格式不正确")
    private String email;
}

3. 创建访问接口

接口中要使用 @Validated 或者 @Valid使Bean 验证生效,下一篇文章 @Validated@Valid 之间的区别。

@RestController
@RequestMapping("validation")
public class ValidationController {

    @GetMapping("user")
    public UserBean getUserBean(@Validated UserBean userBean) {
        return userBean;
    }
}

4. 使用postman进行测试

因为username校验不通过,所以postman响应结果如下,响应结果不够友好,下面会进行改造:

{
    "timestamp": "2024-02-28T09:15:23.925+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/validation/user"
}

控制台打印结果比较详细:

2024-02-28T17:15:23.918+08:00  WARN 24752 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : 
Resolved [org.springframework.web.bind.MethodArgumentNotValidException:
 Validation failed for argument [0] in public site.suncodernote.validation.UserBean 
 site.suncodernote.validation.ValidationController.getUserBean(site.suncodernote.validation.User
 Bean): [Field error in object 'userBean' on field 'username': rejected value [null]; codes 
 [NotEmpty.userBean.username,NotEmpty.username,NotEmpty.java.lang.String,NotEmpty]; arguments 
 [org.springframework.context.support.DefaultMessageSourceResolvable: codes 
 [userBean.username,username]; arguments []; default message [username]]; default message [不能为空]] ]

异常处理

由于默认的校验提示不够友好,无法具体显示是哪个字段出现的问题,下面我们将其简单改造一下。

1. 封装统一响应对象

@Data
public class ResponseResult <T> implements Serializable {

    private boolean success;

    /**
     * 返回处理消息
     */
    private String message;

    /**
     * 返回代码
     */
    private Integer code = 0;

    /**
     * 返回数据对象 data
     */
    private T result;
}

2. 封装全局异常处理类

@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     *  参数校检异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseResult<?> handle(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();

        StringJoiner joiner = new StringJoiner(";");

        for (ObjectError error : bindingResult.getAllErrors()) {
            // 注解名称
            String code = error.getCode();
            String[] codes = error.getCodes();

            String property = codes[1];
            property = property.replace(code ,"").replace(".","");

            String defaultMessage = error.getDefaultMessage();
            joiner.add(property+defaultMessage);
        }
        return handleException(joiner.toString());
    }

    private ResponseResult<?> handleException(String msg) {
        ResponseResult<?> result = new ResponseResult<>();
        result.setMessage(msg);
        result.setCode(500);
        return result;
    }
}

GlobalExceptionHandler 异常处理类中,为了省事就把字段名和验证失败提示语拼接到一起了。

3. 再次测试

再次测试可以看到在响应结果中得到了我们想要的结果了,至此Springboot参数校验入门就完成了。

总结

Springboot 参数校验在实际工作中用处非常大,本文只是简单介绍一下有哪些注解和简单使用,后续会对Springboot参数校验做一个详细的介绍和使用,感兴趣可以关注一下。