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