基于自定义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

java工程师

139 声望
24 粉丝
0 条评论
推荐阅读
hdfs的异构存储
在我们实际的开发过程中,我们的某些数据可能经常使用,但是过了一段时间,这个数据就不怎么使用了,即我们的数据存在一个热、温、冷等这些特性。那么针对数据的热度,我们可以采用不同的策略,存储到不同的存储...

huan1993阅读 113

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败
遍历中删除List或Queue等数据结构中,如何一边遍历一遍删除?1. 常犯错误ArrayList可能没遇到坑过的人会用增强for循环这么写: {代码...} 但是一运行,结果却抛 java.util.ConcurrentModificationException 异常即...

weiweiyi6阅读 954

使用springboot+angular实现web端微信扫码登陆
现在微信的使用用户越来越多,如果网站添加上微信登录,就能节省很多用户注册时间,极大缩小了注册流程。会让用户觉得特别方便。接下来我们就说一下怎么来实现Web端微信扫码登录。

郝泽龙_HZ6阅读 915

利用Docker部署管理LDAP及其初次使用
前言:本周主要写了gitlabWebhook转github的项目,总体上没有遇到什么大问题,这周接触到了LDAP,于是就花时间实际操作了解了一下。

李明5阅读 1.2k

记录本周问题
项目里两个地方都用到了hashmap。但是感觉自己用的时候并没有感觉非常的清晰。同时发现hashmap有线程不安全问题,而自己用的时候就是多线程来使用。于是在这里介绍一下。

weiweiyi5阅读 823

Spring Aop 动态代理
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施...

KerryWu5阅读 8.2k评论 1

记录多项目共用一个公众号逻辑修改
前言微信扫码登陆,前段时间写完微信扫码登录后,由于有多个项目都需要微信登录,而公众号的数量有限。 所以需要研究一下多个项目使用同一个公众号登录。思路原来的思路: 每个后台与微信服务器之间进行通信, 需要...

weiweiyi4阅读 796评论 1

java工程师

139 声望
24 粉丝
宣传栏