Hibernate Validator 中文操作指南
7. Value extraction
值提取是提取容器中值的该过程,以便可以验证这些值。
Value extraction is the process of extracting values from a container so that they can be validated.
It is used when dealing with container element constraints and cascaded validation inside containers.
7.1. Built-in value extractors
Hibernate Validator 为通常的 Java 容器类型提供了内置值抽取器,因此,除非您使用自己的自定义容器类型(或外部库的类型,例如 Guava's Multimap),否则您不必添加自己的值抽取器。
Hibernate Validator comes with built-in value extractors for the usual Java container types so, except if you are using your own custom container types (or the ones of external libraries such as Guava's Multimap), you should not have to add your own value extractors.
下列容器类型均具有内置值提取器:
Built-in value extractors are present for all the following container types:
-
java.util.Iterable;
-
java.util.List;
-
java.util.Map: for keys and values;
-
java.util.Optional, java.util.OptionalInt, java.util.OptionalLong and java.util.OptionalDouble;
-
JavaFX's ObservableValue (see Section 7.4, “JavaFX value extractors” for more details).
可以在 Jakarta Bean Validation specification 中找到内含内置值抽取器的完整列表以及它们的行为详情。
The complete list of built-in value extractors with all the details on how they behave can be found in the Jakarta Bean Validation specification.
7.2. Implementing a ValueExtractor
要从自定义容器中提取值,需要实现 ValueExtractor。
To extract values from a custom container, one needs to implement a ValueExtractor.
实现 ValueExtractor 不够,您还需要注册它。有关更多详细信息,请参见 Section 7.5, “Registering a ValueExtractor” 。 |
Implementing a ValueExtractor is not enough, you also need to register it. See Section 7.5, “Registering a ValueExtractor” for more details. |
ValueExtractor 是一个非常简单的 API,因为值提取器的唯一目的是向 ValueReceiver 提供提取的值。
ValueExtractor is a very simple API as the only purpose of a value extractor is to provide the extracted values to a ValueReceiver.
例如,让我们考虑 Guava 的 Optional 案例。这是一个简单的示例,因为我们可以针对 java.util.Optional 一个塑造其值提取器:
For instance, let’s consider the case of Guava’s Optional. It is an easy example as we can shape its value extractor after the java.util.Optional one:
. Example 7.1: A ValueExtractor for Guava’s Optional
package org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class OptionalValueExtractor
implements ValueExtractor<Optional<@ExtractedValue ?>> {
@Override
public void extractValues(Optional<?> originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.orNull() );
}
}
需要一些解释:
Some explanations are in order:
-
The @ExtractedValue annotation marks the type argument under consideration: it is going to be used to resolve the type of the validated value;
-
We use the value() method of the receiver as Optional is a pure wrapper type;
-
We don’t want to add a node to the property path of the constraint violation as we want the violation to be reported as if it were directly on the property so we pass a null node name to value().
更有趣的示例是 Guava 的 Multimap 案例:我们希望能够验证这种容器类型的键和值。
A more interesting example is the case of Guava’s Multimap: we would like to be able to validate both the keys and the values of this container type.
我们首先来考虑值的情况。需要提取它们的提取器:
Let’s first consider the case of the values. A value extractor extracting them is required:
. Example 7.2: A ValueExtractor for Multimap values
package org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class MultimapValueValueExtractor
implements ValueExtractor<Multimap<?, @ExtractedValue ?>> {
@Override
public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
for ( Entry<?, ?> entry : originalValue.entries() ) {
receiver.keyedValue( "<multimap value>", entry.getKey(), entry.getValue() );
}
}
}
它允许您为 Multimap 的值验证约束:
It allows to validate constraints for the values of the Multimap:
-
示例 7.3: Multimap 值的约束
. Example 7.3: Constraints on the values of a Multimap
private Multimap<String, @NotBlank String> map1;
要能够对 Multimap 的键施加约束,还需要另一个提取器:
Another value extractor is required to be able to put constraints on the keys of a Multimap:
. Example 7.4: A ValueExtractor for Multimap keys
package org.hibernate.validator.referenceguide.chapter07.valueextractor;
public class MultimapKeyValueExtractor
implements ValueExtractor<Multimap<@ExtractedValue ?, ?>> {
@Override
public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
for ( Object key : originalValue.keySet() ) {
receiver.keyedValue( "<multimap key>", key, key );
}
}
}
一旦注册了这两个值提取器,便可以在 Multimap 的键和值上声明约束:
Once these two value extractors are registered, you can declare constraints on the keys and values of a Multimap:
. Example 7.5: Constraints on the keys and values of a Multimap
private Multimap<@NotBlank String, @NotBlank String> map2;
这两者的区别乍一看可能有些微妙,因此我们对它们进行一些说明:
The differences between the two value extractors may be a bit subtle at a first glance so let’s shed some light on them:
-
The @ExtractedValue annotation marks the targeted type argument (either K or V in this case).
-
We use different node names (<multimap key> vs. <multimap value>).
-
In one case, we pass the values to the receiver (third argument of the keyedValue() call), in the other, we pass the keys.
根据容器类型,您应该选择最合适 ValueReceiver 方法:
Depending on your container type, you should choose the ValueReceiver method fitting the best:
value()
用于简单的包装容器 - 用作 _Optional_s
for a simple wrapping container - it is used for _Optional_s
iterableValue()
用于可迭代容器 - 用作 _Set_s
for an iterable container - it is used for _Set_s
indexedValue()
用于包含索引值的容器 - 用作 _List_s
for a container containing indexed values - it is used for _List_s
keyedValue()
用于包含键控值的容器 - 用作 _Map_s。它同时用于键和值。对于键,键也作为验证值传递。
for a container containing keyed values - it is used for _Map_s. It is used for both the keys and the values. In the case of keys, the key is also passed as the validated value.
对于以上所有方法,您需要传递一个节点名称:它是包含在约束违规属性路径中的节点的名称。如前所述,如果节点名称是_null_,则不会向属性路径中添加任何节点:这对于类似于_Optional_的纯包装器类型非常有用。
For all these methods, you need to pass a node name: it is the name included in the node added to the property path of the constraint violation. As mentioned earlier, if the node name is null, no node is added to the property path: it is be useful for pure wrapper types similar to Optional.
所用方法的选择很重要,因为它向约束违规的属性路径添加上下文信息,例如索引或经过验证的值的键。
The choice of the method used is important as it adds contextual information to the property path of the constraint violation e.g. the index or the key of the validated value.
7.3. Non generic containers
您可能已经注意到,到目前为止,我们仅为泛型容器实现了值提取器。
You might have noticed that, until now, we only implemented value extractors for generic containers.
Hibernate 验证工具还支持对非泛型容器的值提取。
Hibernate Validator also supports value extraction for non generic containers.
我们来看看_java.util.OptionalInt_的情况,它将原始_int_包装到_Optional_类容器中。
Let’s take the case of java.util.OptionalInt which wraps a primitive int into an Optional-like container.
第一次尝试_OptionalInt_的值提取器可能如下所示:
A first attempt at a value extractor for OptionalInt would look like:
. Example 7.6: A ValueExtractor for OptionalInt
package org.hibernate.validator.referenceguide.chapter07.nongeneric;
public class OptionalIntValueExtractor
implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {
@Override
public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
}
}
对于非泛型容器来说,缺少一个明显的东西:我们没有类型参数。它有两个结果:
There is an obvious thing missing for a non generic container: we don’t have a type parameter. It has two consequences:
-
we cannot determine the type of the validated value using the type argument;
-
we cannot add constraints on the type argument (e.g. Container<@NotNull String>).
首先,我们需要一种方法来告诉 Hibernate 验证工具从_OptionalInt_中提取的值的类型为_Integer_。如您在上面的示例中所见,_@ExtractedValue_注释的_type_属性允许向验证引擎提供此信息。
First things first, we need a way to tell Hibernate Validator that the value extracted from an OptionalInt is of type Integer. As you can see in the above example, the type attribute of the @ExtractedValue annotation allows to provide this information to the validation engine.
然后,您必须告诉验证引擎,您想要添加到_OptionalInt_属性的_Min_约束与包装的值相关,而不是包装本身。
Then you have to tell the validation engine that the Min constraint you want to add to the OptionalInt property relates to the wrapped value and not the wrapper.
Jakarta Bean 验证为此情况提供了_Unwrapping.Unwrap_负载:
Jakarta Bean Validation provides the Unwrapping.Unwrap payload for this situation:
. Example 7.7: Using Unwrapping.Unwrap payload
@Min(value = 5, payload = Unwrapping.Unwrap.class)
private OptionalInt optionalInt1;
如果我们退一步,我们希望添加到_OptionalInt_属性的大多数(如果不是全部)约束将应用于包装值,因此,找到一种方法使其成为默认值将很好。
If we take a step back, most - if not all - the constraints we would like to add to an OptionalInt property would be applied to the wrapped value so having a way to make it the default would be nice.
这正是_@UnwrapByDefault_注释的用途:
This is exactly what the @UnwrapByDefault annotation is for:
. Example 7.8: A ValueExtractor for OptionalInt marked with @UnwrapByDefault
package org.hibernate.validator.referenceguide.chapter07.nongeneric;
@UnwrapByDefault
public class UnwrapByDefaultOptionalIntValueExtractor
implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {
@Override
public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
}
}
在为_OptionalInt_声明此值提取器时,约束注释将默认应用于包装值:
When declaring this value extractor for OptionalInt, constraint annotations will by default be applied to the wrapped value:
. Example 7.9: Implicit unwrapping thanks to @UnwrapByDefault
@Min(5)
private OptionalInt optionalInt2;
请注意,您仍然可以使用_Unwrapping.Skip_负载为容器本身声明注释:
Note that you can still declare an annotation for the wrapper itself by using the Unwrapping.Skip payload:
. Example 7.10: Avoid implicit unwrapping with Unwrapping.Skip
@NotNull(payload = Unwrapping.Skip.class)
@Min(5)
private OptionalInt optionalInt3;
针对 OptionalInt 的 @UnwrapByDefault 值提取器是内置值提取器的一部分:无需添加。 |
The @UnwrapByDefault value extractor for OptionalInt is part of the built-in value extractors: there is no need to add one. |
7.4. JavaFX value extractors
JavaFX 中的 Bean 属性通常不是像 String 或 int 这样的简单数据类型,而是包装在 Property 类型中,以便使其可观察、用于数据绑定等。
Bean properties in JavaFX are typically not of simple data types like String or int, but are wrapped in Property types which allows to make them observable, use them for data binding etc.
因此,需要值提取才能对包装值应用约束。
Thus, value extraction is required to be able to apply constraints on the wrapped values.
JavaFX ObservableValue 值提取器用 @UnwrapByDefault 标记。因此,容器上承载的约束默认情况下会对包装值进行寻址。
The JavaFX ObservableValue value extractor is marked with @UnwrapByDefault. As such, the constraints hosted on the container target the wrapped value by default.
因此,您可以按如下方式限制 StringProperty:
Thus, you can constrain a StringProperty as below:
. Example 7.11: Constraining a StringProperty
@NotBlank
private StringProperty stringProperty;
或 LongProperty:
Or a LongProperty:
. Example 7.12: Constraining a LongProperty
@Min(5)
private LongProperty longProperty;
-
可迭代属性类型,即ReadOnlyListProperty、ListProperty及其Set和Map对应类型是通用的,因此可以使用容器元素约束。因此,它们有未标记为@UnwrapByDefault**的特定值提取器。
The iterable property types, namely ReadOnlyListProperty, ListProperty and their Set and Map counterparts are generic and, as such, container element constraints can be used. Thus, they have specific value extractors that are not marked with @UnwrapByDefault.
自然会将ReadOnlyListProperty约束为List:
A ReadOnlyListProperty would naturally be constrained as a List:
. Example 7.13: Constraining a ReadOnlyListProperty
@Size(min = 1)
private ReadOnlyListProperty<@NotBlank String> listProperty;
7.5. Registering a ValueExtractor
Hibernate 验证程序不会自动检测classpath中的值提取器,因此必须注册它们。
Hibernate Validator does not detect automatically the value extractors in the classpath so they have to be registered.
注册值提取器有几种方法(按优先级升序):
There are several ways to register value extractors (in increasing order of priority):
Provided by the validation engine itself
Via the Java service loader mechanism
必须提供文件 META-INF/services/jakarta.validation.valueextraction.ValueExtractor ,其中内容为一个或多个值提取器实现的完全限定名称,每行一个。
The file META-INF/services/jakarta.validation.valueextraction.ValueExtractor must be provided, with the fully-qualified names of one or more value extractor implementations as its contents, each on a separate line.
In the META-INF/validation.xml file
有关如何在 XML 配置中注册值提取器的更多信息,请参阅 Section 8.1, “Configuring the validator factory in validation.xml” 。
See Section 8.1, “Configuring the validator factory in validation.xml” for more information about how to register value extractors in the XML configuration.
By calling Configuration#addValueExtractor(ValueExtractor<?>)
有关更多信息,请参阅 Section 9.2.6, “Registering _ValueExtractor_s” 。
See Section 9.2.6, “Registering _ValueExtractor_s” for more information.
By invoking ValidatorContext#addValueExtractor(ValueExtractor<?>)
它仅声明该 Validator 实例的值提取器。
It only declares the value extractor for this Validator instance.
给定类型的优先级和类型参数的值提取器覆盖对相同类型和低优先级类型参数的任何其他提取器。
A value extractor for a given type and type parameter specified at a higher priority overrides any other extractors for the same type and type parameter given at lower priorities.
7.6. Resolution algorithms
在大多数情况下,您不必担心这一点,但如果您要覆盖现有的值提取器,可以在 Jakarta Bean 验证规范中找到值提取器解析算法的详细描述:
In most cases, you should not have to worry about this but, if you are overriding existing value extractors, you can find a detailed description of the value extractors resolution algorithms in the Jakarta Bean Validation specification:
-
for cascaded validation,
-
and for implicit unwrapping.
要注意的一件重要事情是:
One important thing to have in mind is that:
-
for container element constraints, the declared type is used to resolve the value extractors;
-
for cascaded validation, it is the runtime type.