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

简介: 在开发接口时,如果要对参数进行校验,你会怎么写?编写 if-else 吗?虽然也能达到效果,但是不够优雅。今天,推荐一种更简洁的写法,使用 SpringBoot Validation 对方法参数进行校验,特别是在编写 Controller 层的方法时,直接使用一个注解即可完成参数校验。

在开发接口时,如果要对参数进行校验,你会怎么写?编写 if-else 吗?虽然也能达到效果,但是不够优雅。
今天,推荐一种更简洁的写法,使用 SpringBoot Validation 对方法参数进行校验,特别是在编写 Controller 层的方法时,直接使用一个注解即可完成参数校验。
示例代码:spring-validation-demo: SpringBootValidation Demo (gitee.com)
🚀引入依赖
想要完成上述所说的参数校验,我们需要一个核心依赖:spring-boot-starter-validation,此外,为了方便演示,还需要其他依赖。
依赖如下:
 
     
       org.springframework.boot
       spring-boot-starter-web
     
     
       org.springframework.boot
       spring-boot-starter-validation
     
     
       org.projectlombok
       lombok
       true
     
   
复制代码
💡 以下部分不是核心内容:
你在编写下面的示例代码中,会发现主要使用到了javax.validation.constraints 包下的注解,而这个包主要来自于 jakarta.validation-api 这个依赖。
如果引入依赖的时候直接引入 jakarta.validation-api 是无法实现参数校验功能的,因为它只定义了规范,而没有具体实现。但是 hibernate-validator 实现了这个规范,直接引入 hibernate-validator 也是可以实现参数校验功能的。
 
 
     jakarta.validation
     jakarta.validation-api
 
 
 
 
     org.hibernate.validator
     hibernate-validator
 
复制代码
🚀 相关注解说明
这里罗列出一些主要的注解,这些注解主要来自于包 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)被注释的元素长度必须在 min 和 max 之间,可以是 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 进行测试,发送一个不带参数的请求,查看结果:
0.7.png

💡后端控制台日志打印是这样的(显示极度不友好),可以看到校验规则生效了:
 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 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 发起测试:
0.6.png

控制台打印出自定义的日志信息:
 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 校验错误:需要在0和9223372036854775807之间
复制代码
🚀 传递校验
我们也可以使用传递校验,即一个参数类中包含了另一个参数类,被包含的参数类也可以被校验。
在声明一个新的参数类,同时修改 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 测试一次
0.5.png

🚀 分组校验
此外还可以使用分组校验,令一组方法对某些字段校验,而令一组方法对其他字段校验,例如:一般情况下,新增实体的接口方法 [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 上进行测试:
0.4.png
0.3.png

可以看到分组校验也生效了。
🚀 总结
在实际开发中,我们可以使用 Spring Boot Validation 提供的注解进行参数校验,提高代码的可读性,避免编写大量的 if-else 代码块和重复的校验语句。

相关文章
|
14天前
|
Java Spring
SpringBoot 实战 不同参数调用不同实现
本文介绍了如何在实际工作中根据不同的入参调用不同的实现,采用`map+enum`的方式实现优雅且严谨的解决方案。通过Spring Boot框架中的工厂模式或策略模式,避免了使用冗长的`if...else...`语句。文中详细展示了定义接口、实现类、枚举类以及控制器调用的代码示例,确保用户输入的合法性并简化了代码逻辑。
SpringBoot 实战 不同参数调用不同实现
|
6月前
|
缓存 前端开发 Java
springboot 的单体服务 字典参数转译
本文介绍了如何在Spring Boot项目中使用缓存来管理字典参数,并确保前后端数据一致性。首先,通过`@EnableCaching`启用缓存功能,接着创建一个自定义的字典缓存类`DicCache`。然后,通过配置类将`DicCache`添加到`cacheManager`中。此外,对字典服务进行改造,使用`@CachePut`和`@CacheEvict`注解保证数据一致性。最后,实现自定义注解`@DicSerializer`和序列化处理类`DictSerializerHandel`,用于在序列化过程中自动转换字典值。通过这种方式,可最小化代码改动并提高系统性能。
springboot 的单体服务 字典参数转译
|
5月前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
6月前
|
JSON Java 数据格式
springboot 参数统一处理
springboot 参数统一处理
|
5月前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
307 0
|
6月前
|
Java Spring
spring boot 启动项目参数的设定
spring boot 启动项目参数的设定
|
7月前
|
Java API 数据格式
Spring Boot API参数读取秘籍大公开!6大神器助你秒变参数处理大师,让你的代码飞起来!
【8月更文挑战第4天】Spring Boot凭借其便捷的开发和配置特性,成为构建微服务的热门选择。高效处理HTTP请求参数至关重要。本文介绍六种核心方法:查询参数利用`@RequestParam`;路径变量采用`@PathVariable`;请求体通过`@RequestBody`自动绑定;表单数据借助`@ModelAttribute`或`@RequestParam`;请求头使用`@RequestHeader`;Cookie则依靠`@CookieValue`。每种方法针对不同场景,灵活运用可提升应用性能与用户体验。
109 9
|
8月前
|
Java Spring
springBoot 使用 @NotEmpty,@NotBlank,@NotNull 及@Valid注解校验请求参数
springBoot 使用 @NotEmpty,@NotBlank,@NotNull 及@Valid注解校验请求参数
356 7
|
8月前
|
Java 测试技术 Spring
支付系统15-----支付宝支付,引入支付参数,如何使支付宝的配置信息变成SpringBoot相关的配置信息
支付系统15-----支付宝支付,引入支付参数,如何使支付宝的配置信息变成SpringBoot相关的配置信息