美文网首页
基于自定义Validator来验证枚举类型

基于自定义Validator来验证枚举类型

作者: huan1993 | 来源:发表于2021-04-02 09:20 被阅读0次

    一、背景

    在我们系统中,有部分字段的值是枚举类型的,但是请求参数中一般不会直接使用枚举来进行接收,而是使用Interget等类型来接收,当系统中这些值是必须的时候,我们要保证前端系统传递的数据是正确的,合法的,因此需要进行校验。

    例子:
    比如:用户的性别 Sex 只能是 0-未知 1-男 2-女,那么前端只能传递0,1,2其中的一个,如果是别的值,则告知前端用户性别有问题。

    二、技术要点

    1、自己的验证逻辑类需要实现ConstraintValidator接口。
    2、自定义一个注解,注解上需要使用@Constraint(validatedBy = xxx),validatedBy的值指向验证的类,即实现了ConstraintValidator接口的类。

    三、实现一个自定义枚举校验。

    1、需求。

    我们有一个创建学生的接口,请求参数有一个 sex 值,它的值只能是0-未知 1-男 2-女,在控制层基于自定义的枚举注解,验证 sex 的值是否合法。

    2、实现步骤

    1、自定义一个 Sex 枚举。

    此枚举,主要用于记录 sex 属性的值可以是哪些值。

    注意:
    我们的 枚举类中的 code 的值,验证的时候需要用到这个。

    
    @Getter
    @AllArgsConstructor
    public enum Sex {
        
        UNKNOWN(0,"未知"),
        MAN(1,"男"),
        WOMEN(2,"女");
        
        private final Integer code;
        private final String desc;
    }
    
    

    2、自定义一个 Enum 注解

    
    import com.google.common.collect.Lists;
    import com.xincheng.common.exception.BizException;
    import com.xincheng.xxcloud.ehouse.common.EhouseErrorCode;
    import lombok.extern.slf4j.Slf4j;
    
    import javax.validation.Constraint;
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import javax.validation.Payload;
    import java.lang.annotation.*;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.List;
    
    /**
     * @author huan.fu 2021/4/1 - 下午3:35
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.METHOD})
    @Constraint(validatedBy = EnumValidator.class)
    public @interface Enum {
        /**
         * 枚举的类型
         */
        Class<?> value();
    
        /**
         * 错误消息
         *
         * @return
         */
        String message() default "枚举类型的值不正确";
    
        /**
         * 获取枚举值的方法
         */
        String method() default "getCode";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    
    

    解释:
    1、@Constraint(validatedBy = EnumValidator.class) 中的validatedBy指定的是 @Enum 这个注解交由那个类去校验。
    2、Class<?> value() 表示需要这个字段对应的枚举的类型。
    3、String method() default "getCode" 这个 method() 方法表示,我们怎么从具体的枚举对象中获取值。

    比如上方定义的Sex,里面有2个属性codedesc,而code作为枚举的值,此处的method() 就需要写 getCode

    3、编写具体的验证逻辑类

    
    @Component
    @Slf4j
    class SpringBean {
        public void invoked() {
            log.info("调用spring管理的bean的方法");
        }
    }
    
    /**
     * 枚举校验
     */
    @Slf4j
    class EnumValidator implements ConstraintValidator<Enum, Object> {
    
        // 存具体枚举的值
        private final List<Object> values = Lists.newArrayList();
    
        @Autowired
        private SpringBean springBean;
    
        @Override
        public void initialize(Enum constraintAnnotation) {
    
            springBean.invoked();
    
            Class<?> enumClazz = constraintAnnotation.value();
            Object[] enumConstants = enumClazz.getEnumConstants();
            if (null == enumConstants) {
                return;
            }
            Method method = BeanUtils.findMethod(enumClazz, constraintAnnotation.method());
            if (null == method) {
                log.warn("枚举对象:[{}]中不存在方法:[{}],请检查.", enumClazz.getName(), constraintAnnotation.method());
                throw new BizException(EhouseErrorCode.FAIL.getCode(), "枚举对象中不存在获取值的方法");
            }
    
            method.setAccessible(true);
            try {
                for (Object enumConstant : enumConstants) {
                    values.add(method.invoke(enumConstant));
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                log.warn("获取枚举类:[{}]的枚举对象的值失败.", enumClazz);
                throw new BizException(EhouseErrorCode.FAIL.getCode(), "获取枚举值失败");
            }
        }
    
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {
            return null == value || values.contains(value);
        }
    }
    
    
    

    注意:

    1、ConstraintValidator<Enum, Object>

    第一个参数:是我们自定义的校验注解,此处是 Enum,是因为我们上方自定义的注解就是 @interface Enum 。
    第二个参数:指的是页面上传递过来的具体的数据的类型。

    2、如果我们的LocalValidatorFactoryBeanSpringConstraintValidatorFactory,那么在我们的验证类中可以使用Spring的依赖注入。

    3、isValid 方法需要保证线程安全,因为它可能是多线程调用。

    4、编写一个web请求,添加学生。

    1、创建请求参数实体类

    
    @Data
    public class Student {
        @Enum(value = Sex.class, message = "请填写正确的心别")
        private Integer sex;
    }
    
    

    注意:
    1、sex属性上使用了@Enum来标识,表示后期需要使用@Enum这个来验证,而我们自己写的EnumValidator是用来验证这个的。那么我们的sex属性的值匹配上来哪些值是合法的呢,这个可以看到@Enum的value上指定了value = Sex.class,即我们的sex的值需要是Sex这个枚举类的值的其中一个。

    2、编写访问方法

    
    public String addStudent(@Valid @RequestBody Student student) {
            log.info("student:[{}]", student);
            return "ok";
        }
    
    

    3、页面访问

    1、sex 属性的值在 Sex 枚举的范围之内
    sex 属性的值在 Sex 枚举的范围之内
    2、sex 属性的值不在 Sex 枚举的范围之内
    sex 属性的值不在 Sex 枚举的范围之内

    四、对应关系

    对应关系

    五、参考文档

    1、https://docs.spring.io/spring-framework/docs/5.3.x/reference/html/core.html#validation-beanvalidation-spring-constraints

    相关文章

      网友评论

          本文标题:基于自定义Validator来验证枚举类型

          本文链接:https://www.haomeiwen.com/subject/wasfkltx.html