• SpringMvc数据校验@Valid等注解的使用与工具类抽取


    最近在重构老项目的代码,发现校验入参占用了很多代码,之前我对这一块的认识局限于使用StringUtils等工具来多个if块进行判断,代码是没什么问题,但是总写这些令人生烦,毕竟写代码也要讲究优雅的嘛,于是呢我就研究了一下JavaEE Api 上的校验类,基本上推翻了我之前对校验注解之类的认识,在这里记录一下所得。

    1. @NotNull @NotBlank @NotEmpty 这三个注解的区别以及使用

      @NotNull:校验入参不能为空,无法正确校检长度为0的字符串或以完全为空格的字符串

      @NotBlank: 包含@NotNull的功能,可以校验字符串内容是否为空

      @NotEmpty: 校验传入集合是否为空

      当以上几个注解校验不被满足的时候,就会抛出异常,打印出默认的消息内容,

      age not be null这样的错误信息

      想自定义错误信息可以如:@NotNull(message = "信息不能为空")来定义

    2. @Valid的使用

      如果入参为一个对象,想校验这个对象内容是否正确的时候,会用到1节中所讲的几个注解,

      但是此时会有一个问题:如果不使用@Valid注解,1节中的注解在入参过程中是不生效的,只有保存的时候才会被校验(不排除PO和DTO是同一个对象的情况)

      当然,这有一个大前提,就是没有其它对入参校验的方法执行。

      **所以如果是简单为了让异常报出来而不是直接返回的时候可以只用@Valid

    3. 自定义校验与错误信息的处理

      (1)使用BindingResult对象接收被@Valid校验不通过的错误信息

          /**
           * 使用BindingResult与@Valid配合的方式
           * 拿到错误信息,带部分堆栈
           */
          @PostMapping("/validationTest6")
          public String validationTest6(@RequestBody @Valid User user, 
                                        BindingResult result){
              if(result.hasErrors()){
                  //取一条错误信息
                  ObjectError next = result.getAllErrors().iterator().next();
                  log.error("error={}", next);
                  //后边可以自己返回错误信息也可以自定义
                  return next.toString();
              }
              //do somethings
              return "校验通过";
          }
      
          /**
           * 使用BindingResult与@Valid配合的方式
           * 只拿到错误信息
           */
          @PostMapping("/validationTest7")
          public String validationTest7(@RequestBody @Valid User user, 
                                        BindingResult result){
              if(result.hasErrors()){
                  //取一条错误信息
                  ObjectError next = result.getAllErrors().iterator().next();
                  String defaultMessage = next.getDefaultMessage();
                  log.error("error={}", defaultMessage);
                  //后边可以自己返回错误信息也可以自定义
                  return defaultMessage;
              }
              //do somethings
              return "校验通过";
          }
      

      (2)使用Validator对象,不信赖于@Valid的实现

          /**
           * 使用Validator未抽取工具类时的实现
           */
          @PostMapping("/validationTest9")
          public String validationTest9(@RequestBody User user){
              ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
              Validator validator = factory.getValidator();
              Set<ConstraintViolation<User>> validate = validator.validate(user);
              if(!validate.isEmpty()){
                  ConstraintViolation<User> next = validate.iterator().next();
                  String message = next.getMessage();
                  log.error("error={}",message);
                  return message;
              }
              //do somethings
              return "校验通过";
          }
      

      上边给大家介绍了两种方法,其中第二种看起来代码更多一些,其实这应该就是没有使用@Valid的问题了,因为你并不知道@Valid注解校验错误代码有多少。效率基本差不多少

      下边为大家提供一个工具类,分别提供了上边两种方式的封装,如果只想使用BindingResult方式,那么大可以去除用不到的代码

    4. 封装的工具类与使用

      (1)工具类代码

      package com.cnblogs.hellxz.myutils;
      
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.ObjectError;
      
      import javax.validation.ConstraintViolation;
      import javax.validation.Validation;
      import javax.validation.Validator;
      import javax.validation.ValidatorFactory;
      import java.util.Set;
      
      /**
       * <b>类名</b>: ValidatorUtils
       * <p><b>描    述</b> 校验入参 </p>
       *
       * <p><b>创建日期</b>: 8/24/18 12:54 PM </p>
       *
       * @author HELLXZ 张
       * @version 1.0
       * @since jdk 1.8
       */
      public class ValidatorUtils {
      
          private static final Logger log = LoggerFactory.getLogger(ValidatorUtils.class);
          private static final Validator validator;
      
          static {
              ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
              validator = factory.getValidator();
          }
      
          /**
           * 分组进行校验或完整校验
           * 区别在于是否传groups类
           * ps:分组的好处在于同一个DTO分别给不同的方法指定不同的校验参数时候非常有用
           *      也就不用再去重写一个新的相同字段的DTO指定不同校验字段的操作,更灵活
           * @param object 被校验的DTO对象
           * @param groups 分组类,可以是接口也可以是类,仅作为标识
           * @return 想要返回的结果
           */
          public static <T> String validEntity(final T object, Class... groups) {
              Set<ConstraintViolation<T>> violations = validator.validate(object, groups);
              if (!violations.isEmpty()) {
                  //这里只取第一条错误,防止返回参数过多
                  ConstraintViolation<T> violation = violations.iterator().next();
                  log.error(violation.getMessage());
                  //下面的代码可以使用公司或个人习惯的返回工具类也可以
                  return "{"code":"400","msg":"" + violation.getMessage() + ""}";
              }
              return null;
          }
      
          /**
           * 校验入参对象的某个属性是否满足要求,跳过其它校验, 支持分组
           * @param object 被校验的DTO对象
           * @param propertyName DTO对象内的属性名
           * @param groups 分组名
           * @return 校验出错返回的结果
           */
          public static <T> String validProperty(final T object, String propertyName, 
                                                 Class... groups) {
              Set<ConstraintViolation<T>> violations = validator.validateProperty(object, propertyName, groups);
              if (!violations.isEmpty()) {
                  //这里只取第一条错误,防止返回参数过多
                  ConstraintViolation<T> violation = violations.iterator().next();
                  log.error(violation.getMessage());
                  //下面的代码可以使用公司或个人习惯的返回工具类也可以
                  return "{"code":"400","msg":"" + 
                      violation.getMessage() + ""}";
              }
              return null;
          }
      
          /**
           * 使用BindingResult与@Valid注解一起使用实现的工具类
           * @param result BindingResult对象
           * @return 结果串
           */
          public static String validEntity(BindingResult result){
              if(result.hasErrors()){
                  //取一条错误信息
                  ObjectError next = result.getAllErrors().iterator().next();
                  String defaultMessage = next.getDefaultMessage();
                  log.error("error={}", defaultMessage);
                  //后边可以自己返回错误信息也可以自定义
                  return "{"code":"400","msg":"" + defaultMessage + ""}";
              }
              return null;
          }
      }
      

      (2) 工具类的使用

          private static Logger log = 
              LoggerFactory.getLogger(ValidateController.class);
      
          /**
           * 检查User对象注解在A组内的校验
           */
          @PostMapping("/validationTest1")
          public String validationTest1(@RequestBody User user){
              String valid = ValidatorUtils.validEntity(user, A.class);
              if(null != valid) return valid;
              //do somethings
              return "校验通过";
          }
      
          /**
           * 检查User对象注解在B组内的校验
           */
          @PostMapping("/validationTest2")
          public String validationTest2(@RequestBody User user){
              String valid = ValidatorUtils.validEntity(user, B.class);
              if(null != valid) return valid;
              //do somethings
              return "校验通过";
          }
      
          /**
           * 检查User对象注解在A组和B组内的校验
           * ps: 是A组内校验加上B组内校验,不是校验同时在两个组!
           */
          @PostMapping("/validationTest3")
          public String validationTest3(@RequestBody User user){
              String valid = ValidatorUtils.validEntity(user, A.class, B.class);
              if(null != valid) return valid;
              //do somethings
              return "校验通过";
          }
      
          /**
           * 校验入参对象中指定字段
           * 其中分组可以不传,如果传的话,请注意该字段必须在该组下,否则不会被校验
           */
          @PostMapping("/validationTest4")
          public String validationTest4(@RequestBody User user){
              String valid = ValidatorUtils.validProperty(user,"age", B.class);
              if(null != valid) return valid;
              //do somethings
              return "校验通过";
          }
      
          /**
           * 验证上边Test4的说法
           */
          @PostMapping("/validationTest5")
          public String validationTest5(@RequestBody User user){
              String valid = ValidatorUtils.validProperty(user,"age", A.class);
              if(null != valid) return valid;
              //do somethings
              return "校验通过";
          }
      
          /**
           * 使用方法7抽的工具类
           */
          @PostMapping("/validationTest8")
          public String validationTest8(@RequestBody @Valid User user, 
                                        BindingResult result){
              String errorMsg = ValidatorUtils.validEntity(result);
              if(StringUtils.isNotBlank(errorMsg)) return errorMsg;
              //do somethings
              return "校验通过";
          }
      

      (3) 补充:User类

      public class User {
      
          @NotBlank(message = "用户名不能为空串",groups = A.class)
          private String username;
          @NotNull(message = "年龄不能为空",groups = B.class)
          private String age;
          @NotBlank(message = "身高不能为空", groups = {A.class, B.class})
          private String height;
          @NotEmpty(message = "孩子列表不能为空")
          private List<Object> childs;
          
          @Email(message = "email不正确")
          private String email;
          
          //省略get set 方法
      }
      
    5. 扩展部分

      空检查

      @Null:验证某参数必须为空 ps: 没有过 =. = ,其余的空检查在上边已经讲了

      长度检查

      @Size(min=, max=): 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内

      @Length(min=, max=): 长度是否在范围内

      Booelan检查

      @AssertTrue: 验证 Boolean 对象是否为 true

      @AssertFalse: 验证 Boolean 对象是否为 false

      数值检查:建议用在基础类型包装类型上和String类型上

      @Min: 验证最小值

      @Max: 验证最大值

      @DecimalMax:小数数值不能大于设置的值,小数存在精度

      @DecimalMin:小数数值不能小于设置的值,小数存在精度

      @Digits: 验证数字和字符串组成是否合法

      范围检查

      @Range(min=, max=):验证对象最小与最大的范围

      号码检查

      @CreditCardNumber: 信用卡验证(hibernate的实现)

      @Email: 验证是否是邮件地址,如果为null,则不进行验证,通过验证(validation和hibernate均有实现)

      日期检查

      @Past:验证 Date 和 Calendar 对象是否在当前时间之前,null 被认为是通过验证

      @Future:验证 Date 和 Calendar 对象是否在当前时间之后 ,null 被认为是通过验证

      正则表达式检查

      @Pattern(regexp = ""): 使用正则表达式验证String,接受字符序列,regexp必填,所修饰为null时认为是通过验证

    6. 终了

      憋了一晚上才想通校验的实际用法,参考了一些的博文,把最大部分的内容引用粘在下面了,基本是官方文档和一个外文网站

      https://docs.oracle.com/javaee/7/api/toc.htm

      https://docs.jboss.org/hibernate/stable/validator/api/

      https://www.baeldung.com/javax-validation

      https://blog.csdn.net/yanfeng918/article/details/42618593

      本文代码:https://github.com/HellxZ/MyUtils.git

      声明:本文内容未经许可可以转载,但请注明出处

  • 相关阅读:
    ul制作导航菜单
    HTML5+CSS (简易nav设计)
    鼠标事件-拖拽(滑块控制物体透明度变化)
    鼠标事件-拖拽5(带虚线框的拖拽)
    鼠标事件-拖拽4(捕获)
    鼠标事件-拖拽3(磁性吸附)
    鼠标事件-拖拽2(不能拖出指定对象的div)
    生成整数排列的方法
    python工具程序一、复制目录中指定扩展名的文件
    Anaconda packages list
  • 原文地址:https://www.cnblogs.com/hellxz/p/9535034.html
Copyright © 2020-2023  润新知