@小仙女的胖鱼(pycrab)


标签:开发规范参数校验


一、校验规则

JSR303(Bean Validation)是Java提供的一项标准规范,它含有一些基本的constraint,比如@NotNull。Hibernate Validator参考Bean Validation实现了其他的一些校验规则,比如:

结合这两者,我们就可以进行基本的参数校验。Spring Boot默认使用hibernate-validator进行校验,首先我们需要导入相关依赖,Spring Boot Web项目的话肯定会引入spring-boot-starter-web依赖,其中已经包含了hibernate-validator,故无需引入(否则需要引入spring-boot-starter-validation或者hibernate-validator)。


二、实体属性校验

该校验基于使用实体对象来接收请求参数,参数和实体属性对应。比如@RequestBody接受请求体参数和用实体类封装的GET请求参数。

1、基本使用

  1. /**
  2. * 1、通过两个步骤即可实现普通对象校验
  3. */
  4. // 实体属性添加校验规则
  5. @NotBlank(message = "标题不能为空")
  6. private String title;
  7. // 接口方法实体对象参数前面添加@valid注解
  8. public R save(@RequestBody @Valid SaveDTO dto){
  9. }
  10. /**
  11. * 2、如果需要进行嵌套校验,即实体类中的属性是一个实体对象,则需要在以上基础上,
  12. * 在嵌套属性上再添加一个@Valid注解
  13. */
  14. @Valid
  15. private List<SaveDTO> dto;
  16. /**
  17. * 3、如果参数是数组对象,则需要在以上基础上,在类上面添加@Validated注解
  18. */
  19. @Validated
  20. public class FileController {
  21. @PostMapping
  22. public R saveFiles(@RequestBody @Valid List<File> files) { // Java 8可以将@Valid放在<>内
  23. }
  24. }

2、分组校验

如果对同一个参数在不同情况下使用不同的校验规则(比如增加和修改操作),这时候就需要使用分组的功能,使用分组校验与之前的稍有不同,分为三个步骤:

  • 创建代表不同分组的空接口,未指定分组的属性属于Default分组
  • 在校验规则注解上添加group属性
  • 在接口实体对象参数前面添加@Validated注解,并指明分组 ```java /**
    • 1、先声明三个空接口 */ public interface AddGroup{} public interface EditGroup{}

// 该接口定义分组的校验顺序 @GroupSequence({EditGroup.class, AddGroup.class, Default.class}) public interface Group { }

/**

  • 2、声明所属分组 */ @NotBlank(message = “标题不能为空”, group = {AddGroup.class}) private String title;

/**

  • 3、指定校验组别 */ public R save(@RequestBody @Validated({AddGroup.class, Default.class}) SaveDTO dto){ } ```

    3、自定义校验

    如果已有的校验规则不适用具体场景,我们就需要自定义规则来校验,主要有两个步骤,创建自定义注解和实现自定义规则,然后注解和普通注解一样使用即可。

    • 创建自定义注解 ```java /*
  • 元注解 */ @Retention(RetentionPolicy.RUNTIME) // 指定注解的生命周期,这里表示运行时仍可用 @Target({ElementType.FIELD}) // 指定注解的使用范围,这里表示用在实体属性上 @Inherited @Documented

/*

  • 其他声明注解 */ @Constraint(validatedBy = CheckPrimeNumberValidator.class) // 指定注解的处理类 public @interface CheckPrimeNumber { String message() default “该数字不是素数”; //String value(); Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }

    1. - 实现自定义规则,创建一个自定义校验类实现ConstraintValidator类,重写其中的initialize方法和isValid方法
    2. ```java
    3. public class CheckPrimeNumberValidator implements ConstraintValidator<CheckPrimeNumber, Integer> {
    4. @Override
    5. public void initialize(CheckPrimeNumber checkPrimeNumber) {
    6. }
    7. @Override
    8. public boolean isValid(Integer prime, ConstraintValidatorContext constraintValidatorContext) {
    9. // 验证逻辑
    10. Boolean flag = true;
    11. if (prime != null) {
    12. if (prime < 2) {
    13. return false;
    14. }else {
    15. for (int i = 2; i <= Math.sqrt(prime); ++i) {
    16. if (prime % i == 0) {
    17. flag = false;
    18. break;
    19. }
    20. }
    21. }
    22. }
    23. return flag;
    24. }
    25. }

    有这样一个需求,根据实体类的某个字段的值决定要不要校验其它属性,这里也进行自定义校验规则实现: ```java /**

  • 自定义类注解 */ @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = SubValidator.class) public @interface ValidateSub { String message() default “”;

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

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

/**

  • 自定义校验类 */ public class SubValidator implements ConstraintValidator { @Override public void initialize(ValidateSub validateSub) { }

    @Override public boolean isValid(WorkOrderMakeUpDTO.SubDTO subDTO, ConstraintValidatorContext constraintValidatorContext) {

    1. /**
    2. * 这里需要手工判断,可以在没有到达业务前进行判断
    3. */
    4. // 根据该字段判断是否需要校验
    5. if (subDTO.isEnabled())
    6. {
    7. // 禁用默认的校验规则
    8. constraintValidatorContext.disableDefaultConstraintViolation();
    9. if (StringUtils.isBlank(subDTO.getClassId()))
    10. {
    11. // 添加新的校验提示
    12. constraintValidatorContext.buildConstraintViolationWithTemplate("订阅资源标识不能为空").addConstraintViolation();
    13. return false;
    14. }
    15. if (StringUtils.isBlank(subDTO.getClassName()))
    16. {
    17. constraintValidatorContext.buildConstraintViolationWithTemplate("订阅资源名称不能为空").addConstraintViolation();
    18. return false;
    19. }
    20. }
    21. return true;

    } } ```


三、方法参数校验

JSR303和Hibernate validator的校验只能对实体类的属性进行校验,不能对单个方法参数进行校验,spring在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验,通过三个步骤实现:

  • 实例化MethodValidationPostProcessor,实现如下:

    1. @Bean
    2. public MethodValidationPostProcessor methodValidationPostProcessor() {
    3. return new MethodValidationPostProcessor();
    4. }
  • 在接口类上添加@Validated注解

  • 在接口方法的参数前面添加普通校验规则即可

四、自定义校验配置

通过以上方式校验请求参数,返回的校验结果总是全部参数的校验结果,我们需要的是一次校验一个,返回一个校验异常的时候,就需要自定义校验配置类来实现,代码如下:

  1. @Configuration
  2. public class CustomValidatorConfig {
  3. @Bean
  4. public Validator validator(){
  5. ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
  6. .configure()
  7. .failFast(true)
  8. .buildValidatorFactory();
  9. Validator validator = validatorFactory.getValidator();
  10. return validator;
  11. }
  12. }