乐趣区

关于springboot:Spring-Boot参数校验以及分组校验的使用

简介:做 web 开发基本上每个接口都要对参数进行校验,如果参数比拟少,还比拟容易解决,一但参数比拟多了的话代码中就会呈现大量的 if-else 语句。尽管这种形式简略间接,但会大大降低开发效率和代码可读性。所以咱们能够应用 validator 组件来代替咱们进行不必要的 coding 操作。本文将基于 validator 的介绍材料,同时联合作者本人在我的项目中的理论应用教训进行了总结。

作者 | 江岩
起源 | 阿里技术公众号

一 前言

做 web 开发有一点很烦人就是要对前端输出参数进行校验,基本上每个接口都要对参数进行校验,比方一些非空校验、格局校验等。如果参数比拟少的话还是容易解决的一但参数比拟多了的话代码中就会呈现大量的 if-else 语句。

应用这种形式尽管简略间接,然而也有不好的中央,一是升高了开发效率,因为咱们须要校验的参数会存在很多中央,并且不同中央会有反复校验,其次升高了代码可读性,因为在业务代码中掺杂了太多额定工作的代码。

所以咱们能够应用 validator 组件来代替咱们进行不必要的 coding 操作。本文基于 validator 的介绍材料,也联合本人在我的项目中的理论应用教训进行了总结,心愿能帮到大家。

1 什么是 validator

Bean Validation 是 Java 定义的一套基于注解的数据校验标准,目前曾经从 JSR 303 的 1.0 版本升级到 JSR 349 的 1.1 版本,再到 JSR 380 的 2.0 版本(2.0 实现于 2017.08),曾经经验了三个版本。须要留神的是,JSR 只是一项规范,它规定了一些校验注解的标准,但没有实现,比方 @Null、@NotNull、@Pattern 等,它们位于 javax.validation.constraints 这个包下。而 hibernate validator 是对这个标准的实现,并减少了一些其余校验注解,如 @NotBlank、@NotEmpty、@Length 等,它们位于 org.hibernate.validator.constraints 这个包下。

如果咱们的我的项目应用了 Spring Boot,hibernate validator 框架曾经集成在 spring-boot-starter-web 中,所以无需再增加其余依赖。如果不是 Spring Boot 我的项目,须要增加如下依赖。

二 注解介绍

1 validator 内置注解

hibernate validator 中扩大定义了如下注解:

三 应用

应用起来比较简单,都是应用注解形式应用。具体来说分为单参数校验、对象参数校验,单参数校验就是 controller 接口依照单参数接管前端传值,没有封装对象进行接管,如果有封装对象那就是对象参数校验。

1 单参数校验

单参数校验只须要在参数前增加注解即可,如下所示:

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

但有一点须要留神,如果应用单参数校验,controller 类上必须增加

@Validated 注解,如下所示:@RestController
@RequestMapping("/user")
@Validated // 单参数校验须要加的注解
public class UserController {// do something}

2 对象参数校验

对象参数校验应用时,须要先在对象的校验属性上增加注解,而后在 Controller 办法的对象参数前增加 @Validated 注解,如下所示:

public Result addUser(@Validated UserAO userAo) {// do something}

public class UserAO {
  @NotBlank
  private String name;

  @NotNull
  private Integer age;
  
  ……
}

注解分组

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

1)组件有个默认分组 Default.class, 所以咱们能够再创立一个分组 UpdateAction.class,如下所示:

public interface UpdateAction {}

2)在参数类中须要校验的属性上,在注解中增加 groups 属性:

public class UserAO {@NotNull(groups = UpdateAction.class, message = "id 不能为空")
    private Long id;
    
    @NotBlank
    private String name;

    @NotNull
    private Integer age;
    
    ……
}

如上所示,就示意只在 UpdateAction 分组下校验 id 字段,在默认状况下就会校验 name 字段和 age 字段。

而后在 controller 的办法中,在 @Validated 注解里指定哪种场景即可,没有指定就代表采纳 Default.class,采纳其余分组就须要显示指定。如下代码便示意在 addUser()接口中依照默认状况进行参数校验,在 updateUser()接口中依照默认状况和 UpdateAction 分组对参数进行独特校验。

public Result addUser(@Validated UserAO userAo) {// do something}
public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserAO userAo) {// do something}

对象嵌套

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

public class UserAO {@NotNull(groups = UpdateAction.class, message = "id 不能为空")
    private Long id;
    
    @NotBlank
    private String name;

    @NotNull
    private Integer age;
    
    @Valid
    private Phone phone;
    
    ……
}

public class Phone {
    @NotBlank
    private String operatorType;
    
    @NotBlank
    private String phoneNum;
}

3 谬误音讯的捕捉

参数校验失败后会抛出异样,咱们只须要在全局异样解决类中捕捉参数校验的失败异样,而后将谬误音讯增加到返回值中即可。捕捉异样的办法如下所示,返回值 Result 是咱们零碎自定义的返回值类。

@RestControllerAdvice(basePackages= {"com.alibaba.dc.controller","com.alibaba.dc.service"})
public class GlobalExceptionHandler {@ExceptionHandler(value = {Throwable.class})
  Result handleException(Throwable e, HttpServletRequest request){// 异样解决}
}

须要留神的是,如果短少参数抛出的异样是 MissingServletRequestParameterException,单参数校验失败后抛出的异样是 ConstraintViolationException,get 申请的对象参数校验失败后抛出的异样是 BindException,post 申请的对象参数校验失败后抛出的异样是 MethodArgumentNotValidException,不同异样对象的构造不同,对异样音讯的提取形式也就不同。如下图所示:

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)BindException 异样

if (e instanceof BindException){
      // get 申请的对象参数校验异样
      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)MethodArgumentNotValidException 异样

if (e instanceof MethodArgumentNotValidException){
      // post 申请的对象参数校验异样
      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;
}

原文链接
本文为阿里云原创内容,未经容许不得转载。

退出移动版