枚举-注解校验(2)

java validation内没有对枚举的校验工具,但是离散的枚举值校验确实是有必要的,这里列两种枚举的校验方法,实际大同小异。首先,javax.validation包是提供了方便的自定义校验的入口的,就是javax.validation.ConstraintValidator。
1. 对离散值(非枚举)的校验
若离散的值尚未形成枚举,这种校验反而好做一点,因为无需引入反射这种黑魔法。
校验注解

package com.springboot.study.tests.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:49
 * @Version: 1.0
 * @Description:
 */
@Documented
@Constraint(validatedBy = {EnumStringValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidateAnnotation {

    /**
     * 校验报错信息
     * @return
     */
    String message() default "";

    /**
     * 校验分组
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * 附件 用于扩展
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 允许的枚举值,所有类型转成String 存储
     * @return
     */
    String[] enums() default {};

}

校验实现 

package com.springboot.study.tests.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;

/**
 * @Author: guodong
 * @Date: 2021/7/24 12:51
 * @Version: 1.0
 * @Description:
 */
public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation,String> {

    private List<String> enumStrings;

    @Override
    public void initialize(EnumValidateAnnotation constraintAnnotation) {
        enumStrings = Arrays.asList(constraintAnnotation.enums());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        // 对于Integer,将其转化为String 后比较
        return enumStrings.contains(value);
    }
}

使用示例

package com.springboot.study.tests.annotation;

import javax.validation.constraints.NotNull;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:34
 * @Version: 1.0
 * @Description:
 */
public class Student {

    /**
     * 来源
     */
    @NotNull(message = "请求来源不能为空")
    // 这里将枚举值FromEnum的String表示放入注解的enums中
    // 如果是离散值的话,更加合适,因为不用关心枚举值和注解 enums保持一致
    @EnumValidateAnnotation(enums = {"from1", "from2"}, message = "上报来源错误")
    String from;

    @MyAnnotation(age = 26)
    public void test() {
    }

}

缺点
这种使用方式,缺点比较明显,就是如果要修改的话,不仅枚举的地方要修改,使用注解校验的地方因为只写了String 的值,所以注解的enums参数也需要修改,两个地方不能放到一起维护,有遗漏的防线。

2. 对枚举的校验
枚举的校验就有点伤,注解的声明是不允许泛型的,这意味着我们无法优雅的直接将不同的泛型用同一个校验器校验,除非使用黑魔法。甚至,注解的属性,连抽象类、接口、集合都不允许使用,所以想传入一个lambda表达式,自定义处理方式都是不行的。没办法,用反射吧。
校验注解

package com.springboot.study.tests.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:04
 * @Version: 1.0
 * @Description:
 */
@Documented
@Constraint(validatedBy = {EnumStringValidator2.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidateAnnotation2 {

    /**
     * 校验报错信息
     * @return
     */
    String message() default "";

    /**
     * 校验分组
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * 附件 用于扩展
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 允许的枚举
     * @return
     */
    Class<? extends Enum<?>> enumClass();

    /**
     * 校验调用的枚举类的方法
     * @return
     */
    String method();

}

校验实现

package com.springboot.study.tests.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:07
 * @Version: 1.0
 * @Description:
 */
public class EnumStringValidator2 implements ConstraintValidator<EnumValidateAnnotation2, String> {

    private String methodStr;

    private Class<? extends Enum> enumClass;

    @Override
    public void initialize(EnumValidateAnnotation2 constraintAnnotation) {
        methodStr = constraintAnnotation.method();
        enumClass = constraintAnnotation.enumClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        try {
            // 反射获取校验需要调用的枚举的方法
            Method method = enumClass.getMethod(methodStr);
            boolean result = false;
            // 获取所有的枚举值
            Enum[] enums = enumClass.getEnumConstants();
            // 对每一个枚举值调用 校验的方法,获取返回值,和入参作比较
            for (Enum e : enums) {
                Object returnValue = method.invoke(e);
                result = Objects.equals(returnValue, value);
            }
            return result;
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            // 异常处理
        } catch (Throwable throwable) {
            // 异常处理
        }
        return false;
    }

}

使用示例

package com.springboot.study.tests.annotation;

import lombok.Getter;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:11
 * @Version: 1.0
 * @Description:
 */
public enum FromEnum {

    /**
     * 来源1
     */
    form1("form1"),

    /**
     * 来源2
     */
    form2("form2");

    @Getter
    String from;

    FromEnum(String from) {
        this.from = from;
    }

}
package com.springboot.study.tests.annotation;

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:34
 * @Version: 1.0
 * @Description:
 */
@Data
public class Student {

    @NotNull(message = "请求来源不能为空")
    @EnumValidateAnnotation2(enumClass = FromEnum.class, method = "getFrom", message = "上报来源错误")
    String from;

    @MyAnnotation(age = 26)
    public void test() {
    }

}

缺点与一丢丢改进
使用反射,缺点明显,就是性能下降,尤其上面代码对每一个枚举反射调用方法,改进的话,就是在枚举中写一个特定方法,专门用来做这种入参到枚举值的转换,转换成功,则说明入参正确,否则,说明入参错误。
使用示例

package com.springboot.study.tests.annotation;

public interface EnumValidate<T>{

    boolean inEnum(T value);

}
package com.springboot.study.tests.annotation;

import lombok.Getter;

/**
 * @Author: guodong
 * @Date: 2021/7/24 16:04
 * @Version: 1.0
 * @Description:
 */
public enum FromEnum2 implements EnumValidate<String>{

    /**
     * 来源1
     */
    form1("form1"),

    /**
     * 来源2
     */
    form2("form2");

    @Getter
    String from;

    FromEnum2(String from) {
        this.from = from;
    }

    public static FromEnum of(String desc) {
        for (FromEnum from : FromEnum.values()) {
            if (from.getFrom().equalsIgnoreCase(desc)) {
                return from;
            }
        }
        return null;
    }

    @Override
    public boolean inEnum(String value){
        return of(value) != null;
    }

}

校验器实现 

public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation, String> {

    private String methodStr;

    private Class<? extends Enum> enumClass;

    @Override
    public void initialize(EnumValidateAnnotation constraintAnnotation) {
        methodStr = constraintAnnotation.method();
        enumClass = constraintAnnotation.enumClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        EnumValidate[] enums = enumClass.getEnumConstants();
        if(enums ==null || enums.length == 0){
          return false;   
        }
        return enums[0].inEnum(value);
    }
}

补充内容
校验器使用,可以使用这种方式对枚举注解入参进行校验,参数是否合法化,或者是使用springboot的注解@valid等注解对入参进行校验。

private static Validator validator = Validation.byProvider(HibernateValidator.class)
        .configure()
        .failFast(true)
        .buildValidatorFactory()
        .getValidator();

public static <T> void validParam(T param) {
    validParamNonNull(param);
    Set<ConstraintViolation<T>> constraintViolations = validator.validate(param, Default.class);
    StringBuilder sb = new StringBuilder();
    if (constraintViolations != null && !constraintViolations.isEmpty()) {
        for (ConstraintViolation<T> constraintViolation : constraintViolations) {
            sb.append(constraintViolation.getPropertyPath())
                .append(":")
                .append(constraintViolation.getMessage())
                .append(".");
        }
        throw new IllegalArgumentException(sb.toString());
    }
}

 

posted @ 2021-07-24 10:52  郭慕荣  阅读(743)  评论(4编辑  收藏  举报