SpringBoot参数校验最全使用

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

<dependency>
     <groupId>org.hibernate.validator</groupId>
     <artifactId>hibernate-validator</artifactId>
     <version>6.0.8.Final</version>
 </dependency>

常用注解说明
在这里插入图片描述
单参数校验
controller类上必须添加@Validated注解,如下所示:

@RestController
@RequestMapping("/user")
@Validated // 需要添加的注解
public class UserController {
 // do something

方法参数前面加上注解即可

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

对象参数校验

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

public Result addUser(@RequestBody @Validated UserDto userDto) {
    // do something
}

public class UserDto {
  @NotBlank(message="名称不能为空")
  private String name;

  @NotNull(message="年龄不能为空")
  private Integer age;

  ……
}

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

public class UserDto {
    @NotNull
    private Long id;

    @NotBlank
    private String name;

    @NotNull
    private Integer age;

    @Valid
    private Phone phone;

    ……
}

public class Phone {
    @NotBlank
    private String type;

    @NotBlank
    private String phoneNum;
}

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

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

 public interface UpdateAction {
 }

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

public class UserDto {
    @NotNull(groups = {UpdateAction.class}, message = "id不能为空")
    private Long id;

    @NotBlank
    private String name;

    @NotNull
    private Integer age;

    ……
}

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

public Result addUser(@Validated UserDto userDto) {
  // do something
}

public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserDto userDto) {
  // do something
}

枚举校验

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

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

@Documented
@Constraint(validatedBy = EnumConstraintValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(EnumCheck.List.class)
public @interface EnumCheck {
    String message() default "{javax.validation.constraints.EnumCheck.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    /**
     * 枚举类
     *
     */
    Class<? extends EnumValidator> clazz();

    /**
     * 调用的方法名称
     */
    String method() default "getValue";

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        EnumCheck[] value();
    }
}

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

 public interface EnumValidator {
     Object getValue();
 }

3)自定义实现EnumConstraintValidator,需要实现ConstraintValidator接口,

public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> {
    /**
     * 注解对象
     */
    private EnumCheck annotation;

    /**
     * 初始化方法
     *
     * @param constraintAnnotation 注解对象
     */
    @Override
    public void initialize(EnumCheck constraintAnnotation) {
        this.annotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (Objects.isNull(value)) {
            return false;
        }

        Object[] enumConstants = annotation.clazz().getEnumConstants();
        try {
            Method method = annotation.clazz().getMethod(annotation.method());
            for (Object enumConstant : enumConstants) {
                if (value.equals(method.invoke(enumConstant))) {
                    return true;
                }
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        return false;
    }
}

4) 具体使用步骤如下:

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

public enum RefNodeType implements EnumValidator {
    PROJECT("project", "项目"),
    FIELD("field", "变量"),
    FUNC("func", "函数");

    private final String code;
    private final String label;

    RefNodeType(String code, String label) {
        this.code = code;
        this.label = label;
    }

    public String getCode() {
        return code;
    }

    public String getLabel() {
        return label;
    }

    @Override // 需要实现getValue方法
    public Object getValue() {
        return code;
    }
}

参数校验加上@EnumCheck枚举校验注解

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

正则通用校验

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

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

2)对象属性加上@Pattern注解

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

各类异常捕获处理

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

@RestControllerAdvice
public class GlobalExceptionHandler {
     /**
      * 全局异常处理
      */
     @ExceptionHandler(Exception.class)
     public Result handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
         // do something
     }
}

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

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

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

if (e instanceof ConstraintViolationException){
  Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
  Set<ConstraintViolation<?>> sets = ((ConstraintViolationException) e).getConstraintViolations();
  if(CollectionUtils.isNotEmpty(sets)){
    StringBuilder sb = new StringBuilder();
    sets.forEach(error -> {
                    if (error instanceof FieldError) {
                        sb.append(((FieldError)error).getField()).append(":");
                    }
                    sb.append(error.getMessage()).append(";");
                });
    String msg = sb.toString();
    msg = StringUtils.substring(msg, 0, msg.length() -1);
    result.setMessage(msg);
  }
  return result;
}

3)get请求的对象参数校验失败后抛出的异常是BindException

if (e instanceof BindException){
      Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
      List<ObjectError> errors = ((BindException) e).getBindingResult().getAllErrors();
      String msg = getValidExceptionMsg(errors);
      if (StringUtils.isNotBlank(msg)){
        result.setMessage(msg);
      }

      return result;
}

private String getValidExceptionMsg(List<ObjectError> errors) {
  if(CollectionUtils.isNotEmpty(errors)){
    StringBuilder sb = new StringBuilder();
    errors.forEach(error -> {
                    if (error instanceof FieldError) {
                       sb.append(((FieldError)error).getField()).append(":");
                    }
                    sb.append(error.getDefaultMessage()).append(";");
                });
    String msg = sb.toString();
    msg = StringUtils.substring(msg, 0, msg.length() -1);
    return msg;
  }
  return null;
}

4)post请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException

if (e instanceof MethodArgumentNotValidException){
      Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
      List<ObjectError> errors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
      String msg = getValidExceptionMsg(errors);
      if (StringUtils.isNotBlank(msg)){
        result.setMessage(msg);
      }

      return result;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值