SpringBoot 使用Validator 校验框架


Hibernate Validator 简介

  Hibernate Validator是Hibernate项目中的一个数据校验框架,是Bean Validation 的参考实现,Hibernate Validator除了提供了JSR 303规范中所有内置constraint 的实现,还有一些附加的constraint。

Hibernate Validator 作用

  • 数据校验逻辑和业务代码分离,程序解耦性提高
  • 统一且规范的校验格式,规避了大量重复的数据校验代码
  • 精力更加集中于业务代码

Hibernate Validator 使用

项目中,主要通过接口API的接口入参校验和封装工具类在代码中使用两种方式

引入jar包
<!-- 使用SpringBoot框架 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- 直接引用jar包 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.9.Final</version>
</dependency>
Java对象添加约束

级联校验需要添加@Valid注解

import com.ai.chinapost.crm.mdb.mgr.common.ValidateGroup;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.Valid;
import java.util.List;

/**
 * @date 2021/01/11
 */
@Data
public class CltMktOccuAndIncmBO {
    /**
     * 保存或修改标识 1:保存 2:修改
     */
    @NotBlank(message = "保存或修改标识不能为空" ,groups = ValidateGroup.CltMktOccuAndIncm.class)
    private String saveOrUpdate;

    /**
     * 更新人编码
     */
    @NotBlank(message = "更新人编码必填" ,groups = ValidateGroup.CltMktOccuAndIncm.class)
    private String updatedByUserCode;
    /**
     * 更新人所属机构编码
     */
    @NotBlank(message = "更新人所属机构编码必填" ,groups = ValidateGroup.CltMktOccuAndIncm.class)
    private String updatedByOrgCode;
    /**
     * 统计时间
     */
    @NotBlank(message = "统计时间必填" ,groups = ValidateGroup.CltMktOccuAndIncm.class)
    private String statisDate;

    @Valid
    private List<CltMktOccuAndIncmDetailBO> cltMktOccuAndIncmDetailList;
}
校验组设置
/**
 * @date 2020/7/21
 */
public interface ValidateGroup {
    interface CltMktOccuAndIncm {
    }
}
API接口入参校验
  • 定义接口
      接口入参需要添加@Validated注解,进行参数校验
@PostMapping("/saveDist")
@ResponseBody
    public ResponseEntity dist(@Validated(ValidateGroup.CltMktOccuAndIncm.class) @RequestBody TrgtMktOcBase entity) throws CommonException {
        ResponseEntity resp = distTrgtFacadeConsumer.dist(entity);
        return resp;
    }

  • postMan测试结果
    请求报文:
    {
    "saveOrUpdate": "",
    "updatedByUserCode": "20000",
    "updatedByOrgCode": "100",
    "statisDate": "",
    "cltMktOccuAndIncmDetailList": [
      {
        "provId": "",
        "provName": "安徽"
      },
      {
        "provId": "370000",
        "provName": "山东"
      }
    ]
    }
    响应报文:
    {
      "code": 400,
      "message": "statisDate:统计时间必填 saveOrUpdate:保存或修改标识不能为空 cltMktOccuAndIncmDetailList[0].provId:省份编码必填 参数值有误"
    }
    

封装工具类校验
  • 工具类中的Validator对象有两种方式获取(选其一即可)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.validation.Validator;

/**
 * @date 2021/01/13
 */
@Component
public class ValidatorConfig {

    @Bean(name = "validator")
    @Primary
    public Validator validator() {
        return new LocalValidatorFactoryBean();
    }
}

import lombok.Data;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * 校验工具类
 *
 * @date 2021/01/13
 */
@Component
public class ValidationUtil implements ApplicationContextAware {

    private static final ValidationUtil Instance = new ValidationUtil();

    public static ValidationUtil getInstance() {
        return Instance;
    }

    private static Validator validator;

    // 结合ValidatorConfig类通过Spring容器获取
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ValidationUtil.validator = (Validator) applicationContext.getBean("validator");
    }

    // 直接通过Validation类获取对象
    // 开启快速结束模式 failFast (true)
    private static Validator validator1 = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();

    /**
     * 校验对象
     *
     * @param obj    bean对象
     * @param groups 校验组,必须是一个接口
     * @param <T>
     * @return Optional<ValidResult>
     */
    public <T> Optional<ValidResult> validateBean(T obj, Class<?>...groups) {
        ValidResult result = Instance.new ValidResult();
        Set<ConstraintViolation<T>> violationSets = validator.validate(obj, groups);

        if (CollectionUtils.isEmpty(violationSets)) {
            return Optional.empty();
        }

        for (ConstraintViolation<T> violation : violationSets) {
            result.addError(violation.getPropertyPath().toString(), violation.getMessage());
        }

        return Optional.of(result);
    }

    /**
     * 校验对象的某一个属性
     *
     * @param obj          bean对象
     * @param propertyName 属性名称
     * @param <T>
     * @return Optional<ValidResult>
     */
    public <T> Optional<ValidResult> validateProperty(T obj, String propertyName) {
        ValidResult result = Instance.new ValidResult();
        Set<ConstraintViolation<T>> violationSets = validator.validateProperty(obj, propertyName);

        if (CollectionUtils.isEmpty(violationSets)) {
            return Optional.empty();
        }

        for (ConstraintViolation<T> violation : violationSets) {
            result.addError(violation.getPropertyPath().toString(), violation.getMessage());
        }

        return Optional.of(result);
    }

    @Data
    public class ValidResult {

        /**
         * 错误信息
         */
        private List<ErrorMessage> errors;

        public ValidResult() {
            this.errors = new ArrayList<>();
        }

        /**
         * 获取所有验证信息
         *
         * @return 集合形式
         */
        public List<ErrorMessage> getAllErrors() {
            return errors;
        }

        /**
         * 获取所有验证信息
         *
         * @return 字符串形式
         */
        public String getErrors() {
            StringBuilder sb = new StringBuilder();
            for (ErrorMessage error : errors) {
                sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" ");
            }
            return sb.toString();
        }

        public void addError(String propertyName, String message) {
            this.errors.add(new ErrorMessage(propertyName, message));
        }
    }

    @Data
    private class ErrorMessage {
        private String propertyPath;

        private String message;

        public ErrorMessage() {
        }

        public ErrorMessage(String propertyPath, String message) {
            this.propertyPath = propertyPath;
            this.message = message;
        }
    }

}

  • 测试代码

    也可不指定分组(groups),会默认使用Default.class分组

public ResponseEntity saveCltMktOccuAndIncm(String params) {
         // 校验参数
        Class cltMktOccuAndIncm = ValidateGroup.CltMktOccuAndIncm.class;
        CltMktOccuAndIncmBO cltMktOccuAndIncmBO = JSON.parseObject(params, CltMktOccuAndIncmBO.class);
        Optional<ValidationUtil.ValidResult> validResult = ValidationUtil.getInstance().validateBean(cltMktOccuAndIncmBO, cltMktOccuAndIncm);
        if (validResult.isPresent()) {
            ValidationUtil.ValidResult errMessage = validResult.get();
            return ResponseEntity.fail(ResponseEnum.DATA_ERROR, errMessage.getErrors());
        }

        return cltMktOccuAndIncmFacade.saveOrModifyCltMktOccuAndIncm(cltMktOccuAndIncmBO);
    }

  • 测试结果
    响应报文:
    {
      "code": 400,
      "message": "statisDate:统计时间必填 saveOrUpdate:保存或修改标识不能为空 cltMktOccuAndIncmDetailList[0].provId:省份编码必填 参数值有误"
    }
    

其他常用的constranint

@AssertFalse @AssertTrue 检验boolean类型的值

@DecimalMax @DecimalMin 限定被标注的属性的值的大小

@Digits(intege=,fraction=) 限定被标注的属性的整数位数和小数位数

@Future 检验给定的日期是否比现在晚

@Past 校验给定的日期是否比现在早

@Max 检查被标注的属性的值是否小于等于给定的值

@Min 检查被标注的属性的值是否大于等于给定的值

@NotNull 检验被标注的值不为空

@Null 检验被标注的值为空

@Pattern(regex=,flag=) 检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配

@Size(min=,max=) 检查被标注元素的长度

@Valid 递归的对关联的对象进行校验

文章借鉴处


文章作者: Huowy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Huowy !
评论
  目录