在开发接口时,假如要对参数进行校验,你会怎么写?编写 if-else 吗?虽然也能到达作用,可是不够优雅。

今日,引荐一种更简练的写法,运用 SpringBoot Validation 对办法参数进行校验,特别是在编写 Controller 层的办法时,直接运用一个注解即可完结参数校验。

示例代码:spring-validation-demo: SpringBootValidation Demo (gitee.com)

引进依靠

想要完结上述所说的参数校验,咱们需要一个核心依靠:spring-boot-starter-validation,此外,为了便利演示,还需要其他依靠。

依靠如下:

<dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
  <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
  </dependency>
 </dependencies>

以下部分不是核心内容:

你在编写下面的示例代码中,会发现首要运用到了javax.validation.constraints 包下的注解,而这个包首要来自于 jakarta.validation-api 这个依靠。

假如引进依靠的时分直接引进 jakarta.validation-api 是无法完结参数校验功用的,因为它只界说了规范,而没有具体完结。可是 hibernate-validator 完结了这个规范,直接引进 hibernate-validator 也是能够完结参数校验功用的。

<!--无法完结功用-->
<dependency>
  <groupId>jakarta.validation</groupId>
  <artifactId>jakarta.validation-api</artifactId>
</dependency>
<!--能够完结参数校验功用-->
<!--spring-boot-starter-validation 引进了这个依靠-->
<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
</dependency>

相关注解阐明

这里罗列出一些首要的注解,这些注解首要来自于包 javax.validation.constraints,有兴趣检查源码的能够去这个包下检查。

能够先跳过这部分内容,下面的代码假如遇到不清楚作用的注解再回来查阅。

✈ 空值检查

注解 阐明
@NotBlank 用于字符串,字符串不能为null 也不能为空字符串
@NotEmpty 字符串同上,对于集合(Map,List,Set)不能为空,有必要有元素
@NotNull 不能为 null
@Null 有必要为 null

✈ 数值检查

注解 阐明
@DecimalMax(value) 被注释的元素有必要为数字,其值有必要小于等于指定的值
@DecimalMin(value) 被注释的元素有必要为数字,其值有必要大于等于指定的值
@Digits(integer, fraction) 被注释的元素有必要为数字,其值的整数部分精度为 integer,小数部分精度为 fraction
@Positive 被注释的元素有必要为正数
@PositiveOrZero 被注释的元素有必要为正数或 0
@Max(value) 被注释的元素有必要小于等于指定的值
@Min(value) 被注释的元素有必要大于等于指定的值
@Negative 被注释的元素有必要为负数
@NegativeOrZero 被注释的元素有必要为负数或 0

✈ Boolean 检查

注解 阐明
@AssertFalse 被注释的元素有必要值为 false
@AssertTrue 被注释的元素有必要值为 true

✈ 长度检查

注解 阐明
@Size(min,max) 被注释的元素长度有必要在 minmax 之间,能够是 String、Collection、Map、数组

✈ 日期检查

注解 阐明
@Future 被注释的元素有必要是一个将来的日期
@FutureOrPresent 被注释的元素有必要是现在或者将来的日期
@Past 被注释的元素有必要是一个曩昔的日期
@PastOrPresent 被注释的元素有必要是现在或者曩昔的日期

✈ 其他检查

注解 阐明
@Email 被注释的元素有必要是电子邮箱地址
@Pattern(regexp) 被注释的元素有必要契合正则表达式

除此之外,org.hibernate.validator.constraints 包下还有其他校验注解,例如 @ISBN 检查一个字符串是否是一个有效地 ISBN 序列号。

参数校验

接下来开始体验 Spring Boot Validation。

首要,编写一个需要校验的实体类:

@Data
public class Student {
  @NotBlank(message = "主键不能为空")
  private String id;
  @NotBlank(message = "名字不能为空")
  @Size(min=2, max = 4, message = "姓名字符长度有必要为 2~4个")
  private String name;
  @Pattern(regexp = "^1(3\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\d|9[0-35-9])\d{8}$", message = "手机号格局过错")
  private String phone;
  @Email(message = "邮箱格局过错")
  private String email;
  @Past(message = "生日有必要早于当前时刻")
  private Date birth;
  @Min(value = 0, message = "年纪有必要为 0~100")
  @Max(value = 100, message = "年纪有必要为 0~100")
  private Integer age;
  @PositiveOrZero
  private Double score;
}

随后编写一个操控层代码,进行测试:

@RestController
public class TestController {
​
  @GetMapping("/test")
  public Student test(@RequestBody @Validated Student student) {
    return student;
   }
}

运用 postman 进行测试,发送一个不带参数的恳求,检查结果:

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验

后端操控台日志打印是这样的(显示极度不友好),能够看到校验规则收效了:

2022-11-23 22:10:13.249  WARN 19840 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.springvalidationdemo.domain.Student com.example.springvalidationdemo.controller.TestController.test(com.example.springvalidationdemo.domain.Student) with 2 errors: [Field error in object 'student' on field 'name': rejected value [null]; codes [NotBlank.student.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.name,name]; arguments []; default message [name]]; default message [名字不能为空]] [Field error in object 'student' on field 'id': rejected value [null]; codes [NotBlank.student.id,NotBlank.id,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [student.id,id]; arguments []; default message [id]]; default message [主键不能为空]] ]

大局反常处理

检查上面的日志打印,能够看到当参数校验不经过期,会抛出反常 MethodArgumentNotValidException,一起也会打印那些参数没有经过校验,以及该参数校验规则

为了便利检查,咱们能够编写一个大局反常处理,处理这个参数校验反常,并运用统一回来实体回来给前端

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
​
  @ExceptionHandler(MethodArgumentNotValidException.class)
  @ResponseBody
  public ResponseEntity<Object> exception(MethodArgumentNotValidException e, HttpServletRequest request) {
    Map<String, String> result = new HashMap<>();
    BindingResult bindingResult = e.getBindingResult();
    log.error("恳求[ {} ] {} 的参数校验发生过错", request.getMethod(), request.getRequestURL());
    for (ObjectError objectError : bindingResult.getAllErrors()) {
      FieldError fieldError = (FieldError) objectError;
      log.error("参数 {} = {} 校验过错:{}", fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage());
      result.put(fieldError.getField(), fieldError.getDefaultMessage());
     }
    // 一般项目都会有自己界说的公共回来实体类,这里直接运用现成的 ResponseEntity 进行回来,一起设置 Http 状况码为 400
    return ResponseEntity.badRequest().body(result);
​
   }
​
}

再次运用 postman 发起测试:

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验

操控台打印出自界说的日志信息:

2022-11-23 22:16:37.800 ERROR 19880 --- [nio-8080-exec-2] c.e.s.handler.GlobalExceptionHandler   : 恳求[ GET ] http://localhost:8080/test 的参数校验发生过错
2022-11-23 22:16:37.800 ERROR 19880 --- [nio-8080-exec-2] c.e.s.handler.GlobalExceptionHandler   : 参数 name = null 校验过错:名字不能为空
2022-11-23 22:16:37.800 ERROR 19880 --- [nio-8080-exec-2] c.e.s.handler.GlobalExceptionHandler   : 参数 id = null 校验过错:主键不能为空
2022-11-23 22:19:36.594 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 恳求[ GET ] http://localhost:8080/test 的参数校验发生过错
2022-11-23 22:19:36.594 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 email = abc.com 校验过错:邮箱格局过错
2022-11-23 22:19:36.594 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 score = -20 校验过错:有必要是正数或零
2022-11-23 22:19:36.595 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 birth = Thu Jan 01 08:00:00 CST 2099 校验过错:生日有必要早于当前时刻
2022-11-23 22:19:36.595 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 phone = 12233 校验过错:手机号格局过错
2022-11-23 22:19:36.595 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 age = -40 校验过错:年纪有必要为 0~100
2022-11-23 22:19:36.595 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 name = 我是很长的名字 校验过错:姓名字符长度有必要为 2~4个
2022-11-23 22:19:36.595 ERROR 19880 --- [nio-8080-exec-6] c.e.s.handler.GlobalExceptionHandler   : 参数 score = -20 校验过错:需要在09223372036854775807之间

传递校验

咱们也能够运用传递校验,即一个参数类中包括了另一个参数类,被包括的参数类也能够被校验。

在声明一个新的参数类,一起修正 Student 类。

@Data
public class ClassInfo {
  @NotBlank(message = "班主任名字不能为空")
  private String teacher;
  @NotNull(message = "教师不能为空")
  private Integer classroom;
  @NotNull(message = "年级不能为空")
  @Min(value = 1, message = "年级只能是 1-6")
  @Max(value = 6, message = "年级只能是 1-6")
  private Integer grade;
}
​
@Data
public class Student {
  //.............
  // 新加的字段,被包括的参数类,运用 @Valid 就能传递校验,假如不运用 @Valid 注解,则无法传递校验。
  @Valid
  private ClassInfo classInfo;
}

再运用 postman 测试一次

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验

分组校验

此外还能够运用分组校验,令一组办法对某些字段校验,而令一组办法对其他字段校验,例如:一般情况下,新增实体的接口办法 [POST] 不需要主键 ID,修正实体的接口办法 [PUT] 就需要主键 ID 以便进行修正。

为注解 @Validated 赋值属性 value,以及为那些校验注解赋值属性 group, 即可到达分组的作用。

接下来看看如何完结分组校验。

Student 类中添加两个内部接口 Inteface,一起修正 id 字段的注解,以进行分组

@Data
public class Student {
  // id 字段归于 Create 组
  @NotBlank(message = "主键不能为空", groups = {Student.Create.class})
  private String id;
  // .............
  // 更新分组
  public interface Update {}
​
  // 创建分组
  public interface Create {}
}

在操控层新增两个接口

@RestController
public class TestController {
​
  // @Validated 注解能够赋值 value 属性进行分组,value 是能够以数组的方式赋值,既能够分配多个组
  @PostMapping("/students")
  public Student create(@RequestBody @Validated(Student.Create.class) Student student) {
    return student;
   }
​
  @PutMapping("/students")
  public Student update(@RequestBody @Validated(Student.Update.class) Student student) {
    return student;
   }
}

在 postman 上进行测试:

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验

更简洁的参数校验,使用 SpringBoot Validation 对参数进行校验

能够看到分组校验也收效了。

总结

在实践开发中,咱们能够运用 Spring Boot Validation 提供的注解进行参数校验,提高代码的可读性,防止编写很多的 if-else 代码块和重复的校验句子。