7

SpringBoot Validation 表单验证

 2 years ago
source link: https://maxqiu.com/article/detail/117
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
SpringBoot Validation 表单验证
SpringBoot Validation 表单验证

2021/08/19  Java  Web  SpringBoot

示例代码:
GitHub:https://github.com/Max-Qiu/demo-SpringBoot
Gitee:https://gitee.com/Max-Qiu/demo-SpringBoot

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-validation</artifactId>
  4. </dependency>

启用验证注解

注解 说明 javax.validation.Valid JSR303标准的注解 org.springframework.validation.annotation.Validated JSR-303的变体。为支持SpringJSR-303

参考文档:@Validated和@Valid的区别?教你使用它完成Controller参数校验(含级联属性校验)以及原理分析【享学Spring】

常用约束条件注解

以下注解都在javax.validation.constraints包(JSR303)中,此外还有org.hibernate.validator.constraints包(Hibernate)中的@URL比较常用

注解 说明 被约束类型 是否可以为null @Null 必须为null 所有类型 必须为null @NotNull 不能为null 所有类型 不能为null @NotEmpty 不能为null CharSequence(字符串长度)
Collection(集合大小)
Map(Map大小)
Array(数组大小) 不能为null @Size 大小 同上 可以为null @NotBlank 至少包含一个非空白字符 CharSequence(字符串) 不能为null @AssertTrue@AssertFalse 只能为truefalse boolean、Boolean 可以为null @Max@Min 最大最小值
注解参数是long BigDecimal
BigInteger
byte、short、int、long、和各自的包装类
由于舍入错误,double和float不受支持 可以为null @DecimalMax@DecimalMin 最大最小值
注解参数是String
额外inclusive属性判断是否包含当前值 同上 可以为null @Positive 正数 同上 可以为null @PositiveOrZero 正数或0 同上 可以为null @Negative 负数 同上 可以为null @NegativeOrZero 负数或0 同上 可以为null @Digits 数值整数与小数的最大位数 同上 可以为null @Future 将来的时间 java.util.Date
java.util.Calendar
java.time.Instant
java.time.LocalDate
java.time.LocalDateTime
java.time.LocalTime
java.time.MonthDay
java.time.OffsetDateTime
java.time.OffsetTime
java.time.Year
java.time.YearMonth
java.time.ZonedDateTime
java.time.chrono.HijrahDate
java.time.chrono.JapaneseDate
java.time.chrono.MinguoDate
java.time.chrono.ThaiBuddhistDate 可以为null @FutureOrPresent 将来或现在的时间 同上 可以为null @Past 过去的时间 同上 可以为null @PastOrPresent 过去或现在的时间 同上 可以为null @Email 邮箱(不太好用,建议正则) String 可以为null @Pattern 正则 CharSequence(字符串) 不能为null @URL URL地址(不太好用,建议正则) String 可以为null
  1. 以上标注可以为null的(即null是合法值),若必须有值,需要再添加@NotNull注解
  2. 以上注解均可添加message属性用于自定义错误消息

普通参数校验

  1. 在字段上添加校验规则
  2. 在类上添加@Validated@Valid
  3. 在验证注解内使用message字段添加自定义的校验语句
  1. @RestController
  2. @Validated
  3. public class IndexController {
  4. /**
  5. * 例:校验邮箱与验证码
  6. */
  7. @GetMapping("code")
  8. public String code(@Email @NotBlank(message = "邮箱不能为空!") String email,
  9. @Size(min = 6, max = 6, message = "验证码为6位") @NotBlank String code) {
  10. // 邮箱和验证码正确性校验:略
  11. return email + "\t" + code;
  12. }
  13. }

实体对象校验

  1. 在实体中添加校验规则
  2. 在方法的参数添加@Validated@Valid
  1. @Data
  2. public class NormalVO {
  3. /**
  4. * 不能为null
  5. */
  6. @NotNull
  7. private Integer id;
  8. /**
  9. * 不为null或者空
  10. */
  11. @NotEmpty
  12. private String notEmpty;
  13. /**
  14. * 大小
  15. */
  16. @Size(min = 6, max = 6)
  17. private String size;
  18. /**
  19. * 至少有一个非空白字符串
  20. */
  21. @NotBlank
  22. private String notBlank;
  23. /**
  24. * 判断标识符
  25. */
  26. @AssertTrue
  27. // @AssertFalse
  28. private Boolean flag;
  29. /**
  30. * 最大和最小值
  31. */
  32. @Max(100)
  33. @Min(10)
  34. private Integer number;
  35. /**
  36. * 最大和最小值(inclusive可设置是否包含边界值)
  37. */
  38. @DecimalMax(value = "100", inclusive = false)
  39. @DecimalMin(value = "10", inclusive = true)
  40. private Integer decimal;
  41. /**
  42. * 正数
  43. */
  44. @Positive
  45. private Integer positive;
  46. /**
  47. * 负数
  48. */
  49. @Negative
  50. private Integer negative;
  51. /**
  52. * 整数与小数的最大长度
  53. */
  54. @Digits(integer = 5, fraction = 3)
  55. private BigDecimal digits;
  56. /**
  57. * 将来的时间
  58. */
  59. @Future
  60. // 使用指定格式接收数据
  61. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  62. private LocalDateTime future;
  63. /**
  64. * 过去的时间
  65. */
  66. @Past
  67. // 使用指定格式接收数据
  68. @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  69. private LocalDateTime past;
  70. /**
  71. * 邮箱
  72. */
  73. @Email
  74. private String email;
  75. /**
  76. * 正则
  77. *
  78. * 例:只能是数字和字母
  79. */
  80. @Pattern(regexp = "^[A-Za-z0-9]+$")
  81. private String pattern;
  82. /**
  83. * 是一个URL连接
  84. */
  85. @URL
  86. private String url;
  87. }
  1. @RestController
  2. @RequestMapping("vo")
  3. public class VoController {
  4. /**
  5. * 实体校验
  6. */
  7. @PostMapping("normal")
  8. public NormalVO normal(@Validated NormalVO vo) {
  9. return vo;
  10. }
  11. }

嵌套实体校验

  1. 在实体中添加校验规则
  2. 在方法的参数添加@Validated@Valid
  3. 在父实体的子实体属性上添加@Valid注解
  1. @RestController
  2. @RequestMapping("vo")
  3. public class VoController {
  4. /**
  5. * 嵌套实体验证
  6. */
  7. @PostMapping("nest")
  8. public UserVO nest(@Validated @RequestBody UserVO user) {
  9. return user;
  10. }
  11. }
  1. @Data
  2. public class UserVO {
  3. @NotNull
  4. private Integer id;
  5. @NotBlank
  6. private String name;
  7. @Valid
  8. @NotNull
  9. private AddressVO address;
  10. }
  1. @Data
  2. public class AddressVO {
  3. @NotBlank
  4. private String province;
  5. @NotBlank
  6. private String city;
  7. }

有时,需要对不同场景添加不同的校验规则(例如:新增和修改),此时可以使用SpringJSR-303分组功能

  1. 新增分组校验接口(空的接口,无需任何方法)
  2. 在方法的参数添加@Validated,并在注解内添加分组校验接口(可添加多个)
  3. 在实体的对应的字段的校验注解中添加groups属性并指定分组校验接口(可添加多个)
  4. 注:未添加分组校验接口的校验注解不会生效

示例如下:

  1. public interface AddValidGroup {}
  2. public interface UpdateValidGroup {}

指定校验分组

  1. @RestController
  2. @RequestMapping("group")
  3. public class GroupController {
  4. @PostMapping("add")
  5. public EmployeeVO add(@Validated(AddValidGroup.class) EmployeeVO vo) {
  6. return vo;
  7. }
  8. @PostMapping("update")
  9. public EmployeeVO update(@Validated(UpdateValidGroup.class) EmployeeVO vo) {
  10. return vo;
  11. }
  12. }

校验注解添加校验分组属性

  1. @Data
  2. public class EmployeeVO {
  3. /**
  4. * id
  5. */
  6. @Null(groups = AddValidGroup.class, message = "新增时ID必须为null")
  7. @NotNull(groups = UpdateValidGroup.class, message = "修改时员工ID不能为空")
  8. private Integer id;
  9. /**
  10. * 姓名
  11. */
  12. @NotBlank(groups = {AddValidGroup.class, UpdateValidGroup.class}, message = "姓名不能为空")
  13. private String name;
  14. /**
  15. * 手机号
  16. *
  17. * 例:若不加分组,则不进行校验
  18. */
  19. @NotBlank
  20. private String phone;
  21. }

自定义校验

有时,系统自带的校验器不能满足使用需求,此时可以自定义校验规则,完成校验

  1. 参考系统自带的校验注解,编写自定义的校验注解
  2. 编写校验注解对应的校验器
  3. 编写校验失败默认的提示语句
  4. 使用校验注解

以校验状态字段为例,示例如下:

自定义校验注解,可以参考JSR-303的校验注解

  1. /**
  2. * 值在指定的List中
  3. */
  4. @Documented
  5. @Constraint(validatedBy = {ListValueValidator.class})
  6. @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
  7. @Retention(RUNTIME)
  8. public @interface ListValue {
  9. String message() default "{com.maxqiu.demo.valid.constraints.ListValue.message}";
  10. Class<?>[] groups() default {};
  11. Class<? extends Payload>[] payload() default {};
  12. int[] valueList() default {};
  13. }

自定义校验注解对应的校验器,可以参考ConstraintValidator接口的实现类

  1. /**
  2. * ListValue校验器
  3. */
  4. public class ListValueValidator implements ConstraintValidator<ListValue, Integer> {
  5. private Set<Integer> set = new HashSet<>();
  6. /**
  7. * 初始化
  8. */
  9. @Override
  10. public void initialize(ListValue constraintAnnotation) {
  11. // 将注解中的合法值取出,放在set中
  12. for (int val : constraintAnnotation.valueList()) {
  13. set.add(val);
  14. }
  15. }
  16. /**
  17. * 判断是否校验成功
  18. *
  19. * @param value
  20. * 需要校验的值
  21. */
  22. @Override
  23. public boolean isValid(Integer value, ConstraintValidatorContext context) {
  24. return set.contains(value);
  25. }
  26. }

编写默认的校验失败提示语句

  1. 新建ValidationMessages.properties文件,放在项目的resources目录下
  2. 添加内容com.maxqiu.demo.valid.constraints.ListValue.message=\u5FC5\u987B\u63D0\u4EA4\u6307\u5B9A\u7684\u503C
    1. 内容的键就是自定义校验注解的message
    2. 内容的值就是提示的内容,直接写中文也可以,建议进行Unicode编码转换

使用注解(支持使用分组)

  1. @Data
  2. public class EmployeeVO {
  3. /**
  4. * id
  5. */
  6. @NotNull(groups = ChangeStatusValidGroup.class, message = "修改时员工ID不能为空")
  7. private Integer id;
  8. /**
  9. * 状态
  10. */
  11. @ListValue(valueList = {0, 1}, groups = ChangeStatusValidGroup.class)
  12. private Integer status;
  13. }
  1. @RestController
  2. @RequestMapping("custom")
  3. public class CustomController {
  4. /**
  5. * 修改状态
  6. */
  7. @PostMapping("status")
  8. public EmployeeVO status(@Validated(ChangeStatusValidGroup.class) EmployeeVO vo) {
  9. return vo;
  10. }
  11. }

校验异常处理

默认情况下,校验出错后的返回结果不能符合业务需求,所以需要自定义返回结果

局部异常处理

Spring提供了BindingResult用于接收校验异常结果,只需要在被校验的实体后面紧跟着一个BindingResult即可获取校验失败结果。示例如下:L

  1. @GetMapping("exception")
  2. public Object exception(@Validated NormalVO vo, BindingResult result) {
  3. if (result.hasErrors()) {
  4. Map<String, String> map = new HashMap<>();
  5. // 获取校验的错误结果并遍历
  6. result.getFieldErrors().forEach((item) -> {
  7. // 获取错误的属性的名字和错误提示
  8. map.put(item.getField(), item.getDefaultMessage());
  9. });
  10. return Result.error(500, map);
  11. }
  12. return vo;
  13. }

全局异常处理

  • Spring提供了ControllerAdviceExceptionHandler注解用于捕获Controller抛出的异常
  • 普通参数和实体参数校验异常不一样,需要分开处理
  • 局部异常处理覆盖全局异常处理(局部处理完成,全局这边捕获不到异常)

示例:处理全局异常,并返回json

  1. /**
  2. * 集中处理所有异常
  3. *
  4. * @author Max_Qiu
  5. */
  6. @Slf4j
  7. // @ResponseBody
  8. // @ControllerAdvice(basePackages = "com.maxqiu.demo.controller")
  9. @RestControllerAdvice(basePackages = "com.maxqiu.demo.controller")
  10. public class ExceptionControllerAdvice {
  11. /**
  12. * 处理方法的普通参数校验异常
  13. */
  14. @ExceptionHandler(value = ConstraintViolationException.class)
  15. public Result handleValidException(ConstraintViolationException e) {
  16. Map<String, String> errorMap = new HashMap<>();
  17. for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
  18. String field = "";
  19. for (Path.Node node : constraintViolation.getPropertyPath()) {
  20. field = node.getName();
  21. }
  22. errorMap.put(field, constraintViolation.getMessage());
  23. }
  24. return Result.error(500, errorMap);
  25. }
  26. /**
  27. * 处理方法的实体参数校验异常
  28. */
  29. @ExceptionHandler(value = BindException.class)
  30. public Result handleValidException(BindException e) {
  31. Map<String, String> errorMap = new HashMap<>();
  32. e.getBindingResult().getFieldErrors().forEach(r -> errorMap.put(r.getField(), r.getDefaultMessage()));
  33. return Result.error(500, errorMap);
  34. }
  35. @ExceptionHandler(value = Throwable.class)
  36. public Result handleException(Throwable throwable) {
  37. log.error("其他异常:{}\n异常类型:{}", throwable.getMessage(), throwable.getClass());
  38. return Result.error();
  39. }
  40. }

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK