1. Introduction
1.介绍
In the tutorial Java Bean Validation Basics, we saw how we can apply javax validations using JSR 380 to various types. And in the tutorial Spring MVC Custom Validation, we saw how to create custom validations.
在教程Java Bean 验证基础中,我们看到了如何使用JSR 380对各种类型应用javax验证。而在教程Spring MVC自定义验证中,我们看到如何创建自定义验证。
In this next tutorial, we’ll focus on building validations for enums using custom annotations.
在接下来的教程中,我们将专注于使用自定义注释为枚举构建 验证。
2. Validating Enums
2.验证枚举
Unfortunately, most standard annotations can not be applied to enums.
不幸的是,大多数标准注释不能应用于枚举。
For example, when applying the @Pattern annotation to an enum we receive an error like this one with Hibernate Validator:
例如,当把@Pattern注解应用于一个枚举时,我们在Hibernate验证器中会收到类似这样的错误。
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'
Actually, the only standard annotations which can be applied to enum’s are @NotNull and @Null.
实际上,唯一可以应用于枚举的标准注释是@NotNull和@Null.。
3. Validating the Pattern of an Enum
3.验证一个枚举的模式
Let’s start by defining an annotation to validate the pattern of an enum:
让我们从定义一个注解开始,以验证一个枚举的模式:。
@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 {};
}
Now we can simply add this new annotation using a regular expression to our CustomerType enum:
现在我们可以简单地使用正则表达式向我们的CustomerType枚举添加这个新注释。
@EnumNamePattern(regexp = "NEW|DEFAULT")
private CustomerType customerType;
As we can see, the annotation does not actually contain the validation logic. Therefore, we need to provide a ConstraintValidator:
正如我们所看到的,该注释实际上并不包含验证逻辑。因此,我们需要提供一个ConstraintValidator:。
public class EnumNamePatternValidator implements ConstraintValidator<EnumNamePattern, Enum<?>> {
private Pattern pattern;
@Override
public void initialize(EnumNamePattern annotation) {
try {
pattern = Pattern.compile(annotation.regexp());
} catch (PatternSyntaxException e) {
throw new IllegalArgumentException("Given regex is invalid", e);
}
}
@Override
public boolean isValid(Enum<?> value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
Matcher m = pattern.matcher(value.name());
return m.matches();
}
}
In this example, the implementation is very similar to the standard @Pattern validator. However, this time, we match the name of the enum.
在这个例子中,实现与标准@Pattern验证器非常相似。但是,这一次,我们匹配了枚举的名称。
4. Validating a Subset of an Enum
4.验证一个枚举的子集
Matching an enum with a regular expression is not type-safe. Instead, it makes more sense to compare with the actual values of an enum.
用正则表达式来匹配一个枚举不是类型安全的。相反,与一个枚举的实际值进行比较更有意义。
However, because of the limitations of annotations, such an annotation cannot be made generic. This is because arguments for annotations can only be concrete values of a specific enum, not instances of the enum parent class.
然而,由于注解的限制,这样的注解不能被做成通用的。这是因为注释的参数只能是特定枚举的具体数值,而不是枚举父类的实例。
Let’s see how to create a specific subset validation annotation for our CustomerType enum:
让我们看看如何为我们的CustomerType枚举创建一个特定的子集验证注释。
@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 {};
}
This annotation can then be applied to enums of the type CustomerType:
然后这个注解可以应用于CustomerType类型的枚举。
@CustomerTypeSubset(anyOf = {CustomerType.NEW, CustomerType.OLD})
private CustomerType customerType;
Next, we need to define the CustomerTypeSubSetValidator to check whether the list of given enum values contains the current one:
接下来,我们需要定义CustomerTypeSubSetValidator,以检查给定枚举值的列表是否包含当前枚举值。
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);
}
}
While the annotation has to be specific for a certain enum, we can of course share code between different validators.
虽然注释必须是针对某个枚举的,但我们当然可以在不同的验证器之间共享共享代码。
5. Validating That a String Matches a Value of an Enum
5.验证一个字符串与一个枚举的值是否匹配
Instead of validating an enum to match a String, we could also do the opposite. For this, we can create an annotation that checks if the String is valid for a specific enum.
与其验证一个枚举以匹配一个String,我们还可以做相反的事情。为此,我们可以创建一个注解,检查String是否对特定枚举有效。
@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 {};
}
This annotation can be added to a String field and we can pass any enum class.
这个注解可以被添加到String字段,我们可以传递任何枚举类。
@ValueOfEnum(enumClass = CustomerType.class)
private String customerTypeString;
Let’s define the ValueOfEnumValidator to check whether the String (or any CharSequence) is contained in the enum:
让我们定义ValueOfEnumValidator来检查String(或任何CharSequence)是否包含在枚举中。
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());
}
}
This validation can especially be useful when working with JSON objects. Because the following exception appears, when mapping an incorrect value from a JSON object to an enum:
这种验证在处理JSON对象时尤其有用。因为当把一个不正确的值从JSON对象映射到一个枚举时,会出现下面的异常。
Cannot deserialize value of type CustomerType from String value 'UNDEFINED': value not one
of declared Enum instance names: [...]
We can, of course, handle this exception. However, this does not allow us to report all violations at once.
当然,我们可以处理这种例外。然而,这并不允许我们一次报告所有的违规行为。
Instead of mapping the value to an enum, we can map it to a String. We’d then use our validator to check whether it matches any of the enum values.
我们可以不把值映射到一个枚举中,而是把它映射到一个String。然后我们用我们的验证器来检查它是否与任何一个枚举值相匹配。
6. Bringing It All Together
6.把所有的东西集中起来
We can now validate beans using any of our new validations. Most importantly, all of our validations accept null values. Consequently, we can also combine it with the annotation @NotNull:
我们现在可以使用任何新的验证方法来验证Bean。最重要的是,我们所有的验证都接受null值。因此,我们也可以将其与注解@NotNull结合起来。
public class Customer {
@ValueOfEnum(enumClass = CustomerType.class)
private String customerTypeString;
@NotNull
@CustomerTypeSubset(anyOf = {CustomerType.NEW, CustomerType.OLD})
private CustomerType customerTypeOfSubset;
@EnumNamePattern(regexp = "NEW|DEFAULT")
private CustomerType customerTypeMatchesPattern;
// constructor, getters etc.
}
In the next section, we’ll see how we can test our new annotations.
在下一节,我们将看到如何测试我们的新注释。
7. Testing Our Javax Validations for Enums
7.测试我们对枚举的Javax验证
In order to test our validators, we’ll set up a validator, which supports our newly defined annotations. We’ll the Customer bean for all our tests.
为了测试我们的验证器,我们将设置一个验证器,它支持我们新定义的注释。我们将用Customer bean来进行所有的测试。
First, we want to make sure that a valid Customer instance does not cause any violations:
首先,我们要确保一个有效的Customer实例不会造成任何违规。
@Test
public void whenAllAcceptable_thenShouldNotGiveConstraintViolations() {
Customer customer = new Customer();
customer.setCustomerTypeOfSubset(CustomerType.NEW);
Set violations = validator.validate(customer);
assertThat(violations).isEmpty();
}
Second, we want our new annotations to support and accept null values. We only expect a single violation. This should be reported on customerTypeOfSubset by the @NotNull annotation:
第二,我们希望我们的新注解能够支持并接受null值。我们只期望有一次违反。这应该被customerTypeOfSubset的@NotNull注解所报告。
@Test
public void whenAllNull_thenOnlyNotNullShouldGiveConstraintViolations() {
Customer customer = new Customer();
Set<ConstraintViolation> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(1);
assertThat(violations)
.anyMatch(havingPropertyPath("customerTypeOfSubset")
.and(havingMessage("must not be null")));
}
Finally, we validate our validators to report violations, when the input is not valid:
最后,我们验证我们的验证器,在输入无效的情况下,报告违规。
@Test
public void whenAllInvalid_thenViolationsShouldBeReported() {
Customer customer = new Customer();
customer.setCustomerTypeString("invalid");
customer.setCustomerTypeOfSubset(CustomerType.DEFAULT);
customer.setCustomerTypeMatchesPattern(CustomerType.OLD);
Set<ConstraintViolation> violations = validator.validate(customer);
assertThat(violations.size()).isEqualTo(3);
assertThat(violations)
.anyMatch(havingPropertyPath("customerTypeString")
.and(havingMessage("must be any of enum class com.baeldung.javaxval.enums.demo.CustomerType")));
assertThat(violations)
.anyMatch(havingPropertyPath("customerTypeOfSubset")
.and(havingMessage("must be any of [NEW, OLD]")));
assertThat(violations)
.anyMatch(havingPropertyPath("customerTypeMatchesPattern")
.and(havingMessage("must match \"NEW|DEFAULT\"")));
}
8. Conclusion
8.结论
In this tutorial, we covered three options to validate enums using custom annotations and validators.
在本教程中,我们介绍了使用自定义注解和验证器验证枚举的三个选项。
First, we learned how to validate the name of an enum using a regular expression.
首先,我们学习了如何使用正则表达式来验证一个枚举的名称。
Second, we discussed a validation for a subset of values of a specific enum. We also explained why we cannot build a generic annotation to do this.
其次,我们讨论了对一个特定枚举的值的子集的验证。我们还解释了为什么我们不能建立一个通用的注释来做这件事。
Finally, we also looked at how to build a validator for strings. In order to check whether a String conforms to a particular value of a given enum.
最后,我们还研究了如何为字符串建立一个验证器。为了检查一个String是否符合一个给定枚举的特定值。
As always, the full source code of the article is available over on Github.
一如既往,该文章的完整源代码可在Github上获得。