Hibernate Validator 中文操作指南

12. Hibernate Validator Specifics

在本章中,您将了解如何利用 Hibernate 验证器提供的多项功能,除了Jakarta Bean Validation 规范定义的功能之外。这包括快速失败模式、用于编程约束配置的 API 以及约束的布尔组合。

In this chapter you will learn how to make use of several features provided by Hibernate Validator in addition to the functionality defined by the Jakarta Bean Validation specification. This includes the fail fast mode, the API for programmatic constraint configuration and the boolean composition of constraints.

只要 org.hibernate.validator.Incubating 注释仍在开发中,就会标记新的 API 或 SPI。这意味着此类元素(例如,包、类型、方法、常数等)在后续版本中可能会被不兼容地更改或删除。鼓励使用正在孵化的 API/SPI 成员(以便开发团队可以对这些新功能获得反馈),但您应该准备好根据需要在升级到 Hibernate 验证器的更高版本时更新使用它们时的代码。

New APIs or SPIs are tagged with the org.hibernate.validator.Incubating annotation as long as they are under development. This means that such elements (e.g. packages, types, methods, constants etc.) may be incompatibly altered - or removed - in subsequent releases. Usage of incubating API/SPI members is encouraged (so the development team can get feedback on these new features) but you should be prepared for updating code which is using them as needed when upgrading to a new version of Hibernate Validator.

使用以下部分中描述的功能可能会导致在 Jakarta Bean Validation 提供程序之间不可移植的应用程序代码。

Using the features described in the following sections may result in application code which is not portable between Jakarta Bean Validation providers.

12.1. Public API

不过,让我们从查看 Hibernate 验证器的公开 API 开始。您可以在下面找到属于此 API 的所有包及其用途的列表。请注意,当包是公开 API 的一部分时,其子包不一定也是公开 API 的一部分。

Let’s start, however, with a look at the public API of Hibernate Validator. Below you can find a list of all packages belonging to this API and their purpose. Note that when a package is part of the public API this is not necessarily true for its sub-packages.

org.hibernate.validator

用于 Jakarta Bean 验证引导机制的类(例如,验证提供程序、配置类);有关详细信息,请参阅 Chapter 9, Bootstrapping

Classes used by the Jakarta Bean Validation bootstrap mechanism (eg. validation provider, configuration class); for more details see Chapter 9, Bootstrapping.

org.hibernate.validator.cfg, org.hibernate.validator.cfg.context, org.hibernate.validator.cfg.defs, org.hibernate.validator.spi.cfg

Hibernate Validator 约束声明的 fluent API;在 org.hibernate.validator.cfg 中,您将找到 ConstraintMapping 接口,在 org.hibernate.validator.cfg.defs 中找到所有约束定义,在 org.hibernate.validator.spi.cfg 中找到用于配置默认验证器工厂的 API 的回调。有关详细信息,请参阅 Section 12.4, “Programmatic constraint definition and declaration”

Hibernate Validator’s fluent API for constraint declaration; in org.hibernate.validator.cfg you will find the ConstraintMapping interface, in org.hibernate.validator.cfg.defs all constraint definitions and in org.hibernate.validator.spi.cfg a callback for using the API for configuring the default validator factory. Refer to Section 12.4, “Programmatic constraint definition and declaration” for the details.

org.hibernate.validator.constraints, org.hibernate.validator.constraints.br, org.hibernate.validator.constraints.pl

除了 Jakarta Bean 验证规范定义的内置约束外,Hibernate 验证器提供的一些有用的自定义约束;约束在 Section 2.3.2, “Additional constraints” 中进行了详细描述。

Some useful custom constraints provided by Hibernate Validator in addition to the built-in constraints defined by the Jakarta Bean Validation specification; the constraints are described in detail in Section 2.3.2, “Additional constraints”.

org.hibernate.validator.constraintvalidation

扩展的约束验证器上下文,该上下文允许为消息插值设置自定义属性。 Section 12.13.1, “HibernateConstraintValidatorContext 介绍了如何利用该功能。

Extended constraint validator context which allows to set custom attributes for message interpolation. Section 12.13.1, “HibernateConstraintValidatorContext describes how to make use of that feature.

org.hibernate.validator.group, org.hibernate.validator.spi.group

组序列提供程序功能,该功能允许您根据已验证对象状态定义动态默认组序列;具体内容可以在 Section 5.4, “Redefining the default group sequence” 中找到。

The group sequence provider feature which allows you to define dynamic default group sequences in function of the validated object state; the specifics can be found in Section 5.4, “Redefining the default group sequence”.

org.hibernate.validator.messageinterpolation, org.hibernate.validator.resourceloading, org.hibernate.validator.spi.resourceloading

与约束消息插值相关的类;第一个软件包包含 Hibernate 验证器的默认消息内插器 ResourceBundleMessageInterpolator 。后两个软件包提供了用于加载资源包的 ResourceBundleLocator SPI(请参阅 Section 4.2.1, “ResourceBundleLocator )及其默认实现。

Classes related to constraint message interpolation; the first package contains Hibernate Validator’s default message interpolator, ResourceBundleMessageInterpolator. The latter two packages provide the ResourceBundleLocator SPI for the loading of resource bundles (see Section 4.2.1, “ResourceBundleLocator) and its default implementation.

org.hibernate.validator.parameternameprovider

基于 Paranamer 库的 ParameterNameProvider ,请参阅 Section 12.14, “Paranamer based ParameterNameProvider

A ParameterNameProvider based on the Paranamer library, see Section 12.14, “Paranamer based ParameterNameProvider.

org.hibernate.validator.propertypath

jakarta.validation.Path API 的扩展,请参阅 Section 12.7, “Extensions of the Path API”

Extensions to the jakarta.validation.Path API, see Section 12.7, “Extensions of the Path API”.

org.hibernate.validator.spi.constraintdefinition

用于以编程方式注册其他约束验证器的 SPI,请参阅 Section 12.15, “Providing constraint definitions”

An SPI for registering additional constraint validators programmatically, see Section 12.15, “Providing constraint definitions”.

org.hibernate.validator.spi.messageinterpolation

用于在插值约束违规消息时调整区域设置解析的 SPI。请参阅 Section 12.12, “Customizing the locale resolution”

An SPI that can be used to tweak the resolution of the locale when interpolating the constraint violation messages. See Section 12.12, “Customizing the locale resolution”.

org.hibernate.validator.spi.nodenameprovider

用于在构造属性路径时修改属性名称解析方式的 SPI。请参阅 Section 12.18, “Customizing the property name resolution for constraint violations”

An SPI that can be used to alter how the names of properties will be resolved when the property path is constructed. See Section 12.18, “Customizing the property name resolution for constraint violations”.

Hibernate Validator 的公共包分为两类:而实际 API 部分旨在由客户端 invokedused(例如用于以编程方式声明约束的 API 或自定义约束),但 SPI(服务提供程序接口)包包含旨在 implemented 让客户端(例如 ResourceBundleLocator)。

The public packages of Hibernate Validator fall into two categories: while the actual API parts are intended to be invoked or used by clients (e.g. the API for programmatic constraint declaration or the custom constraints), the SPI (service provider interface) packages contain interfaces which are intended to be implemented by clients (e.g. ResourceBundleLocator).

该表中未列出的任何包都是 Hibernate 验证程序的内部包,不打算由客户端访问。这些内部包的内容可能在版本之间发生变化而无需通知,从而可能破坏依赖它们的任何客户端代码。

Any packages not listed in that table are internal packages of Hibernate Validator and are not intended to be accessed by clients. The contents of these internal packages can change from release to release without notice, thus possibly breaking any client code relying on it.

12.2. Fail fast mode

使用快速失败模式,Hibernate 验证器允许在发生第一个约束违反时立即返回当前验证。这对于验证大型对象图表很有用,而您只需要快速检查是否存在任何约束违反。

Using the fail fast mode, Hibernate Validator allows to return from the current validation as soon as the first constraint violation occurs. This can be useful for the validation of large object graphs where you are only interested in a quick check whether there is any constraint violation at all.

Example 12.1, “Using the fail fast validation mode”显示如何引导并使用已启用快速失败的验证器。

Example 12.1, “Using the fail fast validation mode” shows how to bootstrap and use a fail fast enabled validator.

示例 12.1:使用快速失败验证模式

. Example 12.1: Using the fail fast validation mode

package org.hibernate.validator.referenceguide.chapter12.failfast;

public class Car {

    @NotNull
    private String manufacturer;

    @AssertTrue
    private boolean isRegistered;

    public Car(String manufacturer, boolean isRegistered) {
        this.manufacturer = manufacturer;
        this.isRegistered = isRegistered;
    }

    //getters and setters...
}
Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .failFast( true )
        .buildValidatorFactory()
        .getValidator();

Car car = new Car( null, false );

Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );

assertEquals( 1, constraintViolations.size() );

这里,经过验证的对象实际上未能满足 Car 类中声明的两个约束,但由于启用了快速失败模式,因此验证调用只产生一个 ConstraintViolation

Here the validated object actually fails to satisfy both the constraints declared on the Car class, yet the validation call yields only one ConstraintViolation since the fail fast mode is enabled.

不能保证约束的评估顺序,也就是说,无法确定返回的违规是否来自 @NotNull@AssertTrue 约束。如果需要,可以使用 Section 5.3, “Defining group sequences” 中描述的分组序列来强制执行确定性的评估顺序。

There is no guarantee in which order the constraints are evaluated, i.e. it is not deterministic whether the returned violation originates from the @NotNull or the @AssertTrue constraint. If required, a deterministic evaluation order can be enforced using group sequences as described in Section 5.3, “Defining group sequences”.

请参阅 Section 9.2.8, “Provider-specific settings”以了解在引导验证器时启用快速失败模式的不同方法。

Refer to Section 9.2.8, “Provider-specific settings” to learn about the different ways of enabling the fail fast mode when bootstrapping a validator.

12.3. Relaxation of requirements for method validation in class hierarchies

Jakarta Bean Validation 规范定义了一组在类层次结构中定义方法约束时适用的前提条件。这些前提条件在 Jakarta Bean Validation 规范的 section 5.6.5中定义。另请参阅本指南中的 Section 3.1.4, “Method constraints in inheritance hierarchies”

The Jakarta Bean Validation specification defines a set of preconditions which apply when defining constraints on methods within class hierarchies. These preconditions are defined in section 5.6.5 of the Jakarta Bean Validation specification. See also Section 3.1.4, “Method constraints in inheritance hierarchies” in this guide.

根据规范,Jakarta Bean Validation 提供者可以放宽这些前提条件。借助 Hibernate 验证器,您可以通过两种方式之一来执行此操作。

As per specification, a Jakarta Bean Validation provider is allowed to relax these preconditions. With Hibernate Validator you can do this in one of two ways.

首先,您可以在 validation.xml_中使用配置属性_hibernate.validator.allow_parameter_constraint_overridehibernate.validator.allow_multiple_cascaded_validation_on_result_和_hibernate.validator.allow_parallel_method_parameter_constraint。请参见示例 Example 12.2, “Configuring method validation behaviour in class hierarchies via properties”

First you can use the configuration properties hibernate.validator.allow_parameter_constraint_override, hibernate.validator.allow_multiple_cascaded_validation_on_result and hibernate.validator.allow_parallel_method_parameter_constraint in validation.xml. See example Example 12.2, “Configuring method validation behaviour in class hierarchies via properties”.

示例 12.2:通过属性配置类层次结构中的方法验证行为

. Example 12.2: Configuring method validation behaviour in class hierarchies via properties

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
        xmlns="https://jakarta.ee/xml/ns/validation/configuration"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/configuration https://jakarta.ee/xml/ns/validation/validation-configuration-3.0.xsd"
        version="3.0">
    <default-provider>org.hibernate.validator.HibernateValidator</default-provider>

    <property name="hibernate.validator.allow_parameter_constraint_override">true</property>
    <property name="hibernate.validator.allow_multiple_cascaded_validation_on_result">true</property>
    <property name="hibernate.validator.allow_parallel_method_parameter_constraint">true</property>
</validation-config>

或者,这些设置可以在编程引导期间应用。

Alternatively these settings can be applied during programmatic bootstrapping.

示例 12.3:配置类层次结构中的方法验证行为

. Example 12.3: Configuring method validation behaviour in class hierarchies

HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();

configuration.allowMultipleCascadedValidationOnReturnValues( true )
        .allowOverridingMethodAlterParameterConstraint( true )
        .allowParallelMethodsDefineParameterConstraints( true );

默认情况下,所有这些属性都为 false,实现了 Jakarta Bean Validation 规范中定义的默认行为。

By default, all of these properties are false, implementing the default behavior as defined in the Jakarta Bean Validation specification.

更改方法验证的默认行为将导致不符合规范且不可移植的应用程序。确保了解自己正在做什么,并且您的用例确实需要更改默认行为。

Changing the default behaviour for method validation will result in non specification-conforming and non portable application. Make sure to understand what you are doing and that your use case really requires changes to the default behaviour.

12.4. Programmatic constraint definition and declaration

根据 Jakarta Bean Validation 规范,您可以使用 Java 注释和基于 XML 的约束映射来定义和声明约束。

As per the Jakarta Bean Validation specification, you can define and declare constraints using Java annotations and XML based constraint mappings.

此外,Hibernate 验证器提供了一个流畅的 API,允许以编程方式配置约束。用例包括根据某些应用程序状态动态添加约束或在不同场景中需要具有不同约束的实体的测试,但不希望为每个测试用例实现实际的 Java 类。

In addition, Hibernate Validator provides a fluent API which allows for the programmatic configuration of constraints. Use cases include the dynamic addition of constraints at runtime depending on some application state or tests where you need entities with different constraints in different scenarios but don’t want to implement actual Java classes for each test case.

默认情况下,通过流畅 API 添加的约束会附加到通过标准配置功能配置的约束。但在需要时,也可以忽略已配置的注释和 XML 约束。

By default, constraints added via the fluent API are additive to constraints configured via the standard configuration capabilities. But it is also possible to ignore annotation and XML configured constraints where required.

该 API 以_ConstraintMapping_接口为中心。您可以通过_HibernateValidatorConfiguration#createConstraintMapping()_获得新映射,然后以流畅的方式对其进行配置,如 Example 12.4, “Programmatic constraint declaration”所示。

The API is centered around the ConstraintMapping interface. You obtain a new mapping via HibernateValidatorConfiguration#createConstraintMapping() which you then can configure in a fluent manner as shown in Example 12.4, “Programmatic constraint declaration”.

示例 12.4:以编程方式声明约束

. Example 12.4: Programmatic constraint declaration

HibernateValidatorConfiguration configuration = Validation
        .byProvider( HibernateValidator.class )
        .configure();

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .field( "manufacturer" )
            .constraint( new NotNullDef() )
        .field( "licensePlate" )
            .ignoreAnnotations( true )
            .constraint( new NotNullDef() )
            .constraint( new SizeDef().min( 2 ).max( 14 ) )
    .type( RentalCar.class )
        .getter( "rentalStation" )
            .constraint( new NotNullDef() );

Validator validator = configuration.addMapping( constraintMapping )
        .buildValidatorFactory()
        .getValidator();

可以使用方法链在多个类和属性上配置约束。约束定义类 NotNullDefSizeDef 是帮助类,允许以类型安全的方式配置约束参数。org.hibernate.validator.cfg.defs 包中所有内置约束都有定义类。通过调用 ignoreAnnotations(),将忽略通过注释或 XML 为给定元素配置的任何约束。

Constraints can be configured on multiple classes and properties using method chaining. The constraint definition classes NotNullDef and SizeDef are helper classes which allow to configure constraint parameters in a type-safe fashion. Definition classes exist for all built-in constraints in the org.hibernate.validator.cfg.defs package. By calling ignoreAnnotations() any constraints configured via annotations or XML are ignored for the given element.

每个元素(类型、属性、方法等)只能在一个验证器工厂设置中所使用的所有约束映射中配置一次。否则将引发 ValidationException

Each element (type, property, method etc.) may only be configured once within all the constraint mappings used to set up one validator factory. Otherwise a ValidationException is raised.

不支持通过配置子类型向非覆盖的超类型属性和方法添加约束。相反,您需要在这种情况下配置超类型。

It is not supported to add constraints to non-overridden supertype properties and methods by configuring a subtype. Instead you need to configure the supertype in this case.

配置映射后,你必须将其添加回配置对象,然后可以从中获取验证器工厂。

Having configured the mapping, you must add it back to the configuration object from which you then can obtain a validator factory.

对于自定义约束,您可以创建自己的定义类来扩展_ConstraintDef_,也可以使用_GenericConstraintDef_(如 Example 12.5, “Programmatic declaration of a custom constraint”所示)。

For custom constraints, you can either create your own definition classes extending ConstraintDef or you can use GenericConstraintDef as seen in Example 12.5, “Programmatic declaration of a custom constraint”.

示例 12.5:以编程方式声明自定义约束

. Example 12.5: Programmatic declaration of a custom constraint

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .field( "licensePlate" )
            .constraint( new GenericConstraintDef<>( CheckCase.class )
                .param( "value", CaseMode.UPPER )
            );

容器元素约束受程序化 API 支持,使用 containerElementType()

Container element constraints are supported by the programmatic API, using containerElementType().

Example 12.6, “Programmatic declaration of a nested container element constraint”显示了在嵌套容器元素上声明约束的示例。

Example 12.6, “Programmatic declaration of a nested container element constraint” show an example where constraints are declared on nested container elements.

示例 12.6:以编程方式声明嵌套容器元素约束

. Example 12.6: Programmatic declaration of a nested container element constraint

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .field( "manufacturer" )
            .constraint( new NotNullDef() )
        .field( "licensePlate" )
            .ignoreAnnotations( true )
            .constraint( new NotNullDef() )
            .constraint( new SizeDef().min( 2 ).max( 14 ) )
        .field( "partManufacturers" )
            .containerElementType( 0 )
                .constraint( new NotNullDef() )
            .containerElementType( 1, 0 )
                .constraint( new NotNullDef() )
    .type( RentalCar.class )
        .getter( "rentalStation" )
            .constraint( new NotNullDef() );

如所示,传递给 containerElementType() 的参数是用于获取所需的嵌套容器元素类型的类型参数索引的路径。

As demonstrated, the parameters passed to containerElementType() are the path of type argument indexes used to obtain the desired nested container element type.

通过调用 valid(),您可以标记一个成员进行级联验证,这等效于使用 @Valid_对其进行注解。使用 _convertGroup()_方法(等效于 _@ConvertGroup)配置在级联验证期间应用的任何组转换。可以在 Example 12.7, “Marking a property for cascaded validation”中看到一个示例。

By invoking valid() you can mark a member for cascaded validation which is equivalent to annotating it with @Valid. Configure any group conversions to be applied during cascaded validation using the convertGroup() method (equivalent to @ConvertGroup). An example can be seen in Example 12.7, “Marking a property for cascaded validation”.

示例 12.7:标记一个属性进行级联验证

. Example 12.7: Marking a property for cascaded validation

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .field( "driver" )
            .constraint( new NotNullDef() )
            .valid()
            .convertGroup( Default.class ).to( PersonDefault.class )
        .field( "partManufacturers" )
            .containerElementType( 0 )
                .valid()
            .containerElementType( 1, 0 )
                .valid()
    .type( Person.class )
        .field( "name" )
            .constraint( new NotNullDef().groups( PersonDefault.class ) );

您不仅可以使用流畅 API 配置 Bean 约束,还可以配置方法和构造函数约束。如 Example 12.8, “Programmatic declaration of method and constructor constraints”所示,构造函数由其参数类型标识,而方法则由其名称和参数类型标识。选择了一个方法或构造函数后,您可以标记其参数和/或返回值以进行级联验证,并添加约束以及跨参数约束。

You can not only configure bean constraints using the fluent API but also method and constructor constraints. As shown in Example 12.8, “Programmatic declaration of method and constructor constraints” constructors are identified by their parameter types and methods by their name and parameter types. Having selected a method or constructor, you can mark its parameters and/or return value for cascaded validation and add constraints as well as cross-parameter constraints.

如示例中所示,也可以对容器元素类型调用 valid()

As shown in the example, valid() can be also invoked on a container element type.

示例 12.8:以编程方式声明方法和构造函数约束

. Example 12.8: Programmatic declaration of method and constructor constraints

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .constructor( String.class )
            .parameter( 0 )
                .constraint( new SizeDef().min( 3 ).max( 50 ) )
            .returnValue()
                .valid()
        .method( "drive", int.class )
            .parameter( 0 )
                .constraint( new MaxDef().value( 75 ) )
        .method( "load", List.class, List.class )
            .crossParameter()
                .constraint( new GenericConstraintDef<>(
                        LuggageCountMatchesPassengerCount.class ).param(
                            "piecesOfLuggagePerPassenger", 2
                        )
                )
        .method( "getDriver" )
            .returnValue()
                .constraint( new NotNullDef() )
                .valid();

最后但并非最不重要的是,你可以配置类型的默认组序列或默认组序列提供程序,如下面的示例所示。

Last but not least you can configure the default group sequence or the default group sequence provider of a type as shown in the following example.

示例 12.9:默认组顺序和默认组顺序提供程序的配置

. Example 12.9: Configuration of default group sequence and default group sequence provider

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
    .type( Car.class )
        .defaultGroupSequence( Car.class, CarChecks.class )
    .type( RentalCar.class )
        .defaultGroupSequenceProviderClass( RentalCarGroupSequenceProvider.class );

12.5. Applying programmatic constraint declarations to the default validator factory

如果您不是手动自举验证程序工厂,而是使用通过 META-INF/validation.xml 配置的默认工厂(请参见 Chapter 8, Configuring via XML ),您可以通过创建一个或多个约束映射贡献者来添加一个或多个约束映射。为此,请实现 ConstraintMappingContributor 契约:

If you are not bootstrapping a validator factory manually but work with the default factory as configured via META-INF/validation.xml (see Chapter 8, Configuring via XML), you can add one or more constraint mappings by creating one or several constraint mapping contributors. To do so, implement the ConstraintMappingContributor contract:

示例 12.10:自定义 ConstraintMappingContributor 实现

. Example 12.10: Custom ConstraintMappingContributor implementation

package org.hibernate.validator.referenceguide.chapter12.constraintapi;

public class MyConstraintMappingContributor implements ConstraintMappingContributor {

    @Override
    public void createConstraintMappings(ConstraintMappingBuilder builder) {
        builder.addConstraintMapping()
            .type( Marathon.class )
                .getter( "name" )
                    .constraint( new NotNullDef() )
                .field( "numberOfHelpers" )
                    .constraint( new MinDef().value( 1 ) );

        builder.addConstraintMapping()
            .type( Runner.class )
                .field( "paidEntryFee" )
                    .constraint( new AssertTrueDef() );
    }
}

然后,你需要在 META-INF/validation.xml 中使用属性 key hibernate.validator.constraint_mapping_contributors 指定生成器实现的完全限定类名。你可以通过逗号分隔来指定多个生成器。

You then need to specify the fully-qualified class name of the contributor implementation in META-INF/validation.xml, using the property key hibernate.validator.constraint_mapping_contributors. You can specify several contributors by separating them with a comma.

12.6. Advanced constraint composition features

12.6.1. Validation target specification for purely composed constraints

如果你在方法声明上指定了一个纯粹的组合约束(即一个本身没有验证器的约束,但仅由其他组合约束组成),验证引擎无法确定是否要将该约束应用为返回值约束或跨参数约束。

In case you specify a purely composed constraint - i.e. a constraint which has no validator itself but is solely made up from other, composing constraints - on a method declaration, the validation engine cannot determine whether that constraint is to be applied as a return value constraint or as a cross-parameter constraint.

Hibernate Validator 允许通过在复合约束类型声明中指定 @SupportedValidationTarget_注解(如 Example 12.11, “Specifying the validation target of a purely composed constraint”所示)来解决此类歧义。@ValidInvoiceAmount_不声明任何验证器,但它完全由_@Min_和_@NotNull_约束组成。当在方法声明中给定时,_@SupportedValidationTarget_确保约束应用于方法返回值。

Hibernate Validator allows to resolve such ambiguities by specifying the @SupportedValidationTarget annotation on the declaration of the composed constraint type as shown in Example 12.11, “Specifying the validation target of a purely composed constraint”. The @ValidInvoiceAmount does not declare any validator, but it is solely composed by the @Min and @NotNull constraints. The @SupportedValidationTarget ensures that the constraint is applied to the method return value when given on a method declaration.

示例 12.11:指定纯复合约束的验证目标

. Example 12.11: Specifying the validation target of a purely composed constraint

package org.hibernate.validator.referenceguide.chapter12.purelycomposed;

@Min(value = 0)
@NotNull
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@ReportAsSingleViolation
public @interface ValidInvoiceAmount {

    String message() default "{org.hibernate.validator.referenceguide.chapter11.purelycomposed."
            + "ValidInvoiceAmount.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @OverridesAttribute(constraint = Min.class, name = "value")
    long value();
}

12.6.2. Boolean composition of constraints

Jakarta Bean Validation 规定,复合约束(见 Section 6.4, “Constraint composition”)的约束通过逻辑_AND_组合。这意味着所有组成约束都需要返回 true 才能获得总体成功的验证。

Jakarta Bean Validation specifies that the constraints of a composed constraint (see Section 6.4, “Constraint composition”) are all combined via a logical AND. This means all of the composing constraints need to return true to obtain an overall successful validation.

Hibernate Validator 提供了对此的扩展,并允许你通过逻辑 ORNOT 来组合约束。为此,你必须使用 ConstraintComposition 注释和枚举 CompositionType 及其值 ANDORALL_FALSE

Hibernate Validator offers an extension to this and allows you to compose constraints via a logical OR or NOT. To do so, you have to use the ConstraintComposition annotation and the enum CompositionType with its values AND, OR and ALL_FALSE.

Example 12.12, “OR composition of constraints”显示如何构建复合约束 @PatternOrSize,其中为了通过验证,只需要一个组成约束有效即可。已验证的字符串要么全部为小写,要么长度在 2 到 3 个字符之间。

Example 12.12, “OR composition of constraints” shows how to build a composed constraint @PatternOrSize where only one of the composing constraints needs to be valid in order to pass the validation. Either the validated string is all lower-cased or it is between two and three characters long.

示例 12.12:约束的 OR 组合

. Example 12.12: OR composition of constraints

package org.hibernate.validator.referenceguide.chapter12.booleancomposition;

@ConstraintComposition(OR)
@Pattern(regexp = "[a-z]")
@Size(min = 2, max = 3)
@ReportAsSingleViolation
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
public @interface PatternOrSize {
    String message() default "{org.hibernate.validator.referenceguide.chapter11." +
            "booleancomposition.PatternOrSize.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

隐式地使用 ALL_FALSE 作为组成类型强制执行只在约束组合验证失败时报告单 个违规行为。

Using ALL_FALSE as composition type implicitly enforces that only a single violation will get reported in case validation of the constraint composition fails.

12.7. Extensions of the Path API

Hibernate Validator 提供了对 jakarta.validation.Path API 的扩展。对于 ElementKind.PROPERTYElementKind.CONTAINER_ELEMENT 的节点,它允许获取所表示属性的值。为此,可以使用 Node#as() 将给定节点分别缩小为 org.hibernate.validator.path.PropertyNodeorg.hibernate.validator.path.ContainerElementNode 类型,如下面的示例所示:

Hibernate Validator provides an extension to the jakarta.validation.Path API. For nodes of ElementKind.PROPERTY and ElementKind.CONTAINER_ELEMENT it allows to obtain the value of the represented property. To do so, narrow down a given node to the type org.hibernate.validator.path.PropertyNode or org.hibernate.validator.path.ContainerElementNode respectively using Node#as(), as shown in the following example:

示例 12.13:从属性节点获取值

. Example 12.13: Getting the value from property nodes

Building building = new Building();

// Assume the name of the person violates a @Size constraint
Person bob = new Person( "Bob" );
Apartment bobsApartment = new Apartment( bob );
building.getApartments().add( bobsApartment );

Set<ConstraintViolation<Building>> constraintViolations = validator.validate( building );

Path path = constraintViolations.iterator().next().getPropertyPath();
Iterator<Path.Node> nodeIterator = path.iterator();

Path.Node node = nodeIterator.next();
assertEquals( node.getName(), "apartments" );
assertSame( node.as( PropertyNode.class ).getValue(), bobsApartment );

node = nodeIterator.next();
assertEquals( node.getName(), "resident" );
assertSame( node.as( PropertyNode.class ).getValue(), bob );

node = nodeIterator.next();
assertEquals( node.getName(), "name" );
assertEquals( node.as( PropertyNode.class ).getValue(), "Bob" );

这也非常便于获取属性路径上 Set 属性的元素(例如示例中的 apartments),否则该元素将无法识别(与 MapList 不同,在这种情况下没有键或索引)。

This is also very useful to obtain the element of Set properties on the property path (e.g. apartments in the example) which otherwise could not be identified (unlike for Map and List, there is no key nor index in this case).

12.8. Dynamic payload as part of ConstraintViolation

有时,如果约束冲突提供附加数据(即所谓的动态负载),可以辅助对冲突进行自动处理。例如,此动态负载可以包含提示用户如何解决冲突的提示。

In some cases automatic processing of violations can be aided, if the constraint violation provides additional data - a so called dynamic payload. This dynamic payload could for example contain hints to the user on how to resolve the violation.

可以在 custom constraints 中使用 HibernateConstraintValidatorContext 设置动态负载。示例 Example 12.14, “ConstraintValidator implementation setting a dynamic payload” 中显示了此操作,其中 jakarta.validation.ConstraintValidatorContext 被解包到 HibernateConstraintValidatorContext 中以调用 withDynamicPayload

Dynamic payloads can be set in custom constraints using HibernateConstraintValidatorContext. This is shown in example Example 12.14, “ConstraintValidator implementation setting a dynamic payload” where the jakarta.validation.ConstraintValidatorContext is unwrapped to HibernateConstraintValidatorContext in order to call withDynamicPayload.

示例 12.14:设定动态负载的 ConstraintValidator 实现

. Example 12.14: ConstraintValidator implementation setting a dynamic payload

package org.hibernate.validator.referenceguide.chapter12.dynamicpayload;

import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;

public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> {

    private static final Map<Integer, String> suggestedCars = newHashMap();

    static {
        suggestedCars.put( 2, "Chevrolet Corvette" );
        suggestedCars.put( 3, "Toyota Volta" );
        suggestedCars.put( 4, "Maserati GranCabrio" );
        suggestedCars.put( 5, " Mercedes-Benz E-Class" );
    }

    @Override
    public void initialize(ValidPassengerCount constraintAnnotation) {
    }

    @Override
    public boolean isValid(Car car, ConstraintValidatorContext context) {
        if ( car == null ) {
            return true;
        }

        int passengerCount = car.getPassengers().size();
        if ( car.getSeatCount() >= passengerCount ) {
            return true;
        }
        else {

            if ( suggestedCars.containsKey( passengerCount ) ) {
                HibernateConstraintValidatorContext hibernateContext = context.unwrap(
                        HibernateConstraintValidatorContext.class
                );
                hibernateContext.withDynamicPayload( suggestedCars.get( passengerCount ) );
            }
            return false;
        }
    }
}

在约束冲突处理方面,然后可以将 jakarta.validation.ConstraintViolation 解包为 HibernateConstraintViolation,以便检索动态负载进行进一步处理。

On the constraint violation processing side, a jakarta.validation.ConstraintViolation can then in turn be unwrapped to HibernateConstraintViolation in order to retrieve the dynamic payload for further processing.

示例 12.15:检索 ConstraintViolation 的动态负载

. Example 12.15: Retrieval of a ConstraintViolation's dynamic payload

Car car = new Car( 2 );
car.addPassenger( new Person() );
car.addPassenger( new Person() );
car.addPassenger( new Person() );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();
HibernateConstraintViolation<Car> hibernateConstraintViolation = constraintViolation.unwrap(
        HibernateConstraintViolation.class
);
String suggestedCar = hibernateConstraintViolation.getDynamicPayload( String.class );
assertEquals( "Toyota Volta", suggestedCar );

12.9. Enabling Expression Language features

Hibernate Validator 限制了默认公开的表达式语言功能。

Hibernate Validator restricts the Expression Language features exposed by default.

为此目的,我们在 ExpressionLanguageFeatureLevel 中定义了多个功能级别:

For this purpose, we define several feature levels in ExpressionLanguageFeatureLevel:

  1. NONE: Expression Language interpolation is fully disabled.

  2. VARIABLES: Allow interpolation of the variables injected via addExpressionVariable(), resources bundles and usage of the formatter object.

  3. BEAN_PROPERTIES: Allow everything VARIABLES allows plus the interpolation of bean properties.

  4. BEAN_METHODS: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.

根据上下文,我们公开的功能是不同的:

Depending on the context, the features we expose are different:

  1. For constraints, the default level is BEAN_PROPERTIES. For all the built-in constraint messages to be correctly interpolated, you need at least the VARIABLES level.

  2. For custom violations, created via the ConstraintValidatorContext, Expression Language is disabled by default. You can enable it for specific custom violations and, when enabled, it will default to VARIABLES.

引导 ValidatorFactory 时,Hibernate Validator 提供了方法来覆盖这些默认值。

Hibernate Validator provides ways to override these defaults when boostrapping the ValidatorFactory.

要在约束中更改表达式语言功能级别,请使用以下内容:

To change the Expression Language feature level for constraints, use the following:

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .constraintExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES )
        .buildValidatorFactory();

要在特定冲突中更改表达式语言功能级别,请使用以下内容:

To change the Expression Language feature level for custom violations, use the following:

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .customViolationExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES )
        .buildValidatorFactory();

执行此操作将自动为应用程序中的所有自定义违规启用表达式语言。

Doing this will automatically enable Expression Language for all the custom violations in your application.

它应仅用于兼容性并简化从旧版 Hibernate Validator 版本的迁移。

It should only be used for compatibility and to ease the migration from older Hibernate Validator versions.

还可以使用以下属性定义这些级别:

These levels can also be defined using the following properties:

  1. hibernate.validator.constraint_expression_language_feature_level

  2. hibernate.validator.custom_violation_expression_language_feature_level

这些属性的接受值包括:nonevariablesbean-propertiesbean-methods

Accepted values for these properties are: none, variables, bean-properties and bean-methods.

12.10. ParameterMessageInterpolator

默认情况下,Hibernate Validator 要求 Unified EL(请参见 Section 1.1.1, “Unified EL”)的实现可用。这是为了允许使用 Jakarta Bean Validation 规范定义的 EL 表达式插值约束错误消息。

Hibernate Validator requires per default an implementation of the Unified EL (see Section 1.1.1, “Unified EL”) to be available. This is needed to allow the interpolation of constraint error messages using EL expressions as defined by the Jakarta Bean Validation specification.

对于无法或不希望提供 EL 实现的环境,Hibernate Validator 提供了基于非 EL 的消息内插器 - org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator

For environments where you cannot or do not want to provide an EL implementation, Hibernate Validator offers a non EL based message interpolator - org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator.

请参阅 Section 4.2, “Custom message interpolation”以了解如何插入自定义消息插值器实现。

Refer to Section 4.2, “Custom message interpolation” to see how to plug in custom message interpolator implementations.

包含 EL 表达式的约束消息将以未插值的形式由 org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator 返回。这也影响了 使用 EL 表达式的内置默认约束消息。目前,DecimalMinDecimalMax 会受到影响。

Constraint messages containing EL expressions will be returned un-interpolated by org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator. This also affects built-in default constraint messages which use EL expressions. At the moment, DecimalMin and DecimalMax are affected.

12.11. ResourceBundleLocator

通过 ResourceBundleLocator ,Hibernate Validator 提供了一个其他 SPI,允许从 ValidationMessages 以外的其他资源包中检索错误消息,同时仍在使用由规范定义的实际内插算法。请参阅 Section 4.2.1, “ResourceBundleLocator 以了解如何利用该 SPI。

With ResourceBundleLocator, Hibernate Validator provides an additional SPI which allows to retrieve error messages from other resource bundles than ValidationMessages while still using the actual interpolation algorithm as defined by the specification. Refer to Section 4.2.1, “ResourceBundleLocator to learn how to make use of that SPI.

12.12. Customizing the locale resolution

这些合约已标记为 @Incubating,因此将来可能会更改。

These contracts are marked as @Incubating so they might be subject to change in the future.

Hibernate 验证程序提供多个扩展点来构建自定义区域设置解析策略。在内插约束违规消息时,使用已解析的区域设置。

Hibernate Validator provides several extension points to build a custom locale resolution strategy. The resolved locale is used when interpolating the constraint violation messages.

Hibernate Validator 的默认行为是始终使用系统默认语言环境(通过 Locale.getDefault() 获得)。如果(例如)你通常将你的系统语言环境设置为 en-US 但希望你的应用程序提供法语消息,则这可能不是你期望的行为。

The default behavior of Hibernate Validator is to always use the system default locale (as obtained via Locale.getDefault()). This might not be the desired behavior if, for example, you usually set your system locale to en-US but want your application to provide messages in French.

以下示例展示了如何将 Hibernate Validator 默认语言环境设置为 fr-FR

The following example shows how to set the Hibernate Validator default locale to fr-FR:

示例 12.16:配置默认区域设置

. Example 12.16: Configure the default locale

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .defaultLocale( Locale.FRANCE )
        .buildValidatorFactory()
        .getValidator();

Set<ConstraintViolation<Bean>> violations = validator.validate( new Bean() );
assertEquals( "doit avoir la valeur vrai", violations.iterator().next().getMessage() );

虽然这已是一个不错的改进,但在完全国际化的应用程序中,这还不够:你需要 Hibernate 根据用户上下文选择语言环境。

While this is already a nice improvement, in a fully internationalized application, this is not sufficient: you need Hibernate Validator to select the locale depending on the user context.

Hibernate Validator 提供 org.hibernate.validator.spi.messageinterpolation.LocaleResolver SPI,该 SPI 允许微调语言环境解析。通常,在 JAX-RS 环境中,你可以从 Accept-Language HTTP 标头解析需要使用的语言环境。

Hibernate Validator provides the org.hibernate.validator.spi.messageinterpolation.LocaleResolver SPI which allows to fine-tune the resolution of the locale. Typically, in a JAX-RS environment, you can resolve the locale to use from the Accept-Language HTTP header.

在以下示例中,我们使用了硬编码值但是,例如,对于 RESTEasy 应用程序,你可以从 ResteasyContext 提取标头。

In the following example, we use a hardcoded value but, for instance, in the case of a RESTEasy application, you could extract the header from the ResteasyContext.

示例 12.17:通过 LocaleResolver 微调用于内插消息的区域设置

. Example 12.17: Fine tune the locale used to interpolate the messages via a LocaleResolver

LocaleResolver localeResolver = new LocaleResolver() {

    @Override
    public Locale resolve(LocaleResolverContext context) {
        // get the locales supported by the client from the Accept-Language header
        String acceptLanguageHeader = "it-IT;q=0.9,en-US;q=0.7";

        List<LanguageRange> acceptedLanguages = LanguageRange.parse( acceptLanguageHeader );
        List<Locale> resolvedLocales = Locale.filter( acceptedLanguages, context.getSupportedLocales() );

        if ( resolvedLocales.size() > 0 ) {
            return resolvedLocales.get( 0 );
        }

        return context.getDefaultLocale();
    }
};

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .defaultLocale( Locale.FRANCE )
        .locales( Locale.FRANCE, Locale.ITALY, Locale.US )
        .localeResolver( localeResolver )
        .buildValidatorFactory()
        .getValidator();

Set<ConstraintViolation<Bean>> violations = validator.validate( new Bean() );
assertEquals( "deve essere true", violations.iterator().next().getMessage() );

使用 LocaleResolver 时,必须通过 locales() 方法定义支持的语言环境列表。

When using the LocaleResolver, you have to define the list of supported locales via the locales() method.

12.13. Custom contexts

Jakarta Bean Validation 规范在 API 中提供了在多个节点处将给定接口解包到特定于实现的子类型中的可能。在 ConstraintValidator 实现中的约束违例创建和 MessageInterpolator 实例中的消息插值的情况下,存在 unwrap() 方法用于提供的上下文实例——分别为 ConstraintValidatorContextMessageInterpolatorContext。Hibernate Validator 为这两个接口提供自定义扩展。

The Jakarta Bean Validation specification offers at several points in its API the possibility to unwrap a given interface to an implementor specific subtype. In the case of constraint violation creation in ConstraintValidator implementations as well as message interpolation in MessageInterpolator instances, there exist unwrap() methods for the provided context instances - ConstraintValidatorContext respectively MessageInterpolatorContext. Hibernate Validator provides custom extensions for both of these interfaces.

12.13.1. HibernateConstraintValidatorContext

HibernateConstraintValidatorContextConstraintValidatorContext 的子类型,它允许您:

HibernateConstraintValidatorContext is a subtype of ConstraintValidatorContext which allows you to:

  1. enable Expression Language interpolation for a particular custom violation - see below

  2. set arbitrary parameters for interpolation via the Expression Language message interpolation facility using HibernateConstraintValidatorContext#addExpressionVariable(String, Object) or HibernateConstraintValidatorContext#addMessageParameter(String, Object).

示例 155. 自定义 @Future 验证器注入表达式变量_程序包 org.hibernate.validator.referenceguide.chapter12.context;

Example 155. Custom @Future validator injecting an expression variable_package org.hibernate.validator.referenceguide.chapter12.context;

public class MyFutureValidator implements ConstraintValidator<Future, Instant> {

@Override public void initialize(Future constraintAnnotation) { }

@Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; }

HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class );

Instant now = Instant.now( context.getClockProvider().getClock() );

if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addExpressionVariable( "now", now ) .buildConstraintViolationWithTemplate( "Must be after ${now}" ) .addConstraintViolation();

    return false;
}
        return true;
    }
}_
Example 156. Custom _@Future_ validator injecting a message parameter_package org.hibernate.validator.referenceguide.chapter12.context;

public class MyFutureValidatorMessageParameter implements ConstraintValidator<Future, Instant> {

@Override public void initialize(Future constraintAnnotation) { }

@Override public boolean isValid(Instant value, ConstraintValidatorContext context) { if ( value == null ) { return true; }

HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class );

Instant now = Instant.now( context.getClockProvider().getClock() );

if ( !value.isAfter( now ) ) { hibernateContext.disableDefaultConstraintViolation(); hibernateContext .addMessageParameter( "now", now ) .buildConstraintViolationWithTemplate( "Must be after {now}" ) .addConstraintViolation();

    return false;
}
        return true;
    }
}_Apart from the syntax, the main difference between message parameters and expression variables is that message parameters are simply interpolated whereas expression variables are interpreted using the Expression Language engine. In practice, use message parameters if you do not need the advanced features of an Expression Language.

请注意,通过 addExpressionVariable(String, Object)addMessageParameter(String, Object) 指定的参数是全局参数,并应用于此 isValid() 调 用创建的所有约束违规行为。这包括默认约束违规行为,以及通过 ConstraintViolationBuilder 创建的所有违规行为。但是,你可以在 ConstraintViolationBuilder#addConstraintViolation() 的调用之间更新参数。

Note that the parameters specified via addExpressionVariable(String, Object) and addMessageParameter(String, Object) are global and apply to all constraint violations created by this isValid() invocation. This includes the default constraint violation, but also all violations created by the ConstraintViolationBuilder. You can, however, update the parameters between invocations of ConstraintViolationBuilder#addConstraintViolation().

默认情况下,Expression Language 插值对自定义违规禁用,这是为了避免任意代码执行或在消息模板是由未正确转义的用户输入构建时发生敏感数据泄漏。

By default, Expression Language interpolation is disabled for custom violations, this to avoid arbitrary code execution or sensitive data leak if message templates are built from improperly escaped user input.

可以通过使用 enableExpressionLanguage() 为给定的自定义违例启用表达式语言,如下例所示:

It is possible to enable Expression Language for a given custom violation by using enableExpressionLanguage() as shown in the example below:

public class SafeValidator implements ConstraintValidator<ZipCode, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ( value == null ) {
            return true;
        }

        HibernateConstraintValidatorContext hibernateContext = context.unwrap(
                HibernateConstraintValidatorContext.class );
        hibernateContext.disableDefaultConstraintViolation();

        if ( isInvalid( value ) ) {
            hibernateContext
                    .addExpressionVariable( "validatedValue", value )
                    .buildConstraintViolationWithTemplate( "${validatedValue} is not a valid ZIP code" )
                    .enableExpressionLanguage()
                    .addConstraintViolation();

            return false;
        }

        return true;
    }

    private boolean isInvalid(String value) {
        // ...
        return false;
    }
}

在这种情况下,消息模板将由表达式语言引擎插值。

In this case, the message template will be interpolated by the Expression Language engine.

默认情况下,在启用表达式语言时仅启用变量插值。

By default, only variables interpolation is enabled when enabling Expression Language.

您可以使用 HibernateConstraintViolationBuilder#enableExpressionLanguage(ExpressionLanguageFeatureLevel level) 启用更多功能。

You can enable more features by using HibernateConstraintViolationBuilder#enableExpressionLanguage(ExpressionLanguageFeatureLevel level).

我们为表达式语言插值定义了几个级别的功能:

We define several levels of features for Expression Language interpolation:

  1. NONE: Expression Language interpolation is fully disabled - this is the default for custom violations.

  2. VARIABLES: Allow interpolation of the variables injected via addExpressionVariable(), resources bundles and usage of the formatter object.

  3. BEAN_PROPERTIES: Allow everything VARIABLES allows plus the interpolation of bean properties.

  4. BEAN_METHODS: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.[.iokays-translated-946a1024d492dcf97679aa58d9f46940]

使用 addExpressionVariable() 是将变量注入表达式中的唯一安全方法,如果你使用 BEAN_PROPERTIES 或 5 @{s} 功能级别,那它尤其重要。

Using addExpressionVariable() is the only safe way to inject a variable into an expression and it’s especially important if you use the BEAN_PROPERTIES or BEAN_METHODS feature levels.

如果你通过在消息中简单的连接用户输入来注入用户输入,那将允许执行任意代码并造成敏感数据泄漏:如果用户输入包含有效的表达式,那它们将由 Expression Language 引擎执行。

If you inject user input by simply concatenating the user input in the message, you will allow potential arbitrary code execution and sensitive data leak: if the user input contains valid expressions, they will be executed by the Expression Language engine.

以下是你绝对不应做的事情的一个示例:

Here is an example of something you should ABSOLUTELY NOT do:

_public class UnsafeValidator implements ConstraintValidator<ZipCode, String> {

@Override public boolean isValid(String value, ConstraintValidatorContext context) { if ( value == null ) { return true; }

context.disableDefaultConstraintViolation();

HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); hibernateContext.disableDefaultConstraintViolation();

if ( isInvalid( value ) ) { hibernateContext // THIS IS UNSAFE, DO NOT COPY THIS EXAMPLE .buildConstraintViolationWithTemplate( value + " is not a valid ZIP code" ) .enableExpressionLanguage() .addConstraintViolation();

    return false;
}
    return true;
}
    private boolean isInvalid(String value) {
        // ...
        return false;
    }
}_
_public class UnsafeValidator implements ConstraintValidator<ZipCode, String> {

@Override public boolean isValid(String value, ConstraintValidatorContext context) { if ( value == null ) { return true; }

context.disableDefaultConstraintViolation();

HibernateConstraintValidatorContext hibernateContext = context.unwrap( HibernateConstraintValidatorContext.class ); hibernateContext.disableDefaultConstraintViolation();

if ( isInvalid( value ) ) { hibernateContext // THIS IS UNSAFE, DO NOT COPY THIS EXAMPLE .buildConstraintViolationWithTemplate( value + " is not a valid ZIP code" ) .enableExpressionLanguage() .addConstraintViolation();

    return false;
}
    return true;
}
    private boolean isInvalid(String value) {
        // ...
        return false;
    }
}_
In the example above, if _value_, which might be user input, contains a valid expression, it will be interpolated by the Expression Language engine, potentially leading to unsafe behaviors.

12.13.2. HibernateMessageInterpolatorContext

Hibernate Validator 也提供了一个 MessageInterpolatorContext 的自定义扩展,即 HibernateMessageInterpolatorContext (参阅 Example 12.18, “HibernateMessageInterpolatorContext )。引入此子类型是为了允许 Hibernate Validator 更好地集成到 Glassfish 中。在这种情况下,需要根 bean 类型以确定消息资源包的正确类加载器。如果你有其他用例,请告知我们。

Hibernate Validator also offers a custom extension of MessageInterpolatorContext, namely HibernateMessageInterpolatorContext (see Example 12.18, “HibernateMessageInterpolatorContext). This subtype was introduced to allow a better integration of Hibernate Validator into Glassfish. The root bean type was in this case needed to determine the right class loader for the message resource bundle. If you have any other use cases, let us know.

Example 12.18: HibernateMessageInterpolatorContext

. Example 12.18: HibernateMessageInterpolatorContext

public interface HibernateMessageInterpolatorContext extends MessageInterpolator.Context {

    /**
     * Returns the currently validated root bean type.
     *
     * @return The currently validated root bean type.
     */
    Class<?> getRootBeanType();

    /**
     * @return the message parameters added to this context for interpolation
     *
     * @since 5.4.1
     */
    Map<String, Object> getMessageParameters();

    /**
     * @return the expression variables added to this context for EL interpolation
     *
     * @since 5.4.1
     */
    Map<String, Object> getExpressionVariables();

    /**
     * @return the path to the validated constraint starting from the root bean
     *
     * @since 6.1
     */
    Path getPropertyPath();

    /**
     * @return the level of features enabled for the Expression Language engine
     *
     * @since 6.2
     */
    ExpressionLanguageFeatureLevel getExpressionLanguageFeatureLevel();
}

12.14. Paranamer based ParameterNameProvider

Hibernate Validator 带有一个 ParameterNameProvider 实现,它利用 Paranamer 库。

Hibernate Validator comes with a ParameterNameProvider implementation which leverages the Paranamer library.

该库提供了在运行时获取参数名称的多种方式,例如基于 Java 编译器创建的调试符号、在后编译步骤中以字节码形式插入参数名称的常量或如 JSR 330 中的 @Named 注释。

This library provides several ways for obtaining parameter names at runtime, e.g. based on debug symbols created by the Java compiler, constants with the parameter names woven into the bytecode in a post-compile step or annotations such as the @Named annotation from JSR 330.

为了使用 ParanamerParameterNameProvider ,可以在引导验证器时传递一个实例,如 Example 9.10, “Using a custom ParameterNameProvider 中所示,或将 org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider 指定为 META-INF/validation.xml 文件中 <parameter-name-provider> 元素的值。

In order to use ParanamerParameterNameProvider, either pass an instance when bootstrapping a validator as shown in Example 9.10, “Using a custom ParameterNameProvider or specify org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider as value for the <parameter-name-provider> element in the META-INF/validation.xml file.

当使用此参数名称提供程序时,你需要将 Paranamer 库添加到你的类路径中。它在 Maven 中心仓库中提供,组 ID 为 com.thoughtworks.paranamer,构件 ID 为 paranamer

When using this parameter name provider, you need to add the Paranamer library to your classpath. It is available in the Maven Central repository with the group id com.thoughtworks.paranamer and the artifact id paranamer.

默认情况下,ParanamerParameterNameProvider 从构建时添加到字节码的常量(通过 DefaultParanamer)和调试符号(通过 BytecodeReadingParanamer)中检索参数名称。或者,你可以在创建 ParanamerParameterNameProvider 实例时指定你自己选择的 Paranamer 实现。

By default ParanamerParameterNameProvider retrieves parameter names from constants added to the byte code at build time (via DefaultParanamer) and debug symbols (via BytecodeReadingParanamer). Alternatively you can specify a Paranamer implementation of your choice when creating a ParanamerParameterNameProvider instance.

12.15. Providing constraint definitions

Jakarta Bean Validation 允许在约束映射文件中通过 XML 重新定义约束定义。有关更多信息,请参阅 Section 8.2, “Mapping constraints via constraint-mappings ,有关示例,请参阅 Example 8.2, “Bean constraints configured via XML” 。虽然此方法足以满足许多用例,但它在其他方面有其缺点。例如,想象一个约束库希望为自定义类型添加约束定义。此库可以提供包含其库的映射文件,但此文件仍需要库的用户引用。幸运的是,有更好的方法。

Jakarta Bean Validation allows to (re-)define constraint definitions via XML in its constraint mapping files. See Section 8.2, “Mapping constraints via constraint-mappings for more information and Example 8.2, “Bean constraints configured via XML” for an example. While this approach is sufficient for many use cases, it has its shortcomings in others. Imagine for example a constraint library wanting to contribute constraint definitions for custom types. This library could provide a mapping file with their library, but this file still would need to be referenced by the user of the library. Luckily there are better ways.

以下概念目前被视为实验性的。请告知我们你发现它们是否有用,以及它们是否满足你的需求。

The following concepts are considered experimental at this time. Let us know whether you find them useful and whether they meet your needs.

12.15.1. Constraint definitions via ServiceLoader

Hibernate Validator 允许利用 Java 的 ServiceLoader 机制来注册额外的约束定义。您只需将 jakarta.validation.ConstraintValidator 文件添加到 META-INF/services 中。在这个服务文件中,您列出约束验证器类的完全限定类名(每行一个)。Hibernate Validator 会自动推断出它们适用的约束类型。请查看 Constraint definition via service file 查看示例。

Hibernate Validator allows to utilize Java’s ServiceLoader mechanism to register additional constraint definitions. All you have to do is to add the file jakarta.validation.ConstraintValidator to META-INF/services. In this service file you list the fully qualified classnames of your constraint validator classes (one per line). Hibernate Validator will automatically infer the constraint types they apply to. See Constraint definition via service file for an example.

Example 12.19: META-INF/services/jakarta.validation.ConstraintValidator

. Example 12.19: META-INF/services/jakarta.validation.ConstraintValidator

# Assuming a custom constraint annotation @org.mycompany.CheckCase
org.mycompany.CheckCaseValidator

要为自定义约束提供默认消息,请将文件 ContributorValidationMessages.properties 和/或其特定区域设置的专有化置于 JAR 的根目录中。除了在 ValidationMessages.properties 中给出的那些消息之外,Hibernate Validator 还将考虑类路径中发现的具有该名称的所有包中的条目。

To contribute default messages for your custom constraints, place a file ContributorValidationMessages.properties and/or its locale-specific specializations at the root of your JAR. Hibernate Validator will consider the entries from all the bundles with this name found on the classpath in addition to those given in ValidationMessages.properties.

在创建大型多模块应用程序时,此机制也很有帮助:您不必将所有约束消息放入一个包中,而是可以为包含该模块中那些消息的每个模块设置一个资源包。

This mechanism is also helpful when creating large multi-module applications: instead of putting all the constraint messages into one single bundle, you can have one resource bundle per module containing only those messages of that module.

我们强烈建议阅读 this blog post by Marko Bekhta,一步步指导你完成创建包含自定义约束并通过 ServiceLoader 声明它们的独立 JAR 的过程。

We highly recommend the reading of this blog post by Marko Bekhta, guiding you step by step through the process of creating an independent JAR that contains your custom constraints and declares them via the ServiceLoader.

12.15.2. Adding constraint definitions programmatically

虽然服务加载程序方法可在许多场景中使用,但不能在所有场景中使用(例如,服务文件不可见的 OSGi),但是还有另一种方式来提供约束定义。您可以使用编程约束声明 API - 请参阅 Example 12.20, “Adding constraint definitions through the programmatic API”

While the service loader approach works in many scenarios, but not in all (think for example OSGi where service files are not visible), there is yet another way of contributing constraint definitions. You can use the programmatic constraint declaration API - see Example 12.20, “Adding constraint definitions through the programmatic API”.

Example 12.20:通过编程 API 添加约束定义

. Example 12.20: Adding constraint definitions through the programmatic API

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
        .constraintDefinition( ValidPassengerCount.class )
        .validatedBy( ValidPassengerCountValidator.class );

如果验证器实现比较简单(即无需从注释进行初始化,且不使用 ConstraintValidatorContext),还可以使用此替代 API 来使用 Lambda 表达式或方法引用指定约束逻辑:

If your validator implementation is rather simple (i.e. no initialization from the annotation is needed, and ConstraintValidatorContext is not used), you also can use this alternative API to specify the constraint logic using a Lambda expression or method reference:

Example 12.21:使用 Lambda 表达式添加约束定义

. Example 12.21: Adding constraint definition with a Lambda expression

ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping
        .constraintDefinition( ValidPassengerCount.class )
            .validateType( Bus.class )
                .with( b -> b.getSeatCount() >= b.getPassengers().size() );

你可以使用一个 ConstraintMappingContributor ,按照 Section 12.5, “Applying programmatic constraint declarations to the default validator factory” 中的详细说明,而不是直接将约束映射添加到配置对象。在使用 META-INF/validation.xml 配置默认验证器工厂时,这可能很有用(参阅 Chapter 8, Configuring via XML )。

Instead of directly adding a constraint mapping to the configuration object, you may use a ConstraintMappingContributor as detailed in Section 12.5, “Applying programmatic constraint declarations to the default validator factory”. This can be useful when configuring the default validator factory using META-INF/validation.xml (see Chapter 8, Configuring via XML).

通过编程 API 注册约束定义的一个用例是能够为 @URL 约束指定备用约束验证器。从历史上看,Hibernate Validator 的此约束的默认约束验证器使用 java.net.URL 构造函数验证 URL。但是,还可以使用 ConstraintDefinitionContributor 配置的基于纯正则表达式的版本:

One use case for registering constraint definitions through the programmatic API is the ability to specify an alternative constraint validator for the @URL constraint. Historically, Hibernate Validator’s default constraint validator for this constraint uses the java.net.URL constructor to validate an URL. However, there is also a purely regular expression based version available which can be configured using a ConstraintDefinitionContributor:

使用编程约束声明 API 注册一个基于正则表达式的约束定义以获取 _@URL__ConstraintMapping constraintMapping = configuration.createConstraintMapping();

Using the programmatic constraint declaration API to register a regular expression based constraint definition for _@URL__ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping .constraintDefinition( URL.class ) .includeExistingValidators( false ) .validatedBy( RegexpURLValidator.class );_

constraintMapping .constraintDefinition( URL.class ) .includeExistingValidators( false ) .validatedBy( RegexpURLValidator.class );_

使用基于程序约束声明的 API 为 @URL _ConstraintMapping constraintMapping = configuration.createConstraintMapping() 注册一个基于正则表达式的约束定义。

Using the programmatic constraint declaration API to register a regular expression based constraint definition for @URL _ConstraintMapping constraintMapping = configuration.createConstraintMapping();

constraintMapping .constraintDefinition( URL.class ) .includeExistingValidators( false ) .validatedBy( RegexpURLValidator.class );_

constraintMapping .constraintDefinition( URL.class ) .includeExistingValidators( false ) .validatedBy( RegexpURLValidator.class );_

12.16. Customizing class-loading

在以下几种情况下,Hibernate Validator 需要加载以名称给出的资源或类:

There are several cases in which Hibernate Validator needs to load resources or classes given by name:

  1. XML descriptors (META-INF/validation.xml as well as XML constraint mappings)

  2. classes specified by name in XML descriptors (e.g. custom message interpolators etc.)

  3. the ValidationMessages resource bundle

  4. the ExpressionFactory implementation used for expression based message interpolation

默认情况下,Hibernate 验证器尝试通过当前线程上下文类加载器加载这些资源。如果这样做不成功,则 Hibernate 验证器自己的类加载器将被尝试作为备选方案。

By default, Hibernate Validator tries to load these resources via the current thread context class loader. If that’s not successful, Hibernate Validator’s own class loader will be tried as a fallback.

在该策略不合适的情况下(例如模块化环境,如 OSGi),您可以在启动验证器工厂时为加载这些资源提供一个特定的类加载器:

For cases where this strategy is not appropriate (e.g. modularized environments such as OSGi), you may provide a specific class loader for loading these resources when bootstrapping the validator factory:

示例 12.22:提供一个类加载器来加载外部资源和类

. Example 12.22: Providing a class loader for loading external resources and classes

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .externalClassLoader( classLoader )
        .buildValidatorFactory()
        .getValidator();

在 OSGi 的情况下,您可以传递一个类加载器,该类加载器来自引导 Hibernate 验证器的软件包或自定义类加载器实现(它委托给 Bundle#loadClass() 等)。

In the case of OSGi, you could e.g. pass the loader of a class from the bundle bootstrapping Hibernate Validator or a custom class loader implementation which delegates to Bundle#loadClass() etc.

如果不再需要给定的验证器工厂实例,请调用 ValidatorFactory#close()。如果不这样做,可能会导致类加载器泄露,在应用程序/软件包重新部署的情况下,非关闭验证器工厂仍被应用程序代码引用。

Call ValidatorFactory#close() if a given validator factory instance is not needed any longer. Failure to do so may result in a class loader leak in cases where applications/bundles are re-deployed and a non-closed validator factory still is referenced by application code.

12.17. Customizing the getter property selection strategy

当由 Hibernate 验证器验证一个 Bean 时,它的属性也会得到验证。属性可以是字段或 getter。默认情况下,Hibernate 验证器遵循 JavaBeans 规范并且在下述条件之一为 true 时将方法视为 getter:

When a bean is validated by Hibernate Validator, its properties get validated. A property can either be a field or a getter. By default, Hibernate Validator respects the JavaBeans specification and considers a method as a getter as soon as one of the conditions below is true:

  1. the method name starts with get, it has a non-void return type and has no parameters;

  2. the method name starts with is, has a return type of boolean and has no parameters;

  3. the method name starts with has, has a return type of boolean and has no parameters (this rule is specific to Hibernate Validator and is not mandated by the JavaBeans specification)

虽然在遵循经典 JavaBeans 惯例时这些规则通常是合适的,但可能发生这种情况,尤其是使用代码生成器时,JavaBeans 命名惯例未得到遵循并且 getter 的名称遵循不同的惯例。

While these rules are usually appropriate when following the classic JavaBeans convention, it might happen, especially with code generators, that the JavaBeans naming convention is not followed and that the getters' names are following a different convention.

在这种情况下,应重新定义检测 getter 的策略,以完全验证对象。

In this case, the strategy for detecting getters should be redefined in order to fully validate the object.

当类遵循流利的命名约定时,这也是需求的一个经典示例,如 Example 12.23, “A class that uses non-standard getters” 中所示。

A classic example of this requirement is when the classes follow a fluent naming convention, as illustrated in Example 12.23, “A class that uses non-standard getters”.

示例 12.23:一个使用非标准 getter 的类

. Example 12.23: A class that uses non-standard getters

package org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy;

public class User {

    private String firstName;
    private String lastName;
    private String email;

    // [...]

    @NotEmpty
    public String firstName() {
        return firstName;
    }

    @NotEmpty
    public String lastName() {
        return lastName;
    }

    @Email
    public String email() {
        return email;
    }
}

如果此类对象得到验证,则不执行 getter 上的验证,因为标准策略未检测到它们。

If such object gets validated, no validation will be performed on the getters as they are not detected by the standard strategy.

示例 12.24:使用默认 getter 属性选择策略验证一个具有非标准 getter 的类

. Example 12.24: Validating a class with non-standard getters using the default getter property selection strategy

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .buildValidatorFactory()
        .getValidator();

User user = new User( "", "", "not an email" );

Set<ConstraintViolation<User>> constraintViolations = validator.validate( user );

// as User has non-standard getters no violations are triggered
assertEquals( 0, constraintViolations.size() );

要使 Hibernate 验证器将此类方法视为属性,应配置一个自定义 GetterPropertySelectionStrategy。在此特定情况下,策略的一个可能实现是:

To make Hibernate Validator treat such methods as properties, a custom GetterPropertySelectionStrategy should be configured. In this particular case, a possible implementation of the strategy would be:

示例 12.25:自定义 GetterPropertySelectionStrategy 实现

. Example 12.25: Custom GetterPropertySelectionStrategy implementation

package org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy;

public class FluentGetterPropertySelectionStrategy implements GetterPropertySelectionStrategy {

    private final Set<String> methodNamesToIgnore;

    public FluentGetterPropertySelectionStrategy() {
        // we will ignore all the method names coming from Object
        this.methodNamesToIgnore = Arrays.stream( Object.class.getDeclaredMethods() )
                .map( Method::getName )
                .collect( Collectors.toSet() );
    }

    @Override
    public Optional<String> getProperty(ConstrainableExecutable executable) {
        if ( methodNamesToIgnore.contains( executable.getName() )
                || executable.getReturnType() == void.class
                || executable.getParameterTypes().length > 0 ) {
            return Optional.empty();
        }

        return Optional.of( executable.getName() );
    }

    @Override
    public List<String> getGetterMethodNameCandidates(String propertyName) {
        // As method name == property name, there always is just one possible name for a method
        return Collections.singletonList( propertyName );
    }
}

配置 Hibernate Validator 以使用此策略有多种方法。它可以通过编程方式(参见 Example 12.26, “Configuring a custom GetterPropertySelectionStrategy programmatically” )或通过 XML 配置中的 hibernate.validator.getter_property_selection_strategy 属性(参见 Example 12.27, “Configuring a custom GetterPropertySelectionStrategy using an XML property” )来完成。

There are multiple ways to configure Hibernate Validator to use this strategy. It can either be done programmatically (see Example 12.26, “Configuring a custom GetterPropertySelectionStrategy programmatically”) or by using the hibernate.validator.getter_property_selection_strategy property in the XML configuration (see Example 12.27, “Configuring a custom GetterPropertySelectionStrategy using an XML property”).

示例 12.26:以编程方式配置一个自定义 GetterPropertySelectionStrategy

. Example 12.26: Configuring a custom GetterPropertySelectionStrategy programmatically

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        // Setting a custom getter property selection strategy
        .getterPropertySelectionStrategy( new FluentGetterPropertySelectionStrategy() )
        .buildValidatorFactory()
        .getValidator();

User user = new User( "", "", "not an email" );

Set<ConstraintViolation<User>> constraintViolations = validator.validate( user );

assertEquals( 3, constraintViolations.size() );
示例 12.27:使用 XML 属性配置一个自定义 GetterPropertySelectionStrategy

. Example 12.27: Configuring a custom GetterPropertySelectionStrategy using an XML property

<validation-config
        xmlns="https://jakarta.ee/xml/ns/validation/configuration"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/configuration
            https://jakarta.ee/xml/ns/validation/validation-configuration-3.0.xsd"
        version="3.0">

    <property name="hibernate.validator.getter_property_selection_strategy">
        org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy.NoPrefixGetterPropertySelectionStrategy
    </property>

</validation-config>

需要提到的是,在使用 HibernateValidatorConfiguration#addMapping(ConstraintMapping) 添加程序约束的情况下,应该始终在配置所需的 getter 属性选择策略后才添加映射。否则,将对在定义策略之前添加的映射使用默认策略。

It is important to mention that in cases where programmatic constraints are added using HibernateValidatorConfiguration#addMapping(ConstraintMapping), adding mappings should always be done after the required getter property selection strategy is configured. Otherwise, the default strategy will be used for the mappings added before defining the strategy.

12.18. Customizing the property name resolution for constraint violations

想象一下,我们有一个带有 @NotNull 约束的简单数据类:

Imagine that we have a simple data class that has @NotNull constraints on some fields:

示例 12.28:Person 数据类

. Example 12.28: Person data class

public class Person {
    @NotNull
    @JsonProperty("first_name")
    private final String firstName;

    @JsonProperty("last_name")
    private final String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

通过使用 Jackson库,此类可序列化为 JSON:

This class can be serialized to JSON by using the Jackson library:

示例 12.29:将 Person 对象序列化为 JSON

. Example 12.29: Serializing Person object to JSON

public class PersonSerializationTest {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Test
    public void personIsSerialized() throws JsonProcessingException {
        Person person = new Person( "Clark", "Kent" );

        String serializedPerson = objectMapper.writeValueAsString( person );

        assertEquals( "{\"first_name\":\"Clark\",\"last_name\":\"Kent\"}", serializedPerson );
    }
}

顾名思义,对象被序列化为:

As we can see, the object is serialized to:

示例 12.30:Person 作为 json

. Example 12.30: Person as json

{
  "first_name": "Clark",
  "last_name": "Kent"
}

请注意,属性的名称如何不同。在 Java 对象中,我们有 firstNamelastName,而在 JSON 输出中,我们有 first_namelast_name。我们通过 @JsonProperty 注释自定义了此行为。

Notice how the names of the properties differ. In the Java object, we have firstName and lastName, whereas in the JSON output, we have first_name and last_name. We customized this behavior through @JsonProperty annotations.

现在想象一下我们在 REST 环境中使用此类,其中用户可以在请求体中发送 a Person instance as JSON 。在指示验证失败的字段时,最好指示他们在 JSON 请求中使用的名称 first_name ,而不是我们在 Java 代码中内部使用的名称 firstName

Now imagine that we use this class in a REST environment, where a user can send a Person instance as JSON in the request body. It would be nice, when indicating on which field the validation failed, to indicate the name they use in their JSON request, first_name, and not the name we use internally in our Java code, firstName.

org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider 合同允许我们这样做。通过实现它,我们可以定义在验证期间如何解析属性的名称。在我们的示例中,我们希望从 Jackson 配置中读取值。

The org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider contract allows us to do this. By implementing it, we can define how the name of a property will be resolved during validation. In our case, we want to read the value from the Jackson configuration.

这里有一个如何执行此操作的示例:

One example of how to do this is to leverage the Jackson API:

示例 12.31:JacksonPropertyNodeNameProvider 实现

. Example 12.31: JacksonPropertyNodeNameProvider implementation

import org.hibernate.validator.spi.nodenameprovider.JavaBeanProperty;
import org.hibernate.validator.spi.nodenameprovider.Property;
import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider;

import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;

public class JacksonPropertyNodeNameProvider implements PropertyNodeNameProvider {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String getName(Property property) {
        if ( property instanceof JavaBeanProperty ) {
            return getJavaBeanPropertyName( (JavaBeanProperty) property );
        }

        return getDefaultName( property );
    }

    private String getJavaBeanPropertyName(JavaBeanProperty property) {
        JavaType type = objectMapper.constructType( property.getDeclaringClass() );
        BeanDescription desc = objectMapper.getSerializationConfig().introspect( type );

        return desc.findProperties()
                .stream()
                .filter( prop -> prop.getInternalName().equals( property.getName() ) )
                .map( BeanPropertyDefinition::getName )
                .findFirst()
                .orElse( property.getName() );
    }

    private String getDefaultName(Property property) {
        return property.getName();
    }
}

并在进行验证时:

And when doing the validation:

示例 12.32:JacksonPropertyNodeNameProvider 用法

. Example 12.32: JacksonPropertyNodeNameProvider usage

public class JacksonPropertyNodeNameProviderTest {
    @Test
    public void nameIsReadFromJacksonAnnotationOnField() {
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .propertyNodeNameProvider( new JacksonPropertyNodeNameProvider() )
                .buildValidatorFactory();

        Validator validator = validatorFactory.getValidator();

        Person clarkKent = new Person( null, "Kent" );

        Set<ConstraintViolation<Person>> violations = validator.validate( clarkKent );
        ConstraintViolation<Person> violation = violations.iterator().next();

        assertEquals( violation.getPropertyPath().toString(), "first_name" );
    }

我们可以看到属性路径现在返回 first_name

We can see that the property path now returns first_name.

请注意,当 getter 中存在这些注解时,此操作同样有效:

Note that this also works when the annotations are on a getter:

示例 12.33:在 getter 上进行注释

. Example 12.33: Annotation on a getter

@Test
public void nameIsReadFromJacksonAnnotationOnGetter() {
    ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
            .configure()
            .propertyNodeNameProvider( new JacksonPropertyNodeNameProvider() )
            .buildValidatorFactory();

    Validator validator = validatorFactory.getValidator();

    Person clarkKent = new Person( null, "Kent" );

    Set<ConstraintViolation<Person>> violations = validator.validate( clarkKent );
    ConstraintViolation<Person> violation = violations.iterator().next();

    assertEquals( violation.getPropertyPath().toString(), "first_name" );
}

public class Person {
    private final String firstName;

    @JsonProperty("last_name")
    private final String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @NotNull
    @JsonProperty("first_name")
    public String getFirstName() {
        return firstName;
    }
}

这只是我们想要更改属性名称解析方式的一个用例。

This is just one use case of why we would like to change how the property names are resolved.

org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider 可以按你认为合适的任何方式实现以提供属性名称(例如,从注解中读取)。

org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider can be implemented to provide a property name in whatever way you see fit (reading from annotations, for instance).

值得一提的是还有另外两个界面:

There are two more interfaces that are worth mentioning:

  1. org.hibernate.validator.spi.nodenameprovider.Property is a base interface that holds metadata about a property. It has a single String getName() method that can be used to get the "original" name of a property. This interface should be used as a default way of resolving the name (see how it is used in Example 12.31, “JacksonPropertyNodeNameProvider implementation”).

  2. org.hibernate.validator.spi.nodenameprovider.JavaBeanProperty is an interface that holds metadata about a bean property. It extends org.hibernate.validator.spi.nodenameprovider.Property and provide some additional methods like Class<?> getDeclaringClass() which returns the class that is the owner of the property.