HibernateValidator自定义验证

39次阅读

共计 5645 个字符,预计需要花费 15 分钟才能阅读完成。

前言

在自己的摸索下对 HibernateValidator 有了初步的认识,可以使用已有的约束条件对字段做出限制,减少不要代码的出现,使代码更简洁。
但在最近的实际使用中,出现了一些无法使用框架处理的问题,例如,在第三方请求我的接口时,根据 status 字段区分不同的业务逻辑;status= 1 进行 A 逻辑处理,status= 2 进行 B 逻辑处理;在网络了检索了相关信息后,做出如下总结。

步骤

  1. 使用通用 Mapper 插件生成实体类和 mapper, 这里为了聚焦对 HibernateValidator 的使用,把关注点放在实体类 Brand 中, 在实体类中,对 status 字段添加自定义约束 @StatusConstraint

     package com.codeup.mybatisjoin.model;
    
    import com.codeup.mybatisjoin.validation.StatusConstraint;
    import lombok.Data;
    import javax.persistence.Column;
    import javax.persistence.Id;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    
    @Data
    public class Brand {
       
        @Id
        @Column(name = "brand_ID")
        private Long brandId;
    
    
        /**
         * 使用 `@NotNull` 添加约束
         */
        @Column(name = "vendor_ID")
        @NotNull(message = "[vendorId]不可为空")
        private Long vendorId;
    
    
        /**
         * 使用 `@NotBlank` 添加约束
         */
        @Column(name = "brand_name")
        @NotBlank(message = "[brandName]字段不可为空")
        private String brandName;
    
        private String description;
    
    
        /**
         * 自定义约束
         */
        @StatusConstraint(message = "[status]为 1 或者 2")
        private String status;
    }
  2. @StatusConstraint实现步骤和自定义注解相似

    package com.codeup.mybatisjoin.validation;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @ProjectName: mybatisjoin
     * @Package: com.codeup.mybatisjoin.validation
     * @ClassName: StatusConstraint
     * @Author: lhc
     * @Description: 状态约束
     * @Date: 2019/8/22 下午 3:36
     */
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    /**
     * 指出当前约束是通过 `StatusConstraintValidator.class` 来实现的
     */
    @Constraint(validatedBy = StatusConstraintValidator.class)
    public @interface StatusConstraint {
    
        /**
         * 配置 message 信息
         * @return
         */
        String message() default "违规参数";
    
        /**
         * 分组
         * @return
         */
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};}
  3. 通过查看源代码里面的文档得知要要实现 ConstraintValidator 接口

    @Documented
    @Target({ANNOTATION_TYPE})
    @Retention(RUNTIME)
    public @interface Constraint {
    
        /**
         * {@link ConstraintValidator} classes implementing the constraint. The given 
    classes
         * must reference distinct target types for a given {@link ValidationTarget}. 
    If two
         * {@code ConstraintValidator}s refer to the same type, an exception will 
    occur.
         * <p>
         * At most one {@code ConstraintValidator} targeting the array of parameters of
         * methods or constructors (aka cross-parameter) is accepted. If two or more
         * are present, an exception will occur.
         *
         * @return array of {@code ConstraintValidator} classes implementing the 
    constraint
         */
        Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
    }
  4. 实现 ConstraintValidator 接口,重写 isValid() 方法,实现自己的判断逻辑

    package com.codeup.mybatisjoin.validation;
    
    import lombok.extern.slf4j.Slf4j;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    /**
     * @ProjectName: mybatisjoin
     * @Package: com.codeup.mybatisjoin.validation
     * @ClassName: StatusConstraintValidator
     * @Author: lhc
     * @Description: TODO
     * @Date: 2019/8/22 下午 3:38
     */
    @Slf4j
    public class StatusConstraintValidator implements 
    ConstraintValidator<StatusConstraint,Object> {
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {String statCode = (String) value;
            // 等于 1 或 2 返回 true, 反之
            if ("1".equals(statCode) || "2".equals(statCode)) {return true;}
            return false;
        }
    }
  5. 至此,对字段添加约束条件已完成。还存在一个重要的操作是对约束的校验,这里通过工具类实现

    package com.codeup.mybatisjoin.validation;
    
    import org.hibernate.validator.HibernateValidator;
    
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.Validator;
    import java.util.Set;
    
    /**
     * @ProjectName: mybatisjoin
     * @Package: com.codeup.mybatisjoin.validation
     * @ClassName: ValidatorConfig
     * @Author: lhc
     * @Description: TODO
     * @Date: 2019/8/22 下午 4:31
     */
    public class ValidatorUtil {
    
    
        /**
         * 配置 hibernate_validator 和快速失败模式
         */
        private static Validator validator = 
    Validation.byProvider(HibernateValidator.class)
               .configure()
               .failFast(true)
               .buildValidatorFactory()
               .getValidator();
    
    
        /**
         * 参数校验,若未匹配约束,则通过已将将之前定义的 `message` 抛出
         * @param object 参数
         * @param groups 属于组
         */
        public static void result(Object object, Class<?>... groups) {
            Set<ConstraintViolation<Object>> constraintViolations = 
    validator.validate(object, groups);
             if (constraintViolations.size() > 0) {String message = constraintViolations.iterator().next().getMessage();
                throw new MissingParameterException(message);
            }
        }
    }
  6. 将 message 信息抛出之后,需要以更统一的方式返回给第三方,使用统一异常处理机制解决

    • 自定义异常类 MissingParameterException

      package com.codeup.mybatisjoin.validation;
      
      public class MissingParameterException extends RuntimeException {public MissingParameterException(String message) {super(message);
          }
      }
      
    • 统一异常处理

      package com.codeup.mybatisjoin.validation;
      
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      
      import java.util.HashMap;
      import java.util.Map;
      
      
      @RestControllerAdvice
      public class GlobalExceptionHandler {@ExceptionHandler(MissingParameterException.class)
          public Map<String, Object> invalidParameter(MissingParameterException e) {Map<String, Object> map = new HashMap<>();
              map.put("code", 500);
              map.put("message", e.getMessage());
              return map;
          }
      }
  • 通过请求查看返回结果

    package com.codeup.mybatisjoin.controller;
    
    import com.codeup.mybatisjoin.model.Brand;
    import com.codeup.mybatisjoin.validation.ValidatorUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    
    @Slf4j
    @RestController
    @RequestMapping(value = "/brandConroller")
    public class BrandConroller {@PostMapping(value = "/go")
        public Map<String, Object> go(@RequestBody Brand brand) {ValidatorUtil.result(brand);
            Map<String, Object> map = new LinkedHashMap<>();
            log.info("brand:{}", brand);
            return map;
        }
    }
  1. 请求

    // request
     {
        "brandId": 1,
        "vendorId": 1,
        "brandName": "demoData",
        "description": "demoData",
           "status": "3"
    }
    // response
    {
        "code": 500,
        "message": "[status]为 1 或者 2"
    }

正文完
 0