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