第一篇介绍了Bean Validation基本使用。本文主要介绍通过自定义Validation注解来验证枚举。系统提供的默认注解
大多数注解无法用来验证枚举。例如,使用@Pattern注解验证enum将会得到错误提示。
javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint
'javax.validation.constraints.Pattern' validating type 'com.baeldung.javaxval.enums.demo.CustomerType'.
Check configuration for 'customerTypeMatchesPattern'
因此为了完成验证,需要自定义注解。如何自定义在Bean Validation这里已经介绍,本文主要介绍验证枚举的方法。
这节,介绍几个验证枚举的例子。包括验证枚举名称、验证枚举成员类型及验证字符串值是否与枚举名匹配。
验证名称,即通过枚举的name方法获取枚举名,然后通过正则表达式看是否与名称的模式匹配。这里关键是在实现Validator时,值的类型要指定为Enum,随后获取枚举的公用方法对其验证。
ConstraintValidator
>
enum CustomerType {
T,S,NEW;
}
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = EnumNamePatternValidator.class)
public @interface EnumNamePattern {
String regexp();
String message() default "must match \"{regexp}\"";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EnumNamePatternValidator implements ConstraintValidator<EnumNamePattern, Enum<?>> {
private Pattern pattern;
@Override
public boolean isValid(Enum<?> value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return pattern.matcher(value.name()).matches();
}
@Override
public void initialize(EnumNamePattern annotation) {
try {
pattern = Pattern.compile(annotation.regexp());
} catch (PatternSyntaxException e) {
throw new IllegalArgumentException("Given regex is invalid", e);
}
}
}
@Test
public void test() {
T t = new T();
t.setM(CustomerType.S);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> violations = validator.validate(t);
for (ConstraintViolation<T> violation : violations) {
System.out.println(violation.getMessage());
}
}
上一个例子中看到如何验证枚举名称。如下CustomerType枚举有三种:
enum CustomerType {
T,S,NEW;
}
当定义一个枚举时我们希望验证是否为 T或S这两种成员。此时我们要通过自定义枚举传入CustomerType枚举数组
CustomerType[] anyOf();
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = CustomerTypeSubSetValidator.class)
public @interface CustomerTypeSubset {
CustomerType[] anyOf();
String message() default "must be any of {anyOf}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
在验证变量时指定期望的类型
@CustomerTypeSubset(anyOf = {CustomerType.NEW, CustomerType.OLD})
private CustomerType customerType;
随后在验证时判断是否属于指定的集合即可。
public class CustomerTypeSubSetValidator implements ConstraintValidator<CustomerTypeSubset, CustomerType> {
private CustomerType[] subset;
@Override
public void initialize(CustomerTypeSubset constraint) {
this.subset = constraint.anyOf();
}
@Override
public boolean isValid(CustomerType value, ConstraintValidatorContext context) {
return value == null || Arrays.asList(subset).contains(value);
}
}
如下这个枚举:
enum CustomerType {
T,S,NEW;
}
对于字符串String s = “T”,即验证CustomerType中是否有一个成员的名称为T。显然本例中有。为了验证,我们就需要拿到这个枚举的各个成员。而Class对象的getEnumConstants()方法即提供了这样的功能。对于CustomerType,该方法会返回一个数组包含T,S,NEW。因此最终实现如下:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = ValueOfEnumValidator.class)
public @interface ValueOfEnum {
Class<? extends Enum<?>> enumClass();
String message() default "must be any of enum {enumClass}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@ValueOfEnum(enumClass = CustomerType.class)
private String customerTypeString;
public class ValueOfEnumValidator implements ConstraintValidator<ValueOfEnum, CharSequence> {
private List<String> acceptedValues;
@Override
public void initialize(ValueOfEnum annotation) {
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
}
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}
例2.3可以看到,既然能拿到Class对象,那么还能拿到一些其它额外的信息。例如获取方法名。调用枚举的方法,验证某个数字是否是为一个枚举中定义的code。最终实功能更强大的自定义注解。同理,对其非枚举类型,思路也是一样。
通过2的3个例子我们可以看到,为了验证枚举,需要完成三件事。
2是一个关键,只有通过不同渠道获取足够的信息,才能完成丰富的验证。
[1].https://www.baeldung.com/javax-validations-enums
[2].https://stackoverflow.com/questions/6294587/java-string-validation-using-enum-values-and-annotation