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

一、背景

在我们系统中,有部分字段的值是枚举类型的,但是请求参数中一般不会直接使用枚举来进行接收,而是使用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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容