Spring参数校验 | 小册免费学

674 阅读5分钟

写一个接口,大概的几个步骤:

  • 参数校验
  • 编写Service、Dao(SQL)
  • Result统一结果封装返回值
  • 如果是分布式,还可能涉及网关配置、服务引用等

参数校验归根结底就两种:手动校验、自动校验

实际生活中常用的两种:

封装ValidationUtils

使用Spring 自动的 Validation

对于上面两种方式,Spring都提供了解决方案。 除了Spring 的 Validation,Spring还提供了Assert

Assert:

  • hasText 存在字符串 不存在字符串则报错
  •     notNull 不为空 为空则报错
    
  •     isTrue 是否为真 假则报错
      
    

当不满足条件时会爆出illegalArgumentException,所以使用Assert时全局异常处理非法参数异常即可。

如果对检验的自由度偏高的话,可以自己封装一个ValidationUtils

封装ValidationUtils

封装校验工具类两种思路:

  • 检验并返回结果,调用者自行处理
  • 检验失败直接抛异常

方式一:检验并返回结果,调用者自行处理

1.方法只返回true/false 2.调用者根据返回值自行处理,抛异常或者用Result封装

/ 检验方法只返回true/false @param id @return / public static final Boolean isNotId(Long id){ if (id == null){ return true; } return id < 0; } / 调用者根据返回值自行处理 / public void method(){ Long id = -1L; ValidationUtilsV1.isNotId(id); }

其实这种方式跟不封装区别不大,只是把判断逻辑放到检验工具类中而已

方式二:检验失败直接抛异常

一般结合@RestControllerAdvice进行全局异常处理:

public class ValidationUtilsV3 {

public static final String NULL_FORMAT = "%s不能为空";
public static final String LESS_THAN_ZERO = "%s不能小于0";

public static final void isNotNull(Object o,String filedName){
    if (o == null){
        throw new ValidationException(String.format(NULL_FORMAT,filedName));
    }
}
public static final void isLessThan0(Integer num,String filedName){
    isNotNull(num,filedName);
    if (num < 0){
        throw new ValidationException(String.format(LESS_THAN_ZERO,filedName));
    }
}

}

@Slf4j @RestControllerAdvice public class GlobalExceptionHandler {

/**
 * ValidationException 异常处理
 */
public <T> T handleValidationExcepion(ValidationException e){
    // 打印精准的错误日志,方便后端排查
    log.warn("校验参数异常:{}",e.getMessage(),e);
    return (T) "校验参数异常";
}

}

Spring Validation

Spring基于注解的检验逻辑常用的有:

  • @Validated
  • @NotNull
  • @NotBlank
  • @NotEmpty
  • @Positive
  • @Length
  • @Max
  • @Min

JSR303的@Valid和Spring的@Validated有什么关系呢?

Spring的@Validated是基于@Valid扩展的,所以相对来说Spring的@Validated功能更强大,但在嵌套校验中@Valid却能够支持。

SpringBoot2.3.x之前可以直接使用@Validated及@Valid,SpringBoot2.3.x以后需要额外引入依赖:

org.hibernate hibernate-validator 6.0.1.Final

GET散装参数校验:校验不满足条件抛出ConstraintViolationException违反约束异常。

1.如果get的参数不多,可以使用散装参数校验,需要在校验方法的类上加@Validated注解

2.因为当检验不满足条件时会爆出异常,所以我们可以在全局异常处理捕获该异常,并返回给前台友好的提示信息。在Spring2.3.X以前,如果没有捕获异常会返回Spring的默认异常JSON。 服务端则抛出ConstraintViolationException

Get DTO参数校验:校验不满足条件时抛出BindException绑定异常

1.使用Get Dto参数校验时,在Dto类中用检验参数检验之外,还需要在检验的Dto前加上@Validated注解。

2.检验不满足条件时,此时抛出的异常为BindException,如果后端全局异常处理器为处理该异常,SpringBoot将会自行处理成异常Json信息给前端。所以我们可以在全局异常处理器中进行处理。

Post参数校验:MethodArgumentNoValidException方法参数不校验异常

1.如果前端传来Json字符串时,需要使用@RequestBody转换一下,然后在需要校验的参数前面加上@Validated注解。

2.检验不满足条件时,此时抛出的异常为MethodArgumentNoValidException

其他场景校验

  • 嵌套校验
  • 分组校验
  • List校验
  • 嵌套校验 @Validated不支持嵌套校验,@Valid支持

@Data public class User {

@NotNull(message = "id不能为空")
private Long id;

@NotNull(message = "age不能为空")
@Max(value = 30,message = "程序员年龄最大不能超过30岁")
@Min(value = 16,message = "程序员年龄最小不能低于16岁")
private Integer age;
@NotNull(message = "部门不能为空")
@Valid
private Department department;

} class Department{

@NotNull(message = "id不能为空")
Long id;

}

1.需要在嵌套校验的类对象前面加上JSR303的@Valid

2.嵌套校验不满足条件时会抛出 IllegalStateException

分组校验

1.首先在设置好检验参数的的分组,在检验注解的group注解附上分组的值(值通常为接口的类对象),其次在校验时指定检验的分组,即指定@Validated(Add.class)等。

注意: interface Add这些接口只是用来作为标记,可以抽取出来复用。

这些接口都可以基础Default

继承Default后,除非显示指定,否则只要加了@NotNull等注解,就默认是此分组,但显示指定Group后,就会按照指定的分组进行校验。

不建议使用Default,理解混乱

List校验

@Validated不支持此种校验,可以借鉴@Valid嵌套校验的方式,可看做特殊的嵌套校验。 可以将List在包一层,在内部创建List的对象。实现List 接口

实际开发中可以专门创建一个包存放Spring Validated 的类

Spring validatorUtils封装: 工具类一般定义成final,构造器私有化 public final class SpringValidatorUtils {

private SpringValidatorUtils(){}
public static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
public static final <T> void valid(T param,Class<?> ... groupClass){
    Set<ConstraintViolation<T>> validateSet = VALIDATOR.validate(param, groupClass);
    if (CollectionUtils.isEmpty(validateSet)){
        return;
    }
    String errorMsg = validateSet.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("&"));
    throw new ValidationException(errorMsg);
}

}

代码其实很简单,做的事情其实和@Validated一样。@Validated通过注解的方式让Spring使用Validator去帮我们校验,异常由Spring抛出(多种异常),而工具类则是我们从Spring那里借来的Validator自己校验,抛出的异常可以为自己自定义的异常。

更灵活的Spring ValidatorUtils封装

调用者自己决定抛出异常还是返回错误信息。 public final class SpringValidatorAndReturnUtils {

private SpringValidatorAndReturnUtils(){}
public static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
/**
 * 校验基于注解校验的对象
 * @param param 被校验对象
 * @param isThrow 是否抛出异常
 * @param <T>
 */
public static final <T> String validReq(T param,Boolean isThrow){
    if (param == null){
        return exceptionHandle("检验对象不能为空",isThrow);
    }
    Set<ConstraintViolation<T>> validateSet = VALIDATOR.validate(param);
    ConstraintViolation<T> first = Iterables.getFirst(validateSet, null);
    if (CollectionUtils.isEmpty(validateSet)){
        return "";
    }
    String errorMsg = validateSet.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("&"));
    return exceptionHandle(errorMsg,isThrow);
}

private static String exceptionHandle(String message, Boolean isThrow) {
    if (isThrow){
        throw new ValidationException(message);
    }
    return message;
}

}

为什么Spring 的@Validated这么方便还要封装工具类呢?

如果想在Service层做校验,使用SpringValidatorUtils会方便些,Service有接口和实现类;当然Service也能用注解方式校验。

工具类,想校验什么就写什么,不用考虑杂七杂八的分组,而Spring Validation更多的是关心分组。

本文正在参与「掘金小册免费学啦!」活动, 点击查看活动详情