枚举校验

356 阅读2分钟

出发点:开发中发现前端传来的枚举的index数值没有进行校验,传入不存在的index值也能传到后端

1、直接在后端业务代码中进行处理枚举的校验侵入性比较大,复用性不高

2、模仿lombok的@NotNull注解,直接在domin中进行注解,代码侵入性不高,复用性比较高,直接将message报给前端

——————————————————————————————————————————

|注解基本知识|

元注解(注解的注解)(copy自zhuanlan.zhihu.com/p/111762882

@Target

  • @Target元注解表示我们的注解作用的范围,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型
  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)

@Retention

  • @Retention表示注解存在的生命周期,是保留在源码(编译期)时期,还是字节码(类加载)或者运行期(JVM中运行)时期。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
  • @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含 (.java)
  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得(.class)
  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到(内存中的字节码)
  • 自定义注解必须选择@Retention(RetentionPolicy.RUNTIME)

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解
如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。

@Documented

  • Document的意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Constraint(validatedBy = EnumValidator.class)

指定实际校验的类

以下是代码实现

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {EnumValidator.class})
public @interface EnumValid {
    String message() default "";
    // 作用参考@Validated和@Valid的区别
    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * 目标枚举类
     */
    Class<?> target() default Class.class;

    /**
     * 是否忽略空值
     */
    boolean ignoreEmpty() default true;
}

//实现ConstraintValidator接口
public class EnumValidator implements ConstraintValidator<EnumValid, Integer> {

    private EnumValid annotation;

    @Override
    public void initialize(EnumValid constraintAnnotation) {
        annotation = constraintAnnotation;
    }

    @Override
    public boolean isValid(Integer val, ConstraintValidatorContext constraintValidatorContext) {
        //获取注解的target值
        Class<?> target = annotation.target();
        //获取注解的ignoreEmpty值
        boolean ignoreEmpty = annotation.ignoreEmpty();
        if ( target.isEnum() && (val != null || !ignoreEmpty) ){
            //获取对应枚举类的枚举值域
            Object[] enumValues = target.getEnumConstants();
            //如果前端传来的值在枚举值域内则返回true
            for( Object enumValue:enumValues ){
                if( enumValue.toString().equals(String.valueOf(val)) ){
                    return true;
                }
            }
        }else{
            return true;
        }
        //前端传来的值不在枚举值域内则返回false
        return false;
    }
}

@EnumValid(target = ScreenProtectorPictureSizeTypeEnum.class,message = "图片尺寸类型状态值错误")
    private Integer pictureSizeType;