Oval框架如何校验枚举类型的一种思路

 

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 构建枚举类
@Getter
@AllArgsConstructor
enum EType {
 
    ONE("one"),
    TWO("two");
 
    private String type;
 
}
 
// 具体的实体类
@Getter
@Setter
@AllArgsConstructor
class TNode {
 
    // 和枚举类EType是一一对应的关系
    private String type;
 
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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注解来实现, 具体如下:

1
2
3
4
5
6
7
8
9
@Getter
@Setter
@AllArgsConstructor
class TNode {
 
    @MemberOf(value = {"one", "two"}, message = "name not int range[one, two]")
    private String type;
 
}

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

 

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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框架也提供了自定义注解的能力.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@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注解, 直接支持函数级校验.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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类型到枚举值校验的一种思路, 总体感觉还可以.

 

posted on   mumuxinfei  阅读(960)  评论(0编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示