0


Spring Boot中参数校验

前言

为了保证数据的正确性、完整性,前后端都需要进行数据检验。作为一名后端开发工程师,不能仅仅依靠前端来校验数据,我们还需要对接口请求的参数进行后端的校验。最常见的做法就是通过if/else语句来对请求的每一个参数一一校验,当很多参数需要校验的时候,if/else语句就会比较长,写起来也比较麻烦,一点都不简洁、美观。所以,今天来和大家分享一下Spring Boot Validation。

spring-boot-starter-validation

Spring Boot 2.3 1 之后,spring-boot-starter-validation 已经不包括在了 spring-boot-starter-web 中,需要我们手动加上。

如下图所示,spring-boot-starter-web-2.2.6.RELEASE就包含了spring-boot-starter-validation,

而spring-boot-starter-web-2.5.7并没有spring-boot-starter-validation,需要自己手动加入依赖。

如下图所示,手动加入依赖spring-boot-starter-validation,实际上依赖了hibernate-validator。

@Valid和@Validated

@Valid所属包为javax.validation,不具备分组校验功能;可以用在方法、构造函数、方法参数和成员属性(field)上

说明:Java的JSR-303声明了@Valid这类接口,而hibernate-validator对其进行了实现。

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

@Validated所属包为org.springframework.validation.annotation,属于spring的校验机制,具有分组校验功能;用在类型、方法和方法参数上。但不能用于成员属性(field)。

@Valid和@Validated注解可以结合使用,来实现嵌套验证。

常用的注解如下:

简单使用

添加依赖

本文使用的是Spring Boot 2.5.7,具体依赖如下:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-validation</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.projectlombok</groupId>
  11. <artifactId>lombok</artifactId>
  12. <optional>true</optional>
  13. </dependency>

实体类

  1. @Data
  2. @ToString
  3. public class User {
  4. private Integer id;
  5. @NotBlank(message = "姓名不能为空")
  6. private String name;
  7. @Max(value = 100, message = "最大不超过100")
  8. private Integer age;
  9. }

Controller

  1. @RestController
  2. public class TestController {
  3. /**
  4. * post请求 实体类User 加上注解@Valid
  5. */
  6. @PostMapping("/test")
  7. public String test(@Valid @RequestBody User user) {
  8. System.out.println(user);
  9. return "Hello World";
  10. }
  11. /**
  12. * get请求 实体类User 加上注解@Valid
  13. * 测试:http://localhost:8080/query2?name=&age=101
  14. */
  15. @GetMapping("/query2")
  16. public String queryUserInfo(@Valid User user) {
  17. System.out.println("query2");
  18. return user.getName();
  19. }
  20. }
  1. /**
  2. * 【反面教材】验证不生效
  3. */
  4. @RestController
  5. public class Test3Controller {
  6. @GetMapping("/test3")
  7. public String test3(@NotEmpty(message = "name不能为空") String name,
  8. @Max(value = 100, message = "请输入数字") Integer age) {
  9. System.out.println("是啥啊3");
  10. return name;
  11. }
  12. @GetMapping("/test4")
  13. @Validated
  14. public String test4(@NotEmpty(message = "name不能为空") String name,
  15. @Max(value = 100, message = "请输入数字") Integer age) {
  16. System.out.println("是啥啊4");
  17. return name;
  18. }
  19. @GetMapping("/test5")
  20. public String test5(@Validated @NotEmpty(message = "name不能为空") String name,
  21. @Valid @Max(value = 100, message = "请输入数字") Integer age) {
  22. System.out.println("是啥啊5");
  23. return name;
  24. }
  25. }
  1. /**
  2. * 【正面教材】类上加注解@Validated,下面的验证生效
  3. */
  4. @RestController
  5. @Validated
  6. public class Test2Controller {
  7. @GetMapping("/query")
  8. public String queryUserInfo(@NotEmpty(message = "name不能为空") String name,
  9. @Max(value = 100, message = "请输入数字") Integer age) {
  10. System.out.println("是啥啊");
  11. return name;
  12. }
  13. }

全局异常处理类

  1. /**
  2. * 参数校验异常包括MethodArgumentNotValidException、BindException、ConstraintViolationException
  3. */
  4. @RestControllerAdvice
  5. public class ExceptionHandler {
  6. @org.springframework.web.bind.annotation.ExceptionHandler(MethodArgumentNotValidException.class)
  7. public String handle(MethodArgumentNotValidException e) {
  8. e.printStackTrace();
  9. return e.getBindingResult().getFieldError().getDefaultMessage();
  10. }
  11. @org.springframework.web.bind.annotation.ExceptionHandler(BindException.class)
  12. public String handle(BindException e) {
  13. e.printStackTrace();
  14. return e.getBindingResult().getFieldError().getDefaultMessage();
  15. }
  16. @org.springframework.web.bind.annotation.ExceptionHandler(ConstraintViolationException.class)
  17. public String handle(ConstraintViolationException e) {
  18. e.printStackTrace();
  19. StringBuffer sb = new StringBuffer();
  20. for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
  21. sb.append(violation.getMessage());
  22. }
  23. return sb.toString();
  24. }
  25. }

进阶使用

分组校验

  • 约束注解上声明适用的分组信息groups
  1. @Data
  2. public class UserDTO {
  3. @Min(value = 10000000000000000L, groups = Update.class)
  4. private Long userId;
  5. @NotNull(groups = {Save.class, Update.class})
  6. @Length(min = 2, max = 10, groups = {Save.class, Update.class})
  7. private String userName;
  8. // 对于添加、修改都需要操作的公共属性,也可以不加groups标签,这时候,controller中入参需要写成@Validated({UserDTO.Update.class, Default.class}) 即可
  9. @NotNull(groups = {Save.class, Update.class})
  10. @Length(min = 6, max = 20, groups = {Save.class, Update.class})
  11. private String account;
  12. @NotNull(groups = {Save.class, Update.class})
  13. @Length(min = 6, max = 20, groups = {Save.class, Update.class})
  14. private String password;
  15. /**
  16. * 保存的时候校验分组
  17. */
  18. public interface Save {
  19. }
  20. /**
  21. * 更新的时候校验分组
  22. */
  23. public interface Update {
  24. }
  25. }
  • @Validated注解上指定校验分组
  1. @PostMapping("/save")
  2. public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
  3. // 校验通过,才会执行业务逻辑处理
  4. return Result.ok();
  5. }
  6. @PostMapping("/update")
  7. public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
  8. // 校验通过,才会执行业务逻辑处理
  9. return Result.ok();
  10. }

嵌套校验

前面的示例中,

  1. DTO

类里面的字段都是

  1. 基本数据类型

  1. String

类型。但是实际场景中,有可能某个字段也是一个对象,这种情况下,可以使用

  1. 嵌套校验

比如,上面保存

  1. User

信息的时候同时还带有

  1. Job

信息。需要注意的是,**此时

  1. DTO

类的对应字段必须标记

  1. @Valid

注解**。

  1. @Data
  2. public class UserDTO {
  3. @Min(value = 10000000000000000L, groups = Update.class)
  4. private Long userId;
  5. @NotNull(groups = {Save.class, Update.class})
  6. @Length(min = 2, max = 10, groups = {Save.class, Update.class})
  7. private String userName;
  8. @NotNull(groups = {Save.class, Update.class})
  9. @Length(min = 6, max = 20, groups = {Save.class, Update.class})
  10. private String account;
  11. @NotNull(groups = {Save.class, Update.class})
  12. @Length(min = 6, max = 20, groups = {Save.class, Update.class})
  13. private String password;
  14. // job属性上添加@Valid注解
  15. @NotNull(groups = {Save.class, Update.class})
  16. @Valid
  17. private Job job;
  18. @Data
  19. public static class Job {
  20. @Min(value = 1, groups = Update.class)
  21. private Long jobId;
  22. @NotNull(groups = {Save.class, Update.class})
  23. @Length(min = 2, max = 10, groups = {Save.class, Update.class})
  24. private String jobName;
  25. @NotNull(groups = {Save.class, Update.class})
  26. @Length(min = 2, max = 10, groups = {Save.class, Update.class})
  27. private String position;
  28. }
  29. public interface Save {
  30. }
  31. public interface Update {
  32. }
  33. }

自定义校验

  • 自定义注解
  1. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
  2. @Retention(RUNTIME)
  3. // 指明使用那个校验器(类) 去校验使用了此标注的元素。
  4. @Constraint(validatedBy = CaseUpperValidator.class)
  5. @Documented
  6. public @interface CaseUpper {
  7. String message() default "必须大写";
  8. Class<?>[] groups() default {};
  9. Class<? extends Payload>[] payload() default {};
  10. }
  • 实现ConstraintValidator接口编写约束校验器
  1. public class CaseUpperValidator implements ConstraintValidator<CaseUpper, String> {
  2. // 提示信息
  3. private String message;
  4. // 实现校验逻辑的方法。value为当前需要进行校验的值,context可以给约束验证器时提供上下文数据和操作,比如设置错误信息等。
  5. @Override
  6. public boolean isValid(String value, ConstraintValidatorContext context) {
  7. // 为空,返回失败
  8. boolean isValid = false;
  9. // value为空不校验;另外,value都是大写校验通过
  10. if (Objects.isNull(value) || value.equals(value.toUpperCase())) {
  11. isValid = true;
  12. }
  13. // 校验不通过,实现自定义错误信息
  14. if (!isValid) {
  15. //禁止默认消息返回
  16. context.disableDefaultConstraintViolation();
  17. //自定义返回消息
  18. context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
  19. }
  20. return isValid;
  21. }
  22. // caseUpper为当前校验注解的实例,可以获取当前注解的属性值
  23. @Override
  24. public void initialize(CaseUpper caseUpper) {
  25. this.message = caseUpper.message();
  26. }
  27. }
  • 使用注解@CaseUpper
  1. @Data
  2. @ToString
  3. public class User {
  4. private Integer id;
  5. @NotBlank(message = "姓名不能为空")
  6. private String name;
  7. @Max(value = 100, message = "最大不超过100")
  8. private Integer age;
  9. @NotBlank(message = "englishName不能为空")
  10. @CaseUpper(message = "englishName必须大写")
  11. private String englishName;
  12. }

参考链接:

SpringBoot 实现各种参数校验,非常实用!_枫哥和java的博客-CSDN博客

Spring Validation最佳实践及其实现原理,参数校验没那么简单! - 掘金

标签: spring boot java spring

本文转载自: https://blog.csdn.net/qq1929892209/article/details/126133350
版权归原作者 咸鱼爱吃青椒 所有, 如有侵权,请联系我们删除。

“Spring Boot中参数校验”的评论:

还没有评论