likes
comments
collection
share

利用自定义Validator和枚举类来限定接口的入参

作者站长头像
站长
· 阅读数 38

趁热记录下,给未来的自己

0. 前言

Spring Validation 作为一个参数验证框架,本身提供的注解已经很强大了,能覆盖大部分业务场景需求,比如:@NotNull, @NotBlank, @Min, @Max, @Size, @Email等等。

但是对于更加复杂的业务场景,Spring Validation 自带的这些注解就无能为力了,比如,假设有个接口,其中的一个入参是 String 类型,由于业务需要,该入参值的范围只能在一个允许的 list 中,不在范围中的传入值,直接返回异常。

常规做法是在业务代码里对入参做一层逻辑判断,但是这样做会导致业务代码和校验代码耦合,且开发效率不高,代码十分不美观。 更加优雅的做法是自定义一个 validator 配合枚举类,来实现这个需求。

1. 架构图

利用自定义Validator和枚举类来限定接口的入参

模块说明:

  • 1: DTO - 接收入参的DTO类,使得收到的参数变成一个对象
    • 1-1: private String para 对象加了 @EnumCheck 注解,表示 para 需要被校验
    • 1-2: private String para1 对象未加 @EnumCheck 注解,表示 para1 不需要被校验
  • 2: EnumCheck - 是一个注解类,需要在该类里实现自定义注解
  • 3: EnumUtil - 枚举工具类,通过传入一个枚举参数,判断该参数是否在指定的枚举里,存在则返回枚举,不存在返回null
  • 4: Enum Class - 用户定义的枚举类,用于存放入参的范围
  • 5: 异常逻辑 - 当EnumCheck失败后,进入异常逻辑
  • 6: 后续业务逻辑 - 当EnumCheck成功后,进入后续的业务逻辑

2. 代码说明

2.1 JenkinsProcessBuildReqDTO.java

入参用该DTO类接收,在该类中定义需要被自定义validate的一个或者多个字段。

@Data
public class JenkinsProcessBuildReqDTO {
    
    /**
     * 部署环境
     */
    @EnumCheck(message = "environment输入有误", enumClass = JenkinsProcessEnum.class)
    private String environment;
}

2.2 JenkinsProcessEnum.java

用户自定义的枚举类,用于存放入参的范围,如该枚举类中,限定了 JenkinsProcessBuildReqDTO.environment 的范围在 DEPLOY_SIT, DEPLOY_UAT, DEPLOY_SIT_UAT 这三个枚举里。

import lombok.Getter;

@Getter
public enum JenkinsProcessEnum {

    /**
     * sit 环境
     */
    DEPLOY_SIT("0", "SIT", "sit 环境"),
    /**
     * uat 环境
     */
    DEPLOY_UAT("1", "UAT", "uat 环境"),
    /**
     * sit 和 uat 环境
     */
    DEPLOY_SIT_UAT("2", "SIT_UAT", "sit 和 uat 环境"),
    ;

    private String code;
    private String name;
    private String desc;

    JenkinsProcessEnum(String code, String name, String desc) {
        this.code = code;
        this.name = name;
        this.desc = desc;
    }
}

2.3 EnumUtil.java

该类中提供了一个方法getEnumByParameter: 通过传入一个枚举参数 enumParameter,判断该参数是否在指定的枚举 clazz 里。存在则返回 enumParameter 对应的枚举,不存在返回 null

@Slf4j
public class EnumUtil {

    /**
     * 
     * @author arkMon
     * @date 14:22 2021/2/23
     * @param clazz 传入的枚举类名
     * @param getEnumMethodName 传入的枚举类clazz中的方法名称
     * @param enumParameter 枚举参数
     * @return T 具体的枚举值 或者 null
     */
    public static <T extends Enum<T>> T getEnumByParameter(Class<T> clazz, String getEnumMethodName, Object enumParameter){
        T result = null;
        try{
            //Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例
            T[] arr = clazz.getEnumConstants();
            //获取定义的方法
            Method targetMethod = clazz.getDeclaredMethod(getEnumMethodName);
            if (targetMethod == null) {
                log.error("getEnumMethodName=" + getEnumMethodName + " 不存在");
                return null;
            }
            Object typeVal;
            //遍历枚举定义
            for(T entity:arr){
                if (enumParameter instanceof Integer) {
                    typeVal = Integer.valueOf(String.valueOf(targetMethod.invoke(entity)));
                } else if (enumParameter instanceof String) {
                    //获取传过来方法的
                    typeVal = String.valueOf(targetMethod.invoke(entity)).replace(" ","");
                    //执行的方法的值等于参数传过来要判断的值
                    enumParameter = ((String) enumParameter).replace(" ", "");
                } else {
                    log.error("传入的enumType不是Integer也不是String类型");
                    return null;
                }
                if(typeVal.equals(enumParameter)){
                    //返回这个枚举
                    result = entity;
                    break;
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return result;
    }
}

2.4 EnumCheck.java

自定义验证器的注解。在该注解下的validator类中的isValid方法里,实现自定义验证器的逻辑:

判断传入的参数是否可以通过枚举里的code或者name,用getEnumByParameter方法找到其对应的枚举,能找到返回true,不能找到返回false

@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumCheck.Validator.class)
public @interface EnumCheck {
    String message() default "{enum.value.invalid}"; // 错误信息
    Class<? extends Enum<?>> enumClass(); // 枚举类
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    String enumMethodCode() default "getCode"; // 枚举校验方法
    String enumMethodName() default "getName"; // 枚举校验方法
    boolean allowNull() default false; // 是否允许为空
    
    
    class Validator implements ConstraintValidator<EnumCheck, Object> {
        private Class<? extends Enum<?>> enumClass;
        private String enumMethodCode;
        private String enumMethodName;
        private boolean allowNull;
        @Override
        public void initialize(EnumCheck enumValue) {
            enumMethodCode = enumValue.enumMethodCode();
            enumMethodName = enumValue.enumMethodName();
            enumClass = enumValue.enumClass();
            allowNull = enumValue.allowNull();
        }
        @Override
        public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
            if (value == null) {
                return allowNull;
            }
            if (enumClass == null) {
                return Boolean.TRUE;
            }
            JenkinsProcessEnum enumByParameter = EnumUtil.getEnumByParameter(JenkinsProcessEnum.class, enumMethodCode, value);
            if (enumByParameter != null) {
                return true;
            }
            JenkinsProcessEnum enumByParameter1 = EnumUtil.getEnumByParameter(JenkinsProcessEnum.class, enumMethodName, value);
            if (enumByParameter1 != null) {
                return true;
            }
            return false;
        }
    }
}

Ref

转载自:https://juejin.cn/post/6932376477547790349
评论
请登录