SpringBoot参数校验最全使用

引入maven依赖(可选)

如果我们的项目使用了Spring Boot,hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,则需要添加如下依赖:

1 <dependency>
2     <groupId>org.hibernate.validator</groupId>
3     <artifactId>hibernate-validator</artifactId>
4     <version>6.0.8.Final</version>
5 </dependency>

 

常用注解介绍

注解 作用类型 来源 说明
@Null 任何类型   属性必须为null
@NotNull 任何类型   属性不能为null
@NotEmpty 集合 hibernate validator扩展注解 集合不能为null,且size大于0
@NotBlank 字符串、字符 hibernate validator扩展注解 字符类不能为null,且去掉空格之后长度大于0
@AssertTrue Boolean、boolean   布尔属性必须是true
@Min 数字类型(原子和包装)   限定数字的最小值(整型)
@Max 同@Min   限定数字的最大值(整型)
@DecimalMin 同@Min   限定数字的最小值(字符串,可以是小数)
@DecimalMax 同@Min   限定数字的最大值(字符串,可以是小数)
@Range 数字类型(原子和包装) hibernate validator扩展注解 限定数字范围(长整型)
@Length(min=,max=) 字符串 hibernate validator扩展注解 限定字符串长度
@Size 集合   限定集合大小
@Past 时间、日期   必须是一个过去的时间或日期
@Future 时期、时间   必须是一个未来的时间或日期
@Email 字符串 hibernate validator扩展注解 必须是一个邮箱格式
@Pattern 字符串、字符   正则匹配字符串

 

单参数校验

controller类上必须添加@Validated注解,如下所示:

 

1 @RestController
2 @RequestMapping("/user")
3 @Validated // 需要添加的注解
4 public class UserController {
5   // do something
6 }

 

方法参数前面加上注解即可, 如下所示:

1 public Result deleteUser(@NotNull(message = "id不能为空") Long id) {
2   // do something
3 }

 

对象参数校验

对象参数校验使用时,需要先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Validated注解,如下所示:

 1 public Result addUser(@RequestBody @Validated UserDto userDto) {
 2     // do something
 3 }
 4 
 5 public class UserDto {
 6   @NotBlank(message="名称不能为空")
 7   private String name;
 8 
 9   @NotNull(message="年龄不能为空")
10   private Integer age;
11   
12   ……
13 }

 

嵌套对象校验

如果需要校验的参数对象中还嵌套有一个对象属性,而该嵌套的对象属性也需要校验,那么就需要在该对象属性上增加@Valid注解。

 1 public class UserDto {
 2     @NotNull
 3     private Long id;
 4     
 5     @NotBlank
 6     private String name;
 7 
 8     @NotNull
 9     private Integer age;
10     
11     @Valid
12     private Phone phone;
13     
14     ……
15 }
16 
17 public class Phone {
18     @NotBlank
19     private String type;
20     
21     @NotBlank
22     private String phoneNum;
23 }

 

分组校验

在对象参数校验场景下,有一种特殊场景,同一个参数对象在不同的场景下有不同的校验规则。比如,在创建对象时不需要传入id字段(id字段是主键,由系统生成,不由用户指定),但是在修改对象时就必须要传入id字段。在这样的场景下就需要对注解进行分组。

1)组件默认有个分组Default.class, 所以我们可以再创建一个分组例如UpdateAction.class,如下所示:

1 public interface UpdateAction {
2 }

2)在参数类中需要校验的属性上,在注解中添加groups属性(表示Default.class默认情况下会校验name,age参数,而在UpdateAction.class情况下会校验id参数),如下所示:

 1 public class UserDto {
 2     @NotNull(groups = {UpdateAction.class}, message = "id不能为空")
 3     private Long id;
 4     
 5     @NotBlank
 6     private String name;
 7 
 8     @NotNull
 9     private Integer age;
10     
11     ……
12 }

 3)在controller的方法中,在@Validated注解里指定哪种场景即可(没有指定就代表采用Default.class,采用其他分组则需要显示指定)。如下代码便表示在addUser()接口中按照默认情况进行参数校验,在updateUser()接口中按照默认情况和UpdateAction分组对参数进行共同校验。

1 public Result addUser(@Validated UserDto userDto) {
2   // do something
3 }
4 
5 public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserDto userDto) {
6   // do something
7 }

 

枚举校验

枚举校验hibernate validator并不支持。需要自己扩展实现。

1) 定义EnumChck注解,如下所示:

 1 @Documented
 2 @Constraint(validatedBy = EnumConstraintValidator.class)
 3 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
 4 @Retention(RUNTIME)
 5 @Repeatable(EnumCheck.List.class)
 6 public @interface EnumCheck {
 7     String message() default "{javax.validation.constraints.EnumCheck.message}";
 8 
 9     Class<?>[] groups() default { };
10 
11     Class<? extends Payload>[] payload() default { };
12 
13     /**
14      * 枚举类
15      *
16      */
17     Class<? extends EnumValidator> clazz();
18 
19     /**
20      * 调用的方法名称
21      */
22     String method() default "getValue";
23 
24     @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
25     @Retention(RUNTIME)
26     @Documented
27     @interface List {
28         EnumCheck[] value();
29     }
30 }

2) 定义EnumValidator接口,让需要校验的枚举类实现其接口的getValue()方法,如下所示:

1 public interface EnumValidator {
2     Object getValue();
3 }

3)自定义实现EnumConstraintValidator,需要实现ConstraintValidator接口,如下所示:

 1 public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> {
 2     /**
 3      * 注解对象
 4      */
 5     private EnumCheck annotation;
 6 
 7     /**
 8      * 初始化方法
 9      *
10      * @param constraintAnnotation 注解对象
11      */
12     @Override
13     public void initialize(EnumCheck constraintAnnotation) {
14         this.annotation = constraintAnnotation;
15     }
16 
17     @Override
18     public boolean isValid(Object value, ConstraintValidatorContext context) {
19         if (Objects.isNull(value)) {
20             return false;
21         }
22 
23         Object[] enumConstants = annotation.clazz().getEnumConstants();
24         try {
25             Method method = annotation.clazz().getMethod(annotation.method());
26             for (Object enumConstant : enumConstants) {
27                 if (value.equals(method.invoke(enumConstant))) {
28                     return true;
29                 }
30             }
31         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
32             throw new RuntimeException(e);
33         }
34 
35         return false;
36     }
37 }

4) 具体使用步骤如下:

具体枚举类实现上面定义的EnumValidator接口:

 1 public enum RefNodeType implements EnumValidator {
 2     PROJECT("project", "项目"),
 3     FIELD("field", "变量"),
 4     FUNC("func", "函数");
 5 
 6     private final String code;
 7     private final String label;
 8 
 9     RefNodeType(String code, String label) {
10         this.code = code;
11         this.label = label;
12     }
13 
14     public String getCode() {
15         return code;
16     }
17 
18     public String getLabel() {
19         return label;
20     }
21 
22     @Override // 需要实现getValue方法
23     public Object getValue() {
24         return code;
25     }
26 }

参数校验加上@EnumCheck枚举校验注解,如下所示:

1 public class TestDto {
2   @NotBlank(message = "变量类型不能为空")
3   @EnumCheck(clazz = RefNodeType.class, message = "变量类型不合法") // 加上枚举校验注解
4   private String sourceType;
5 }

 

正则通用校验

1)定义RegRule接口,将需要用到的正则表达式定义为接口属性,如下所示:

1 public interface RegRule {
2     String MOBILE = "^(((13[0-9])|(14[579])|(15([0-3]|[5-9]))|(16[6])|(17[0135678])|(18[0-9])|(19[89]))\\d{8})$";
3 }

2)对象属性加上@Pattern注解,如下所示:

1 public class UserDto {
2   @Pattern(regexp = RegRule.MOBILE, message = "手机号格式不正确")
3   private String mobile;
4 }

 

 各类异常捕获处理

参数校验失败后会抛出异常,我们只需要在全局异常处理类中捕获参数校验的失败异常,然后将错误消息添加到返回值中即可。捕获异常的方法如下所示,返回值Result是我们系统自定义的返回值类。

 1 @RestControllerAdvice
 2 public class GlobalExceptionHandler {
 3      /**
 4       * 全局异常处理
 5       */
 6      @ExceptionHandler(Exception.class)
 7      public Result handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
 8          // do something
 9      }
10 }

1)缺少参数抛出的异常是MissingServletRequestParameterException,如下所示:

1 if (e instanceof MissingServletRequestParameterException){
2     Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
3     String msg = MessageFormat.format("缺少参数{0}", ((MissingServletRequestParameterException) e).getParameterName());
4     result.setMessage(msg);
5     return result;
6 }

2)单参数校验失败后抛出的异常是ConstraintViolationException,如下所示:

 1 if (e instanceof ConstraintViolationException){
 2   Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
 3   Set<ConstraintViolation<?>> sets = ((ConstraintViolationException) e).getConstraintViolations();
 4   if(CollectionUtils.isNotEmpty(sets)){
 5     StringBuilder sb = new StringBuilder();
 6     sets.forEach(error -> {
 7                     if (error instanceof FieldError) {
 8                         sb.append(((FieldError)error).getField()).append(":");
 9                     }
10                     sb.append(error.getMessage()).append(";");
11                 });
12     String msg = sb.toString();
13     msg = StringUtils.substring(msg, 0, msg.length() -1);
14     result.setMessage(msg);
15   }
16   return result;
17 }

3)get请求的对象参数校验失败后抛出的异常是BindException,如下所示:

 1 if (e instanceof BindException){
 2       Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
 3       List<ObjectError> errors = ((BindException) e).getBindingResult().getAllErrors();
 4       String msg = getValidExceptionMsg(errors);
 5       if (StringUtils.isNotBlank(msg)){
 6         result.setMessage(msg);
 7       }
 8       
 9       return result;
10 }
11 
12 private String getValidExceptionMsg(List<ObjectError> errors) {
13   if(CollectionUtils.isNotEmpty(errors)){
14     StringBuilder sb = new StringBuilder();
15     errors.forEach(error -> {
16                     if (error instanceof FieldError) {
17                        sb.append(((FieldError)error).getField()).append(":");
18                     }
19                     sb.append(error.getDefaultMessage()).append(";");
20                 });
21     String msg = sb.toString();
22     msg = StringUtils.substring(msg, 0, msg.length() -1);
23     return msg;
24   }
25   return null;
26 }

4)post请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException,如下所示:

 1 if (e instanceof MethodArgumentNotValidException){
 2       Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
 3       List<ObjectError> errors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
 4       String msg = getValidExceptionMsg(errors);
 5       if (StringUtils.isNotBlank(msg)){
 6         result.setMessage(msg);
 7       }
 8       
 9       return result;
10 }

 

 

 

 

posted @ 2021-10-19 18:43  冰狼爱魔  阅读(5596)  评论(3编辑  收藏  举报