枚举类 注解校验

本文探讨了在Java中对枚举类型进行校验的两种方法,包括对离散值和枚举值的校验。介绍了自定义校验注解和校验器的实现,并讨论了使用反射进行枚举校验的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


java validation内没有对枚举的校验工具,但是离散的枚举值校验确实是有必要的,这里列两种枚举的校验方法,实际大同小异。

前提知识

首先,javax.validation包是提供了方便的自定义校验的入口的,就是javax.validation.ConstraintValidator
具体使用见下方。

1. 对离散值(非枚举)的校验

若离散的值尚未形成枚举,这种校验反而好做一点,因为无需引入反射这种黑魔法。

校验注解
@Documented
@Constraint(validatedBy = {})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface EnumValidateAnnotation {

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

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

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

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

}
校验实现
public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation,String> {

    private List<String> enumStrings;

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

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

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

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

2. 对枚举的校验

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

没办法,用反射吧。

校验注解
@Documented
@Constraint(validatedBy = {})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface EnumValidateAnnotation {

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

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

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

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

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

}
校验实现
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;
        }
        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;
    }
}

使用示例
public enum FromEnum {

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

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

    @Getter
    String from;

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

}



@NotNull(message = "请求来源不能为空")
@EnumValidateAnnotation(enumClass = FromEnum.class, method = "getFrom", message = "上报来源错误")
String from;
缺点与一丢丢改进

使用反射,缺点明显,就是性能下降,尤其上面代码对每一个枚举反射调用方法

改进的话,就是在枚举中写一个特定方法,专门用来做这种入参到枚举值的转换,转换成功,则说明入参正确,否则,说明入参错误

使用示例
public interface EnumValidate<T>{
    boolean inEnum(T value);
}

public enum FromEnum implements EnumValidate<String>{

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

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

    @Getter
    String from;

    FromEnum(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;   
    }
}


@NotNull(message = "请求来源不能为空")
@EnumValidateAnnotation(enumClass = FromEnum.class, message = "上报来源错误")
String from;
校验器实现
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);
    }
}

补充内容

校验器使用

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());
    }
}
### Java 中校验枚举值的注解方法 在 Java 的 Bean Validation 框架中,默认提供的注解(如 `@NotNull` 或 `@Pattern`)通常不适用于直接验证枚举类型。这是因为框架本身并未提供专门针对枚举类型的内置约束机制[^3]。 #### 自定义注解实现枚举校验 为了满足这一需求,可以通过创建自定义注解并结合相应的验证器类来完成对枚举类型的校验。以下是具体实现方式: 1. **定义自定义注解** 创建一个新的注解,例如 `@EnumValue`,并通过元注解指定其作用范围以及关联的验证逻辑。 ```java import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = EnumValidator.class) // 关联验证器 public @interface EnumValue { String message() default "Invalid enum value"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 定义允许的枚举类 Class<? extends Enum<?>> enumClass(); } ``` 2. **编写验证器类** 实现具体的验证逻辑,确保传入的值属于目标枚举类型中的合法成员。 ```java import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class EnumValidator implements ConstraintValidator<EnumValue, Object> { private Enum<?>[] values; @Override public void initialize(EnumValue constraintAnnotation) { this.values = constraintAnnotation.enumClass().getEnumConstants(); // 获取所有枚举常量 if (this.values == null || this.values.length == 0) { throw new IllegalArgumentException("The given enum is empty!"); } } @Override public boolean isValid(Object value, ConstraintValidatorContext context) { if (value == null) { // 如果字段可以为空,则返回 true return true; } for (Enum<?> e : values) { if (e.name().equals(value.toString())) { // 验证输入值是否匹配任意枚举名称 return true; } } return false; // 输入值非法 } } ``` 3. **应用自定义注解** 将新定义的注解应用于需要校验的字段上。 ```java public enum CustomerType { INDIVIDUAL, BUSINESS } public class Customer { @EnumValue(enumClass = CustomerType.class, message = "Unsupported customer type") private String customerType; // Getter and Setter methods... } ``` 4. **测试验证功能** 使用 Hibernate Validator 等工具运行验证流程,确认枚举值的有效性。 ```java import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import javax.validation.Validation; import javax.validation.ValidatorFactory; import javax.validation.Validator; import javax.validation.ConstraintViolation; public class MainApp { public static void main(String[] args) { ValidatorFactory factory = Validation.byDefaultProvider() .configure() .messageInterpolator(new ResourceBundleMessageInterpolator()) .buildValidatorFactory(); Validator validator = factory.getValidator(); Customer customer = new Customer(); customer.setCustomerType("INVALID_TYPE"); Set<ConstraintViolation<Customer>> violations = validator.validate(customer); for (ConstraintViolation<Customer> violation : violations) { System.out.println(violation.getMessage()); // 输出: Unsupported customer type } } } ``` 以上代码展示了如何通过自定义注解和验证器实现对枚举类型的校验。这种方法不仅灵活而且易于扩展,能够适应多种业务场景的需求[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值