到目前为止,还不能或不容易重用约束以创建更复杂的约束。

新的规范草案引入了约束组合的概念。组合对于以下三个主要事物很有用

  • 重用更原始的约束来构建新约束,避免重复
  • 为给定的约束定义细粒度错误报告
  • 展示约束是如何组合的,并描述其原始块

最后一点特别有趣。约束实现是黑盒,回答的问题是:“这个值有效吗?”当约束需要在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规范将包含一组约束,工具可以依赖这些约束。

请在我们的论坛上告诉我们您的看法。


返回顶部