到目前为止,还不能或不容易重用约束以创建更复杂的约束。
新的规范草案引入了约束组合的概念。组合对于以下三个主要事物很有用
- 重用更原始的约束来构建新约束,避免重复
- 为给定的约束定义细粒度错误报告
- 展示约束是如何组合的,并描述其原始块
最后一点特别有趣。约束实现是黑盒,回答的问题是:“这个值有效吗?”当约束需要在Java世界之外应用或在不同的元数据模型上应用时,黑盒没有任何帮助。没有办法知道这一点@OrderNumber实际上对数字的长度以及CRC验证施加了一些限制。
组合有助于通过提供访问约束原始组成部分的方式来解决这个问题。让我们首先看看如何定义一个组合约束。
定义组合约束
要定义组成主要约束的约束列表,只需将组合约束注解注释到主要约束注解上。
@Numerical @Size(min=5, max=5) @ConstraintValidator(FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface FrenchZipCode { String message() default "Wrong zipcode"; String[] groups() default {}; }
当@FrenchZipCode放在一个属性上时,其值将验证@Numerical, @Size(min=5, max=5)以及约束实现FrenchZipcodeValidator:所有组成约束都会被验证,以及主要约束的逻辑。请注意,组成约束本身也可以由约束组成。
每个违反的约束将生成一个单独的错误报告,这在您想向用户展示细粒度报告时很有用。但这种情况可能相当令人困惑,在某些情况下,单个错误报告可能更合适。您可以使用@ReportAsSingleInvalidConstraint注解来强制Bean Validation在任何一个组成约束失败时生成单个错误报告。
@Numerical @Size(min=5, max=5) @ReportAsSingleInvalidConstraint @ConstraintValidator(FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface FrenchZipCode { String message() default "Wrong zipcode"; String[] groups() default {}; }
在过去的两个示例中,组成注解参数在声明时不能调整。如果邮政编码始终是5位,这很好。但如果大小可以根据属性调整,会发生什么情况?规范提供了一种方法,可以通过使用@OverridesParameter注解来强制Bean Validation在任何一个组成约束失败时生成单个错误报告。
@Numerical @Size //arbitrary parameter values @ConstraintValidator(FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface FrenchZipCode { String message() default "Wrong zipcode"; String[] groups() default {}; @OverridesParameters( { @OverridesParameter(constraint=Size.class, parameter="min") @OverridesParameter(constraint=Size.class, parameter="max") } ) int size() default 5; @OverridesParameter(constraint=Size.class, parameter="message") String sizeMessage() default "{error.zipcode.size}"; @OverridesParameter(constraint=Numerical.class, parameter="message") String numericalMessage() default "{error.zipcode.numerical}"; }
注解从组成注解的参数中“覆盖”一个参数。
@FrenchZipcode(size=9, sizeMessage="Zipcode should be of size {value}")
以下声明
@Numerical @Size(min=9, max=9, message="Zipcode should be of size {value}") @ConstraintValidator(FrenchZipcodeValidator.class) @Documented @Target({ANNOTATION_TYPE, METHOD, FIELD}) @Retention(RUNTIME) public @interface FrenchZipCode { String message() default "Wrong zipcode"; String[] groups() default {}; }
等价于以下定义/声明组合
现在让我们看看工具如何使用这些额外的信息。
使用元数据API探索组合约束使用元数据API,您可以探索给定对象或属性上的约束列表。每个约束由一个ConstraintDescriptor使用元数据API,您可以探索给定对象或属性上的约束列表。每个约束由一个描述。它列出了所有组成约束,并为每个提供了一个使用元数据API,您可以探索给定对象或属性上的约束列表。每个约束由一个对象。它尊重被覆盖的参数(即使用@OverridesParameter):返回的注解和参数值包含覆盖的值。
ElementDescriptor ed = addressValidator.getConstraintsForProperty("zipcode"); for ( processConstraintDescriptor cd : ed.getConstraintDescriptors() ) { processConstraintDescriptor(cd); //check all constraints on zip code } public void processConstraintDescriptor(processConstraintDescriptor cd) { //Size.class is understood by the tool if ( cd.getAnnotation().getAnnotationType().equals( Size.class ) ) { Size m = (Size) cd.getAnnotation(); column.setLength( m.max() ); //read and use the metadata } for (ConstraintDescriptor composingCd : cd.getComposingConstraints() ) { processConstraintDescriptor(cd); //check composing constraints recursively } }
当使用以下声明
@FrenchZipCode(size=10) public String zipCode;
时,工具将设置zipCode列的长度为10。
虽然工具不知道@FrenchZipCode和@Numerical的含义,但它知道如何使用@Max。JavaScript生成库或持久化工具通常理解约束的核心子集。如果一个复杂约束由一个或多个这些核心子集约束组成,它可以通过Java Persistence等部分理解和处理。
这就是强烈建议在更原始的基础上构建复杂约束的原因之一。Bean Validation规范将包含一组约束,工具可以依赖这些约束。
请在我们的论坛上告诉我们您的看法。