枚举类型的验证
最后修改:2020年3月5日
1.介绍
在教程中Java Bean验证基础,我们看到了我们如何申请javax.验证使用JSR 380各种类型。在教程中Spring MVC自定义验证,我们看到如何创建自定义验证。
在下一个教程中,我们将专注于建设使用自定义注释验证枚举。
2.验证枚举
不幸的是,大多数标准注释不能应用于枚举。
例如,在应用时@Pattern注释给枚举我们收到与Hibernate Validator这样的错误:
javax.validation.unexpectedtypeexception:hv000030:无法找到constraint'javax.validation.constraints.pattern'验证类型'com.baeldung.javaxval.enums.demo.customertomerty金宝搏188体育mertype'。检查“customertypematchespattern”的配置
实际上,唯一可以应用于enum的标准注释是@NotNull和@空值。
3.验证枚举的模式
让我们先定义一个注释来验证枚举的模式:
@target({方法,字段,annotation_type,构造函数,参数,type_use})@retention(运行时)@documented @constraint(validatedby = enumnamepatternvalidator.cuass)public @interface enumnamnamepepattern {string regexp();字符串消息()默认值“必须匹配\”{regexp} \“”;类<?> []组()默认{};类< ?扩展负载>[]负载()默认{};}
现在我们可以简单地使用正则表达式将这个新注释添加到CustomerType枚举:
@enumnamepattern(regexp =“new |默认”)私有CustomerType CoveryerType;
正如我们所看到的,注释实际上并不包含验证逻辑。因此,我们需要提供ConstraintValidator:
public class EnumNamePatternValidator implements ConstraintValidator< enumnamepatterator, Enum>>{私有模式模式;@Override public void initialize(enumnameppattern annotation) {try {pattern = pattern .compile(annotation.regexp());} catch (PatternSyntaxException e){抛出新的IllegalArgumentException(“给定的正则表达式是无效的”,e);@Override public boolean isValid(Enum> value, ConstraintValidatorContext context) {if (value == null) {return true;} Matcher m = pattern.matcher(value.name());返回m.matches ();}}
在此示例中,实现与标准非常相似@Pattern验证器。但是,这一次,我们匹配枚举的名称。
4.验证枚举的子集
用正则表达式匹配枚举不是类型安全的。相反,与枚举的实际值进行比较更有意义。
但是,由于注释的局限性,这样的注释不能成为泛型。这是因为注解的参数只能是特定枚举的具体值,而不是枚举父类的实例。
让我们看,了解如何为我们的验证创建特定的子集验证注释CustomerType枚举:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = CustomerTypeSubSetValidator.class) public @interface CustomerTypeSubset {CustomerType[] anyOf();String message()默认“必须是{anyOf}中的任何一个”;类<?> []组()默认{};类< ?扩展负载>[]负载()默认{};}
然后可以将该注释应用于该类型的枚举CustomerType:
@CustomerTypeSubset(任何= {CustomerType。NEW, CustomerType. old}) private CustomerType CustomerType;
接下来,我们需要定义CustomerTypeSubSetValidator检查给定枚举值的列表是否包含当前值:
公共类CustomertypeSubsetValidator实现ConstraintValidator {private customertype []子集;@override public void initialize(customertypesubset约束){this.subset = contrultaint.anyof();} @Override公共布尔ISValid(CustomerType值,ContramizeValidatorContext上下文){return值== null ||arrays.aslist(子集).Contains(价值);}}
虽然注释必须具体对某个枚举,但我们当然可以分享不同验证器之间的代码。
5.验证字符串是否匹配Enum的值
而不是验证枚举匹配a字符串,我们也可以相反。为此,我们可以创建一个检查何处的注释字符串对特定枚举有效。
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = ValueOfEnumValidator.class) public @interface ValueOfEnum {Class > enumClass ();String message()默认“必须是enum {enumClass}的任何一个”;类<?> []组()默认{};类< ?扩展负载>[]负载()默认{};}
可以将该注释添加到a字符串字段,我们可以传递任何枚举类。
@ValueOfEnum(enumClass = CustomerType.class) private String customerTypeString;
我们定义的ValueOfEnumValidator查询是否字符串(或任何CharSequence进行)是否包含在枚举中:
公共类valueofenumvalidator实现ConstraintValidator {私有列表 AcceptedValues;@override public void initialize(valueofenum注释){acceptedvalues = stream.of(annotation.enumclass()。getEnumConstants()).map(枚举:: name).collect(polormors.tolist());@Override公共布尔IsValid(CharSequence值,ConstraindValidatorContext上下文){if(value == null){return true;}返回AcceptedValues.Contains(value.tostring());}}
这种验证在处理JSON对象时尤其有用。当将一个不正确的值从JSON对象映射到enum时,会出现以下异常:
不能从String值'UNDEFINED'中反序列化CustomerType类型的值:值不是声明的Enum实例名之一:[…]
当然,我们可以处理这个例外。但是,这不允许我们立即报告所有违规行为。
我们可以将值映射到枚举,而不是映射到枚举字符串。然后使用验证器检查它是否与枚举值匹配。
6.把所有东西放在一起
我们现在可以使用我们的任何新验证验证Bean。最重要的是,我们所有的验证都接受空值值。因此,我们也可以将其与注释相结合@NotNull:
public class customertype {@ValueOfEnum(enumClass = CustomerType.class) private String customerTypeString;@NotNull @CustomerTypeSubset(anyOf = {CustomerType。NEW, CustomerType. old}) private CustomerType customerTypeOfSubset;@ enumnameppattern (regexp = "NEW|DEFAULT") private CustomerType customerTypeMatchesPattern;//构造函数,getter等}
在下一节中,我们将看到我们如何测试新的注释。
7.测试枚举的Javax验证
要测试我们的验证器,我们将设置一个支持我们新定义的注释的验证器。我们会客户做我们所有的测试
首先,我们要确保一个有效的客户实例不会导致任何违规行为:
@Test public void whenAllAcceptable_thenShouldNotGiveConstraintViolations(){客户客户=新客户();customer.setCustomerTypeOfSubset (CustomerType.NEW);设置违规= validator.validate(customer);为了(违反).isEmpty ();}
其次,我们希望我们的新注释支持和接受空值值。我们只期望有一个违反。这应该报告customerTypeOfSubset由这件事@NotNull注释:
@Test公共void whenallnull_thenonlynotnullshouldgiveConstraintViolations(){客户客户=新客户();设置违规= validator.validate(客户);assertthat(违规行为)。isequalto(1);assertthat(违规).Anymatch(具有备pertypath(“customertypeofsubset”)。(havemessage(“不可能为null”)));}
最后,我们验证我们的验证器以报告违规行为,当输入无效时:
@test public void whenallinvalid_thenviolationshouldbereported(){客户客户=新客户();customer.setCustomertyPestring(“无效”);customer.setcustomertypeofsubset(customertype.default);customer.setCustomertypeMatchEspattn(Customertype.old);设置违规= validator.validate(客户);assertthat(违规行为)。isequalto(3);Assertthat(违规).Anymatch(happropertypath(“customertypestring”)。(havemessage(“必须是enum class com.baeldung.javaxval.enums.demo.c金宝搏188体育ustomertype)));assertthat(违规).Anymatch(havepropertypath(“customertypeofsubset”).and(havemessage(“必须是任何[新,旧]”))));Assertthat(违规).Anymatch(具有备pertypath(“customertypeMatchattnattern”).and(haveMessage(“必须匹配”新|默认\“”))));}
8.结论
在本教程中,我们介绍了使用自定义注释和验证器验证枚举的三个选项。
首先,我们学习了如何使用正则表达式验证枚举的名称。
其次,我们讨论了对特定枚举值子集的验证。我们还解释了为什么不能构建通用注释来实现这一点。
最后,我们还介绍了如何为字符串构建验证器。为了检查是否a字符串符合给定枚举的特定值。
与往常一样,本文的完整源代码是可用的在Github。