zoukankan      html  css  js  c++  java
  • 枚举-注解校验(2)

    java validation内没有对枚举的校验工具,但是离散的枚举值校验确实是有必要的,这里列两种枚举的校验方法,实际大同小异。首先,javax.validation包是提供了方便的自定义校验的入口的,就是javax.validation.ConstraintValidator。
    1. 对离散值(非枚举)的校验
    若离散的值尚未形成枚举,这种校验反而好做一点,因为无需引入反射这种黑魔法。
    校验注解

    package com.springboot.study.tests.annotation;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.*;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 10:49
     * @Version: 1.0
     * @Description:
     */
    @Documented
    @Constraint(validatedBy = {EnumStringValidator.class})
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EnumValidateAnnotation {
    
        /**
         * 校验报错信息
         * @return
         */
        String message() default "";
    
        /**
         * 校验分组
         * @return
         */
        Class<?>[] groups() default {};
    
        /**
         * 附件 用于扩展
         * @return
         */
        Class<? extends Payload>[] payload() default {};
    
        /**
         * 允许的枚举值,所有类型转成String 存储
         * @return
         */
        String[] enums() default {};
    
    }

    校验实现 

    package com.springboot.study.tests.annotation;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 12:51
     * @Version: 1.0
     * @Description:
     */
    public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation,String> {
    
        private List<String> enumStrings;
    
        @Override
        public void initialize(EnumValidateAnnotation constraintAnnotation) {
            enumStrings = Arrays.asList(constraintAnnotation.enums());
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null) {
                return false;
            }
            // 对于Integer,将其转化为String 后比较
            return enumStrings.contains(value);
        }
    }

    使用示例

    package com.springboot.study.tests.annotation;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 10:34
     * @Version: 1.0
     * @Description:
     */
    public class Student {
    
        /**
         * 来源
         */
        @NotNull(message = "请求来源不能为空")
        // 这里将枚举值FromEnum的String表示放入注解的enums中
        // 如果是离散值的话,更加合适,因为不用关心枚举值和注解 enums保持一致
        @EnumValidateAnnotation(enums = {"from1", "from2"}, message = "上报来源错误")
        String from;
    
        @MyAnnotation(age = 26)
        public void test() {
        }
    
    }

    缺点
    这种使用方式,缺点比较明显,就是如果要修改的话,不仅枚举的地方要修改,使用注解校验的地方因为只写了String 的值,所以注解的enums参数也需要修改,两个地方不能放到一起维护,有遗漏的防线。

    2. 对枚举的校验
    枚举的校验就有点伤,注解的声明是不允许泛型的,这意味着我们无法优雅的直接将不同的泛型用同一个校验器校验,除非使用黑魔法。甚至,注解的属性,连抽象类、接口、集合都不允许使用,所以想传入一个lambda表达式,自定义处理方式都是不行的。没办法,用反射吧。
    校验注解

    package com.springboot.study.tests.annotation;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.*;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 13:04
     * @Version: 1.0
     * @Description:
     */
    @Documented
    @Constraint(validatedBy = {EnumStringValidator2.class})
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface EnumValidateAnnotation2 {
    
        /**
         * 校验报错信息
         * @return
         */
        String message() default "";
    
        /**
         * 校验分组
         * @return
         */
        Class<?>[] groups() default {};
    
        /**
         * 附件 用于扩展
         *
         * @return
         */
        Class<? extends Payload>[] payload() default {};
    
        /**
         * 允许的枚举
         * @return
         */
        Class<? extends Enum<?>> enumClass();
    
        /**
         * 校验调用的枚举类的方法
         * @return
         */
        String method();
    
    }

    校验实现

    package com.springboot.study.tests.annotation;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Objects;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 13:07
     * @Version: 1.0
     * @Description:
     */
    public class EnumStringValidator2 implements ConstraintValidator<EnumValidateAnnotation2, String> {
    
        private String methodStr;
    
        private Class<? extends Enum> enumClass;
    
        @Override
        public void initialize(EnumValidateAnnotation2 constraintAnnotation) {
            methodStr = constraintAnnotation.method();
            enumClass = constraintAnnotation.enumClass();
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null) {
                return false;
            }
            try {
                // 反射获取校验需要调用的枚举的方法
                Method method = enumClass.getMethod(methodStr);
                boolean result = false;
                // 获取所有的枚举值
                Enum[] enums = enumClass.getEnumConstants();
                // 对每一个枚举值调用 校验的方法,获取返回值,和入参作比较
                for (Enum e : enums) {
                    Object returnValue = method.invoke(e);
                    result = Objects.equals(returnValue, value);
                }
                return result;
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                // 异常处理
            } catch (Throwable throwable) {
                // 异常处理
            }
            return false;
        }
    
    }

    使用示例

    package com.springboot.study.tests.annotation;
    
    import lombok.Getter;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 13:11
     * @Version: 1.0
     * @Description:
     */
    public enum FromEnum {
    
        /**
         * 来源1
         */
        form1("form1"),
    
        /**
         * 来源2
         */
        form2("form2");
    
        @Getter
        String from;
    
        FromEnum(String from) {
            this.from = from;
        }
    
    }
    package com.springboot.study.tests.annotation;
    
    import lombok.Data;
    
    import javax.validation.constraints.NotNull;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 10:34
     * @Version: 1.0
     * @Description:
     */
    @Data
    public class Student {
    
        @NotNull(message = "请求来源不能为空")
        @EnumValidateAnnotation2(enumClass = FromEnum.class, method = "getFrom", message = "上报来源错误")
        String from;
    
        @MyAnnotation(age = 26)
        public void test() {
        }
    
    }

    缺点与一丢丢改进
    使用反射,缺点明显,就是性能下降,尤其上面代码对每一个枚举反射调用方法,改进的话,就是在枚举中写一个特定方法,专门用来做这种入参到枚举值的转换,转换成功,则说明入参正确,否则,说明入参错误。
    使用示例

    package com.springboot.study.tests.annotation;
    
    public interface EnumValidate<T>{
    
        boolean inEnum(T value);
    
    }
    package com.springboot.study.tests.annotation;
    
    import lombok.Getter;
    
    /**
     * @Author: guodong
     * @Date: 2021/7/24 16:04
     * @Version: 1.0
     * @Description:
     */
    public enum FromEnum2 implements EnumValidate<String>{
    
        /**
         * 来源1
         */
        form1("form1"),
    
        /**
         * 来源2
         */
        form2("form2");
    
        @Getter
        String from;
    
        FromEnum2(String from) {
            this.from = from;
        }
    
        public static FromEnum of(String desc) {
            for (FromEnum from : FromEnum.values()) {
                if (from.getFrom().equalsIgnoreCase(desc)) {
                    return from;
                }
            }
            return null;
        }
    
        @Override
        public boolean inEnum(String value){
            return of(value) != null;
        }
    
    }

    校验器实现 

    public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation, String> {
    
        private String methodStr;
    
        private Class<? extends Enum> enumClass;
    
        @Override
        public void initialize(EnumValidateAnnotation constraintAnnotation) {
            methodStr = constraintAnnotation.method();
            enumClass = constraintAnnotation.enumClass();
        }
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (value == null) {
                return false;
            }
            EnumValidate[] enums = enumClass.getEnumConstants();
            if(enums ==null || enums.length == 0){
              return false;   
            }
            return enums[0].inEnum(value);
        }
    }

    补充内容
    校验器使用,可以使用这种方式对枚举注解入参进行校验,参数是否合法化,或者是使用springboot的注解@valid等注解对入参进行校验。

    private static Validator validator = Validation.byProvider(HibernateValidator.class)
            .configure()
            .failFast(true)
            .buildValidatorFactory()
            .getValidator();
    
    public static <T> void validParam(T param) {
        validParamNonNull(param);
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(param, Default.class);
        StringBuilder sb = new StringBuilder();
        if (constraintViolations != null && !constraintViolations.isEmpty()) {
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                sb.append(constraintViolation.getPropertyPath())
                    .append(":")
                    .append(constraintViolation.getMessage())
                    .append(".");
            }
            throw new IllegalArgumentException(sb.toString());
        }
    }
    郭慕荣博客园
  • 相关阅读:
    记忆碎片:我的2007
    查看 Oracle 是用spfile 启动还是 pfile 启动
    Oracle 10g OCP 042 题库 71120 题 共168题
    Oracle OLAP 与 OLTP 介绍
    Oracle Data Guard Switchover 切换
    Linux 终端访问 FTP 及 上传下载 文件
    多表连接的三种方式详解 HASH JOIN MERGE JOIN NESTED LOOP
    Oracle 表连接方式(内连接/外连接/自连接) 详解
    Oracle 表连接方式(内连接/外连接/自连接) 详解
    查看 Oracle 是用spfile 启动还是 pfile 启动
  • 原文地址:https://www.cnblogs.com/jelly12345/p/15054661.html
Copyright © 2011-2022 走看看