zoukankan      html  css  js  c++  java
  • Oval框架如何校验枚举类型的一种思路

    前言:
      Oval校验框架被广泛集成于各类接口参数校验中, 其方便的注解语法, 易读性和扩展性. 几乎成了java后端服务代码的标配.
      有人会很疑惑, 都已经是枚举类型了, 还需要校验吗? 其实这边更确切的说法(应用场景), String对象映射为枚举值的校验, 如何来实现.
      在Dubbo(RPC服务)中, 并不推荐枚举类型作为参数, 原因涉及枚举类型特殊的序列化实现, 更新升级容易出现诡异的问题. 因此具体的枚举参数用String类型来替换, 但到具体的接口服务中, 又需要把String对象转化为枚举类型, 这中间就多了一个校验过程. 这就是本文需要阐述的.

    目标设定:
      定义一个枚举类, 以及一个具体的实体类:

    // 构建枚举类
    @Getter
    @AllArgsConstructor
    enum EType {
    
        ONE("one"),
        TWO("two");
    
        private String type;
    
    }
    
    // 具体的实体类
    @Getter
    @Setter
    @AllArgsConstructor
    class TNode {
    
        // 和枚举类EType是一一对应的关系
        private String type;
    
    }

      目标是, 实体类中name在枚举类的取值范围内.
      构建验证代码:

    class OvalValidator {
    
        /**
         *
         * 校验对象是否满足约束条件
         *
         * @param obj
         * @return
         *      true: 验证通过
         *      false: 验证不通过
         */
        public static boolean validate(Object obj) {
            Validator validator = new Validator();
            try {
                List<ConstraintViolation> list = validator.validate(obj);
                return (list == null || list.isEmpty());
            } catch (Throwable e) {
                return false;
            }
        }
    
    }
    
    
    public class OvalEnumTest {
    
        @Test
        public void test() {
            // *) 在范围内, one 对应 EType.ONE
            TNode t1 = new TNode("one");
            Assert.assertTrue(OvalValidator.validate(t1));
    
            // *) 不在范围内
            TNode t2 = new TNode("three");
            Assert.assertFalse(OvalValidator.validate(t2));
        }
    
    }

    方法一:
      采用Oval中的@MemberOf注解来实现, 具体如下:

    @Getter
    @Setter
    @AllArgsConstructor
    class TNode {
    
        @MemberOf(value = {"one", "two"}, message = "name not int range[one, two]")
        private String type;
    
    }

      不过这个方法, 也有其明显的缺点, 就是需要手工列出枚举值的所有变量. 如果有多个实体类需要映射该枚举类, 工作量不小, 同时枚举类本身的变化, 维护的成本相对较高.

    方法二:
      采用Oval中的@ValidateWithMethod注解来实现, 具体如下:

    @Getter
    @Setter
    @AllArgsConstructor
    class TNode {
    
        @ValidateWithMethod(methodName = "isValid", parameterType = String.class)
        private String type;
    
        public boolean isValid(String tv) {
            EType[] types = EType.values();
            for ( EType type : types ) {
                if ( type.getType().equalsIgnoreCase(tv) ) {
                    return true;
                }
            }
            return false;
        }
    
    }

      注解@ValidateWithMethod只支持本类内部的函数, 不能指定其他的类的函数方法.
      使用这个方案, 枚举值的增减, 并不需要同步更新涉及到的实体类, 维护的成本变低了, 但是每个实体类都需要自定义一个验证函数, 重复没有美感, 破坏了POJO类设定的初衷.

    方法三:
      结合上述两种方法的缺点, 如果能自定义校验注解, 那该多好, 实时上, Oval框架也提供了自定义注解的能力.

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
    @Constraint(checkWith = CETypeCheck.class)
    public @interface CEType {
        String message() default "没在EType枚举的范围内";
    }
    
    public class CETypeCheck extends AbstractAnnotationCheck<CEType> {
    
        @Override
        public boolean isSatisfied(Object o, Object o1, OValContext oValContext, Validator validator)
                throws OValException {
            if ( o1 == null ) return false;
            if ( o1 instanceof String ) {
                String tv = (String)o1;
                EType[] types = EType.values();
                for ( EType type : types ) {
                    if ( type.getType().equalsIgnoreCase(tv) ) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    
    @Getter
    @Setter
    @AllArgsConstructor
    class TNode {
    
        @CEType(message = "没在EType枚举的范围内")
        private String type;
    
    }
    

      通过自定义注解@CEType和CETypeCheck类, 来实现String映射到枚举类的校验, 简洁用力, 值得推崇.

    方法四:
      实际上, Oval提供了@CheckWith注解, 直接支持函数级校验.

    class ETypeSimpleCheck implements CheckWithCheck.SimpleCheck {
        @Override
        public boolean isSatisfied(Object o, Object o1) {
            if ( o1 == null ) return false;
            if ( o1 instanceof String ) {
                String tv = (String)o1;
                EType[] types = EType.values();
                for ( EType type : types ) {
                    if ( type.getType().equalsIgnoreCase(tv) ) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    @Getter
    @Setter
    @AllArgsConstructor
    class TNode {
    
        @CheckWith(value=ETypeSimpleCheck.class, message = "没在EType枚举的范围内")
        private String type;
    
    }
    

      这边@CheckWith的value需要对应实现CheckWithCheck.SimpleCheck的具体类, 只要重载isSatisfied方法即可. 这个方法应该是最简洁的, 和方法三的自定义注解, 各有优劣.

    总结:
      本文实现了String类型到枚举值校验的一种思路, 总体感觉还可以.

  • 相关阅读:
    Codevs 2597 团伙(并查集)
    Codevs 1074 食物链 2001年NOI全国竞赛
    Bzoj 3831 [Poi2014]Little Bird
    Codevs 4600 [NOI2015]程序自动分析
    Codevs 3287 货车运输 2013年NOIP全国联赛提高组(带权LCA+并查集+最大生成树)
    段落排版--中文字间距、字母间距
    段落排版--行间距(行高)
    段落排版--缩进
    文字排版--删除线
    文字排版--下划线
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9207684.html
Copyright © 2011-2022 走看看