Hibernate Validator 中文操作指南
10. Using constraint metadata
Jakarta Bean Validation 规范不仅提供了校验引擎,还提供了一个 API,用于以统一的方式检索约束元数据,无论约束是使用注释还是通过 XML 映射声明的。阅读本章以了解有关此 API 及其功能的更多信息。您可以在包 jakarta.validation.metadata 中找到所有元数据 API 类型。
本章中介绍的示例基于 @“19”中所示的类和约束声明。
package org.hibernate.validator.referenceguide.chapter10;
public class Person {
public interface Basic {
}
@NotNull
private String name;
//getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;
public interface Vehicle {
public interface Basic {
}
@NotNull(groups = Vehicle.Basic.class)
String getManufacturer();
}
package org.hibernate.validator.referenceguide.chapter10;
@ValidCar
public class Car implements Vehicle {
public interface SeverityInfo extends Payload {
}
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
private Person driver;
private String modelName;
public Car() {
}
public Car(
@NotNull String manufacturer,
String licencePlate,
Person driver,
String modelName) {
this.manufacturer = manufacturer;
this.licensePlate = licencePlate;
this.driver = driver;
this.modelName = modelName;
}
public void driveAway(@Max(75) int speed) {
//...
}
@LuggageCountMatchesPassengerCount(
piecesOfLuggagePerPassenger = 2,
validationAppliesTo = ConstraintTarget.PARAMETERS,
payload = SeverityInfo.class,
message = "There must not be more than {piecesOfLuggagePerPassenger} pieces " +
"of luggage per passenger."
)
public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
//...
}
@Override
@Size(min = 3)
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
@Valid
@ConvertGroup(from = Default.class, to = Person.Basic.class)
public Person getDriver() {
return driver;
}
//further getters and setters...
}
package org.hibernate.validator.referenceguide.chapter10;
public class Library {
@NotNull
private String name;
private List<@NotNull @Valid Book> books;
//getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;
public class Book {
@NotEmpty
private String title;
@NotEmpty
private String author;
//getters and setters ...
}
10.1. BeanDescriptor
元数据 API 的入口点是方法 Validator#getConstraintsForClass() ,它返回 BeanDescriptor 接口的实例。使用此描述符,您可以获取直接在 bean 本身(类级或属性级)上声明的约束的元数据,还可以检索表示单个属性、方法和构造函数的元数据描述符。
Example 10.2, “Using BeanDescriptor” 展示了如何为 Car 类检索 BeanDescriptor ,以及如何以断言的形式使用此描述符。
如果请求的类中托管的约束声明无效,将抛出 ValidationException。 |
BeanDescriptor carDescriptor = validator.getConstraintsForClass( Car.class );
assertTrue( carDescriptor.isBeanConstrained() );
//one class-level constraint
assertEquals( 1, carDescriptor.getConstraintDescriptors().size() );
//manufacturer, licensePlate, driver
assertEquals( 3, carDescriptor.getConstrainedProperties().size() );
//property has constraint
assertNotNull( carDescriptor.getConstraintsForProperty( "licensePlate" ) );
//property is marked with @Valid
assertNotNull( carDescriptor.getConstraintsForProperty( "driver" ) );
//constraints from getter method in interface and implementation class are returned
assertEquals(
2,
carDescriptor.getConstraintsForProperty( "manufacturer" )
.getConstraintDescriptors()
.size()
);
//property is not constrained
assertNull( carDescriptor.getConstraintsForProperty( "modelName" ) );
//driveAway(int), load(List<Person>, List<PieceOfLuggage>)
assertEquals( 2, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER ).size() );
//driveAway(int), getManufacturer(), getDriver(), load(List<Person>, List<PieceOfLuggage>)
assertEquals(
4,
carDescriptor.getConstrainedMethods( MethodType.NON_GETTER, MethodType.GETTER )
.size()
);
//driveAway(int)
assertNotNull( carDescriptor.getConstraintsForMethod( "driveAway", int.class ) );
//getManufacturer()
assertNotNull( carDescriptor.getConstraintsForMethod( "getManufacturer" ) );
//setManufacturer() is not constrained
assertNull( carDescriptor.getConstraintsForMethod( "setManufacturer", String.class ) );
//Car(String, String, Person, String)
assertEquals( 1, carDescriptor.getConstrainedConstructors().size() );
//Car(String, String, Person, String)
assertNotNull(
carDescriptor.getConstraintsForConstructor(
String.class,
String.class,
Person.class,
String.class
)
);
您可以通过 isBeanConstrained() 确定指定的类是否承载任何类或属性级别的约束。isBeanConstrained() 不考虑方法或构造函数约束。
方法 getConstraintDescriptors() 适用于所有从 ElementDescriptor 派生的描述符(请参阅 Section 10.4, “ElementDescriptor” ),并返回一组描述符,代表给定元素上直接声明的约束。对于 BeanDescriptor ,将返回 bean 的类级约束。可在 Section 10.7, “ConstraintDescriptor” 中找到关于 ConstraintDescriptor 的更多详细信息。
通过 getConstraintsForProperty()、getConstraintsForMethod() 和 getConstraintsForConstructor(),您可以获取一个描述符,该描述符表示一个给定的属性或可执行元素,该元素由其名称标识,并且在方法和构造函数中由其参数类型标识。在以下部分中描述了这些方法返回的不同描述符类型。
请注意,这些方法根据 @“34”中所述的约束继承规则考虑在超类型中声明的约束。示例是 @“30”属性的描述符,它提供对 @“31”上定义的所有约束以及实现方法 @“32”的访问。如果指定的元素不存在或未受约束,则返回 @“33”。
方法 getConstrainedProperties()、getConstrainedMethods() 和 getConstrainedConstructors() 分别返回(可能是空的)集合,其中包含所有受约束的属性、方法和构造函数。如果一个元素至少有一个约束或标记为级联验证,则该元素被视为受约束。在调用 getConstrainedMethods() 时,可以指定要返回的方法的类型(getters、non-getters 或两者)。
10.2. PropertyDescriptor
接口 PropertyDescriptor 表示类的某个给定属性。只要遵守 JavaBeans 命名约定,就可以透明地声明对字段或属性 getter 的约束。 Example 10.3, “Using PropertyDescriptor” 展示了如何使用 PropertyDescriptor 接口。
PropertyDescriptor licensePlateDescriptor = carDescriptor.getConstraintsForProperty(
"licensePlate"
);
//"licensePlate" has two constraints, is not marked with @Valid and defines no group conversions
assertEquals( "licensePlate", licensePlateDescriptor.getPropertyName() );
assertEquals( 2, licensePlateDescriptor.getConstraintDescriptors().size() );
assertTrue( licensePlateDescriptor.hasConstraints() );
assertFalse( licensePlateDescriptor.isCascaded() );
assertTrue( licensePlateDescriptor.getGroupConversions().isEmpty() );
PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );
//"driver" has no constraints, is marked with @Valid and defines one group conversion
assertEquals( "driver", driverDescriptor.getPropertyName() );
assertTrue( driverDescriptor.getConstraintDescriptors().isEmpty() );
assertFalse( driverDescriptor.hasConstraints() );
assertTrue( driverDescriptor.isCascaded() );
assertEquals( 1, driverDescriptor.getGroupConversions().size() );
使用 getConstraintDescriptors() ,您可以检索一组 ConstraintDescriptors ,其中提供有关给定属性的各个约束的更多信息。如果将属性标记为级联验证(使用 @Valid 注释或通过 XML),则方法 isCascaded() 返回 true ;否则返回 false 。由 getGroupConversions() 返回任何配置的组转换。有关 GroupConversionDescriptor 的更多详细信息,请参阅 Section 10.6, “GroupConversionDescriptor” 。
10.3. MethodDescriptor and ConstructorDescriptor
受约束的方法和构造函数分别由界面 MethodDescriptor ConstructorDescriptor 表示。 Example 10.4, “Using MethodDescriptor and ConstructorDescriptor” 展示了如何使用这些描述符。
//driveAway(int) has a constrained parameter and an unconstrained return value
MethodDescriptor driveAwayDescriptor = carDescriptor.getConstraintsForMethod(
"driveAway",
int.class
);
assertEquals( "driveAway", driveAwayDescriptor.getName() );
assertTrue( driveAwayDescriptor.hasConstrainedParameters() );
assertFalse( driveAwayDescriptor.hasConstrainedReturnValue() );
//always returns an empty set; constraints are retrievable by navigating to
//one of the sub-descriptors, e.g. for the return value
assertTrue( driveAwayDescriptor.getConstraintDescriptors().isEmpty() );
ParameterDescriptor speedDescriptor = driveAwayDescriptor.getParameterDescriptors()
.get( 0 );
//The "speed" parameter is located at index 0, has one constraint and is not cascaded
//nor does it define group conversions
assertEquals( "speed", speedDescriptor.getName() );
assertEquals( 0, speedDescriptor.getIndex() );
assertEquals( 1, speedDescriptor.getConstraintDescriptors().size() );
assertFalse( speedDescriptor.isCascaded() );
assert speedDescriptor.getGroupConversions().isEmpty();
//getDriver() has no constrained parameters but its return value is marked for cascaded
//validation and declares one group conversion
MethodDescriptor getDriverDescriptor = carDescriptor.getConstraintsForMethod(
"getDriver"
);
assertFalse( getDriverDescriptor.hasConstrainedParameters() );
assertTrue( getDriverDescriptor.hasConstrainedReturnValue() );
ReturnValueDescriptor returnValueDescriptor = getDriverDescriptor.getReturnValueDescriptor();
assertTrue( returnValueDescriptor.getConstraintDescriptors().isEmpty() );
assertTrue( returnValueDescriptor.isCascaded() );
assertEquals( 1, returnValueDescriptor.getGroupConversions().size() );
//load(List<Person>, List<PieceOfLuggage>) has one cross-parameter constraint
MethodDescriptor loadDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
);
assertTrue( loadDescriptor.hasConstrainedParameters() );
assertFalse( loadDescriptor.hasConstrainedReturnValue() );
assertEquals(
1,
loadDescriptor.getCrossParameterDescriptor().getConstraintDescriptors().size()
);
//Car(String, String, Person, String) has one constrained parameter
ConstructorDescriptor constructorDescriptor = carDescriptor.getConstraintsForConstructor(
String.class,
String.class,
Person.class,
String.class
);
assertEquals( "Car", constructorDescriptor.getName() );
assertFalse( constructorDescriptor.hasConstrainedReturnValue() );
assertTrue( constructorDescriptor.hasConstrainedParameters() );
assertEquals(
1,
constructorDescriptor.getParameterDescriptors()
.get( 0 )
.getConstraintDescriptors()
.size()
);
getName() 返回给定方法或构造函数的名称。方法 hasConstrainedParameters() 和 hasConstrainedReturnValue() 可用于快速检查可执行元素是否有任何参数约束(单个参数上的约束或跨参数约束)或返回值约束。
请注意,约束不会直接在 MethodDescriptor 和 ConstructorDescriptor 上公开,而是在表示可执行参数、返回值和跨参数约束的专用描述符上公开的。要获取这些描述符中的一个,请分别调用 getParameterDescriptors()、getReturnValueDescriptor() 或 getCrossParameterDescriptor()。
这些描述符提供对元素约束 ( getConstraintDescriptors() ) 的访问,在参数和返回值的情况下,还提供对级联验证的配置 ( isValid() 和 getGroupConversions() )。对于参数,您还可以通过 getName() 和 getIndex() 检索由当前使用的参数名称提供程序(请参阅 Section 9.2.4, “ParameterNameProvider” )返回的索引和名称。
遵循 JavaBeans 命名约定的 getter 方法被认为是 bean 属性,也是受约束方法。 |
这意味着您可以通过获取 PropertyDescriptor (例如 BeanDescriptor.getConstraintsForProperty("foo") )或检查 getter 的 MethodDescriptor (例如 BeanDescriptor.getConstraintsForMethod("getFoo").getReturnValueDescriptor()) )的返回值描述符来检索相关的元数据。
10.4. ElementDescriptor
ElementDescriptor 接口是单个描述符类型(如 BeanDescriptor 、 PropertyDescriptor 等)的通用基类。除了 getConstraintDescriptors() 之外,它还提供了一些对所有描述符通用的方法。
hasConstraints() 可快速查看元素是否具有任何直接约束(例如,在 BeanDescriptor 的情况下为类级别约束)。
getElementClass() 返回由给定描述符表示的元素的 Java 类型。更具体地说,该方法返回:
-
在 BeanDescriptor 上调用时为对象类型,
-
分别在 PropertyDescriptor 或 ParameterDescriptor 上调用时为属性或参数的类型,
-
在 CrossParameterDescriptor 上调用时为 Object[].class,
-
在 ConstructorDescriptor, MethodDescriptor 或 ReturnValueDescriptor 上调用时的返回类型。对于没有返回值的方法将返回 void.class。
Example 10.5, “Using ElementDescriptor methods” 展示了如何使用这些方法。
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
"manufacturer"
);
assertTrue( manufacturerDescriptor.hasConstraints() );
assertEquals( String.class, manufacturerDescriptor.getElementClass() );
CrossParameterDescriptor loadCrossParameterDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
).getCrossParameterDescriptor();
assertTrue( loadCrossParameterDescriptor.hasConstraints() );
assertEquals( Object[].class, loadCrossParameterDescriptor.getElementClass() );
最后, ElementDescriptor 提供对 ConstraintFinder API 的访问,该 API 可以精细地查询约束条件元数据。 Example 10.6, “Usage of ConstraintFinder” 展示了如何通过 findConstraints() 检索 ConstraintFinder 实例,并使用 API 查询约束条件元数据。
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
"manufacturer"
);
//"manufacturer" constraints are declared on the getter, not the field
assertTrue(
manufacturerDescriptor.findConstraints()
.declaredOn( ElementType.FIELD )
.getConstraintDescriptors()
.isEmpty()
);
//@NotNull on Vehicle#getManufacturer() is part of another group
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.unorderedAndMatchingGroups( Default.class )
.getConstraintDescriptors()
.size()
);
//@Size on Car#getManufacturer()
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.lookingAt( Scope.LOCAL_ELEMENT )
.getConstraintDescriptors()
.size()
);
//@Size on Car#getManufacturer() and @NotNull on Vehicle#getManufacturer()
assertEquals(
2,
manufacturerDescriptor.findConstraints()
.lookingAt( Scope.HIERARCHY )
.getConstraintDescriptors()
.size()
);
//Combining several filter options
assertEquals(
1,
manufacturerDescriptor.findConstraints()
.declaredOn( ElementType.METHOD )
.lookingAt( Scope.HIERARCHY )
.unorderedAndMatchingGroups( Vehicle.Basic.class )
.getConstraintDescriptors()
.size()
);
通过 declaredOn(),您可以在某些元素类型上搜索已声明的 ConstraintDescriptors。这对于查找在字段或 getter 方法上声明的属性约束很有用。
unorderedAndMatchingGroups() 将所得约束限制为与给定验证组匹配的约束。
lookingAt() 允许区分直接在元素上指定的约束 (Scope.LOCAL_ELEMENT) 或属于该元素但托管在类层次结构中的约束 (Scope.HIERARCHY)。
您还可以结合不同的选项,如最后一个示例所示。
unorderedAndMatchingGroups() 不受尊重,但尊重组继承和序列继承。
10.5. ContainerDescriptor and ContainerElementTypeDescriptor
ContainerDescriptor 接口是支持容器元素约束和级联验证( PropertyDescriptor 、 ParameterDescriptor 、 ReturnValueDescriptor )的所有元素的通用接口。
它有一个返回 ContainerElementTypeDescriptor 集合的单个方法 getConstrainedContainerElementTypes()。
ContainerElementTypeDescriptor 扩展 ContainerDescriptor 以支持嵌套容器元素约束。
ContainerElementTypeDescriptor 包含有关容器、约束和级联验证的信息。
Example 10.7, “Using ContainerElementTypeDescriptor” 展示了如何使用 getConstrainedContainerElementTypes() 检索 ContainerElementTypeDescriptor 的集合。
PropertyDescriptor booksDescriptor = libraryDescriptor.getConstraintsForProperty(
"books"
);
Set<ContainerElementTypeDescriptor> booksContainerElementTypeDescriptors =
booksDescriptor.getConstrainedContainerElementTypes();
ContainerElementTypeDescriptor booksContainerElementTypeDescriptor =
booksContainerElementTypeDescriptors.iterator().next();
assertTrue( booksContainerElementTypeDescriptor.hasConstraints() );
assertTrue( booksContainerElementTypeDescriptor.isCascaded() );
assertEquals(
0,
booksContainerElementTypeDescriptor.getTypeArgumentIndex().intValue()
);
assertEquals(
List.class,
booksContainerElementTypeDescriptor.getContainerClass()
);
Set<ConstraintDescriptor<?>> constraintDescriptors =
booksContainerElementTypeDescriptor.getConstraintDescriptors();
ConstraintDescriptor<?> constraintDescriptor =
constraintDescriptors.iterator().next();
assertEquals(
NotNull.class,
constraintDescriptor.getAnnotation().annotationType()
);
10.6. GroupConversionDescriptor
所有表示可能是级联验证对象(即 PropertyDescriptor 、 ParameterDescriptor 和 ReturnValueDescriptor )的元素的描述符类型都通过 getGroupConversions() 提供对元素组转换的访问。返回的集合包含每个已配置转换的 GroupConversionDescriptor ,允许检索转换的源组和目标组。 Example 10.8, “Using GroupConversionDescriptor” 展示了一个示例。
PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );
Set<GroupConversionDescriptor> groupConversions = driverDescriptor.getGroupConversions();
assertEquals( 1, groupConversions.size() );
GroupConversionDescriptor groupConversionDescriptor = groupConversions.iterator()
.next();
assertEquals( Default.class, groupConversionDescriptor.getFrom() );
assertEquals( Person.Basic.class, groupConversionDescriptor.getTo() );
10.7. ConstraintDescriptor
最后但并非最不重要的一点, ConstraintDescriptor 接口描述了单个约束及其组成约束。通过此接口的实例,您可以访问约束注释及其参数。
Example 10.9, “Using ConstraintDescriptor” 展示了如何从 ConstraintDescriptor 检索默认约束属性(如消息模板、组等),以及自定义约束属性 ( piecesOfLuggagePerPassenger ) 和其他元数据,例如约束的注释类型及其验证者。
//descriptor for the @LuggageCountMatchesPassengerCount constraint on the
//load(List<Person>, List<PieceOfLuggage>) method
ConstraintDescriptor<?> constraintDescriptor = carDescriptor.getConstraintsForMethod(
"load",
List.class,
List.class
).getCrossParameterDescriptor().getConstraintDescriptors().iterator().next();
//constraint type
assertEquals(
LuggageCountMatchesPassengerCount.class,
constraintDescriptor.getAnnotation().annotationType()
);
//standard constraint attributes
assertEquals( SeverityInfo.class, constraintDescriptor.getPayload().iterator().next() );
assertEquals(
ConstraintTarget.PARAMETERS,
constraintDescriptor.getValidationAppliesTo()
);
assertEquals( Default.class, constraintDescriptor.getGroups().iterator().next() );
assertEquals(
"There must not be more than {piecesOfLuggagePerPassenger} pieces of luggage per " +
"passenger.",
constraintDescriptor.getMessageTemplate()
);
//custom constraint attribute
assertEquals(
2,
constraintDescriptor.getAttributes().get( "piecesOfLuggagePerPassenger" )
);
//no composing constraints
assertTrue( constraintDescriptor.getComposingConstraints().isEmpty() );
//validator class
assertEquals(
Arrays.<Class<?>>asList( LuggageCountMatchesPassengerCount.Validator.class ),
constraintDescriptor.getConstraintValidatorClasses()
);