Hibernate Validator 中文操作指南

9. Bootstrapping

Section 2.2.1, “Obtaining a Validator instance” 中,您已经看到了一种创建 Validator 实例的方法 - 通过 Validation#buildDefaultValidatorFactory() 。在本章中,您将学习如何使用 jakarta.validation.Validation 中的其他方法来启动经过特定配置的验证器。

In Section 2.2.1, “Obtaining a Validator instance”, you already saw one way of creating a Validator instance - via Validation#buildDefaultValidatorFactory(). In this chapter, you will learn how to use the other methods in jakarta.validation.Validation in order to bootstrap specifically configured validators.

9.1. Retrieving ValidatorFactory and Validator

通过一个 ValidatorFactory 静态方法引用 jakarta.validation.Validation 来检索 Validator 并调用工厂实例的 getValidator(),即可获取该方法。

You obtain a Validator by retrieving a ValidatorFactory via one of the static methods on jakarta.validation.Validation and calling getValidator() on the factory instance.

Example 9.1, “Bootstrapping default ValidatorFactory and Validator 展示了如何从默认校验程序工厂获得一个校验程序:

Example 9.1, “Bootstrapping default ValidatorFactory and Validator shows how to obtain a validator from the default validator factory:

示例 9.1: 自举默认的 ValidatorFactoryValidator

. Example 9.1: Bootstrapping default ValidatorFactory and Validator

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

生成的 ValidatorFactoryValidator 实例是线程安全的,可以缓存。由于 Hibernate Validator 使用工厂作为缓存约束元数据的上下文,因此建议在应用程序中使用一个工厂实例。

The generated ValidatorFactory and Validator instances are thread-safe and can be cached. As Hibernate Validator uses the factory as context for caching constraint metadata it is recommended to work with one factory instance within an application.

Jakarta Bean 验证支持在一个应用程序中使用多个提供者,例如 Hibernate 验证程序。如果类路径上存在多个提供者,在通过 buildDefaultValidatorFactory() 创建工厂时,无法保证选择哪个。

Jakarta Bean Validation supports working with several providers such as Hibernate Validator within one application. If more than one provider is present on the classpath, it is not guaranteed which one is chosen when creating a factory via buildDefaultValidatorFactory().

在这种情况下,您可以通过 Validation#byProvider() 显式指定要使用的供应商,并像 Example 9.2, “Bootstrapping ValidatorFactory and Validator using a specific provider” 中所示的那样传递供应商的 ValidationProvider 类。

In this case, you can explicitly specify the provider to use via Validation#byProvider(), passing the provider’s ValidationProvider class as shown in Example 9.2, “Bootstrapping ValidatorFactory and Validator using a specific provider”.

示例 9.2:使用特定供应商自举 ValidatorFactoryValidator

. Example 9.2: Bootstrapping ValidatorFactory and Validator using a specific provider

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

请注意,configure() 返回的配置对象允许在调用 buildValidatorFactory() 之前专门自定义工厂。本章后续会讨论可用的选项。

Note that the configuration object returned by configure() allows to specifically customize the factory before calling buildValidatorFactory(). The available options are discussed later in this chapter.

同样,您可以检索用于配置的默认验证器工厂,这在 Example 9.3, “Retrieving the default ValidatorFactory for configuration” 中进行了演示。

Similarly you can retrieve the default validator factory for configuration which is demonstrated in Example 9.3, “Retrieving the default ValidatorFactory for configuration”.

示例 9.3:检索用于配置的默认 ValidatorFactory

. Example 9.3: Retrieving the default ValidatorFactory for configuration

ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

如果一个 ValidatorFactory 实例不再被使用,应该通过调用 ValidatorFactory#close() 进行清理。这将释放工厂可能分配的任何资源。

If a ValidatorFactory instance is no longer in use, it should be disposed by calling ValidatorFactory#close(). This will free any resources possibly allocated by the factory.

9.1.1. ValidationProviderResolver

默认情况下,会使用 Java Service Provider机制检测可用的 Jakarta Bean 验证提供程序。

By default, available Jakarta Bean Validation providers are discovered using the Java Service Provider mechanism.

出于该目的,每个提供程序都包含文件 META- INF/services/jakarta.validation.spi.ValidationProvider,它包含其 ValidationProvider 实现的完全限定类名。对于 Hibernate Validator,它就是 org.hibernate.validator.HibernateValidator

For that purpose, each provider includes the file META- INF/services/jakarta.validation.spi.ValidationProvider, containing the fully qualified classname of its ValidationProvider implementation. In the case of Hibernate Validator, this is org.hibernate.validator.HibernateValidator.

根据你的环境及其类加载特性,可能无法通过 Java 的服务加载器机制发现提供程序。这种情况下,你可以插入执行提供程序检索的自定义 ValidationProviderResolver 实现。一个示例是 OSGi,在其中你可以实现一个使用 OSGi 服务进行提供程序发现的提供程序解析器。

Depending on your environment and its classloading specifics, provider discovery via the Java’s service loader mechanism might not work. In this case, you can plug in a custom ValidationProviderResolver implementation which performs the provider retrieval. An example is OSGi, where you could implement a provider resolver which uses OSGi services for provider discovery.

要使用自定义供应商解析器,请通过 Example 9.4, “Using a custom ValidationProviderResolver 中所示的 providerResolver() 传递它。

To use a custom provider resolver, pass it via providerResolver() as shown in Example 9.4, “Using a custom ValidationProviderResolver.

示例 9.4:使用自定义 ValidationProviderResolver

. Example 9.4: Using a custom ValidationProviderResolver

package org.hibernate.validator.referenceguide.chapter09;

public class OsgiServiceDiscoverer implements ValidationProviderResolver {

    @Override
    public List<ValidationProvider<?>> getValidationProviders() {
        //...
        return null;
    }
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .providerResolver( new OsgiServiceDiscoverer() )
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2. Configuring a ValidatorFactory

默认情况下,从 Validation 检索的验证器工厂及其创建的任何验证器都根据 XML 描述符 META-INF/validation.xml 进行配置(请参阅 Chapter 8, Configuring via XML ),如果存在。

By default, validator factories retrieved from Validation and any validators they create are configured as per the XML descriptor META-INF/validation.xml (see Chapter 8, Configuring via XML), if present.

如果你要禁用基于 XML 的配置,可以通过调用 Configuration#ignoreXmlConfiguration() 来做到。

If you want to disable the XML based configuration, you can do so by invoking Configuration#ignoreXmlConfiguration().

可以通过 Configuration#getBootstrapConfiguration() 访问 XML 配置的不同值。例如,如果你要将 Jakarta Bean Validation 集成到托管环境中,并且想要创建通过 XML 配置的对象的托管实例,那么这将很有帮助。

The different values of the XML configuration can be accessed via Configuration#getBootstrapConfiguration(). This can for instance be helpful if you want to integrate Jakarta Bean Validation into a managed environment and want to create managed instances of the objects configured via XML.

使用流配置 API 时,你可以在引导工厂时覆盖一个或多个设置。以下小节展示了如何利用不同的选项。请注意,Configuration 类公开了不同扩展点的默认实现,如果你想要将其用作自定义实现的委托,那么这将很有用。

Using the fluent configuration API, you can override one or more of the settings when bootstrapping the factory. The following sections show how to make use of the different options. Note that the Configuration class exposes the default implementations of the different extension points which can be useful if you want to use these as delegates for your custom implementations.

9.2.1. MessageInterpolator

验证引擎使用消息插值器从约束消息描述符中创建用户可读的错误消息。

Message interpolators are used by the validation engine to create user readable error messages from constraint message descriptors.

如果 Chapter 4, Interpolating constraint error messages 中描述的默认消息内插算法不满足您的需要,您可以通过 Configuration#messageInterpolator() 传入 MessageInterpolator 接口的自己的实现,如 Example 9.5, “Using a custom MessageInterpolator 中所示。

In case the default message interpolation algorithm described in Chapter 4, Interpolating constraint error messages is not sufficient for your needs, you can pass in your own implementation of the MessageInterpolator interface via Configuration#messageInterpolator() as shown in Example 9.5, “Using a custom MessageInterpolator.

示例 9.5:使用自定义 MessageInterpolator

. Example 9.5: Using a custom MessageInterpolator

package org.hibernate.validator.referenceguide.chapter09;

public class MyMessageInterpolator implements MessageInterpolator {

    @Override
    public String interpolate(String messageTemplate, Context context) {
        //...
        return null;
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        //...
        return null;
    }
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .messageInterpolator( new MyMessageInterpolator() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2.2. TraversableResolver

在某些情况下,验证引擎不应该访问 Bean 属性的状态。最明显的示例是 JPA 实体的延迟加载属性或关联。验证此延迟加载属性或关联意味着必须访问其状态,从而从数据库中触发加载。

In some cases the validation engine should not access the state of a bean property. The most obvious example for that is a lazily loaded property or association of a JPA entity. Validating this lazy property or association would mean that its state would have to be accessed, triggering a load from the database.

可以访问哪些属性以及哪些属性不可访问由查询 TraversableResolver 接口来控制。 Example 9.6, “Using a custom TraversableResolver 展示了如何使用自定义可遍历解析器实现。

Which properties can be accessed and which ones not is controlled by querying the TraversableResolver interface. Example 9.6, “Using a custom TraversableResolver shows how to use a custom traversable resolver implementation.

示例 9.6:使用自定义 TraversableResolver

. Example 9.6: Using a custom TraversableResolver

package org.hibernate.validator.referenceguide.chapter09;

public class MyTraversableResolver implements TraversableResolver {

    @Override
    public boolean isReachable(
            Object traversableObject,
            Node traversableProperty,
            Class<?> rootBeanType,
            Path pathToTraversableObject,
            ElementType elementType) {
        //...
        return false;
    }

    @Override
    public boolean isCascadable(
            Object traversableObject,
            Node traversableProperty,
            Class<?> rootBeanType,
            Path pathToTraversableObject,
            ElementType elementType) {
        //...
        return false;
    }
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .traversableResolver( new MyTraversableResolver() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

如果没有配置特定可遍历解析器,默认行为是将所有属性都视为可访问的和可级联的。当将 Hibernate Validator 与 JPA 2 提供程序(如 Hibernate ORM)结合使用时,只会将持久性提供程序已加载的那些属性视为可访问的,并且所有属性都将被视为可级联的。

If no specific traversable resolver has been configured, the default behavior is to consider all properties as reachable and cascadable. When using Hibernate Validator together with a JPA 2 provider such as Hibernate ORM, only those properties will be considered reachable which already have been loaded by the persistence provider and all properties will be considered cascadable.

默认情况下,可遍历解析器调用按每次验证调用进行缓存。这在一个 JPA 环境中尤其重要,因为在此环境中调用 isReachable() 会产生可观的成本。

By default, the traversable resolver calls are cached per validation call. This is especially important in a JPA environment where calling isReachable() has a significant cost.

这种缓存增加了一些开销。如果您的自定义遍历解析器非常快,那么最好考虑关闭缓存。

This caching adds some overhead. In the case your custom traversable resolver is very fast, it might be better to consider turning off the cache.

您可以通过 XML 配置禁用缓存:

You can disable the cache either via the XML configuration:

示例 9.7:通过 XML 配置禁用 TraversableResolver 结果缓存

. Example 9.7: Disabling the TraversableResolver result cache via the XML configuration

<?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.enable_traversable_resolver_result_cache">false</property>
</validation-config>

或通过编程 API:

or via the programmatic API:

示例 9.8:通过编程 API 禁用 TraversableResolver 结果缓存

. Example 9.8: Disabling the TraversableResolver result cache via the programmatic API

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .traversableResolver( new MyFastTraversableResolver() )
        .enableTraversableResolverResultCache( false )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2.3. ConstraintValidatorFactory

ConstraintValidatorFactory 是用于自定义如何实例化和释放约束验证器的扩展点。

ConstraintValidatorFactory is the extension point for customizing how constraint validators are instantiated and released.

Hibernate 验证器提供的默认 ConstraintValidatorFactory 需要一个公有的无参数构造方法来实例化 ConstraintValidator 实例(参见 Section 6.1.2, “The constraint validator”)。例如,使用自定义 ConstraintValidatorFactory 可以使用约束验证器实现中的依赖注入。

The default ConstraintValidatorFactory provided by Hibernate Validator requires a public no-arg constructor to instantiate ConstraintValidator instances (see Section 6.1.2, “The constraint validator”). Using a custom ConstraintValidatorFactory offers for example the possibility to use dependency injection in constraint validator implementations.

要配置自定义约束验证器工厂,请调用 Configuration#constraintValidatorFactory() (请参阅 Example 9.9, “Using a custom ConstraintValidatorFactory

To configure a custom constraint validator factory call Configuration#constraintValidatorFactory() (see Example 9.9, “Using a custom ConstraintValidatorFactory.

示例 9.9:使用自定义 ConstraintValidatorFactory

. Example 9.9: Using a custom ConstraintValidatorFactory

package org.hibernate.validator.referenceguide.chapter09;

public class MyConstraintValidatorFactory implements ConstraintValidatorFactory {

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        //...
        return null;
    }

    @Override
    public void releaseInstance(ConstraintValidator<?, ?> instance) {
        //...
    }
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .constraintValidatorFactory( new MyConstraintValidatorFactory() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

任何依赖于 ConstraintValidatorFactory 特定于实现(依赖注入、没有无参构造函数等等)的行为的约束实现都不被认为是可移植的。

Any constraint implementations relying on ConstraintValidatorFactory behaviors specific to an implementation (dependency injection, no no-arg constructor and so on) are not considered portable.

ConstraintValidatorFactory 实现不应缓存验证器实例,因为每个实例的状态可以在 initialize() 方法中进行更改。

ConstraintValidatorFactory implementations should not cache validator instances as the state of each instance can be altered in the initialize() method.

9.2.4. ParameterNameProvider

如果违反了方法或构造函数参数约束,则 ParameterNameProvider 接口用于检索参数名称并通过约束违规的属性路径向用户提供该名称。

In case a method or constructor parameter constraint is violated, the ParameterNameProvider interface is used to retrieve the parameter name and make it available to the user via the property path of the constraint violation.

默认实现通过 Java 反射 API 返回获得的参数名称。如果您使用 -parameters 编译器标志编译源,则将返回源代码中实际的参数名称。否则,将使用 arg0arg1 等形式的合成名称。

The default implementation returns parameter names as obtained through the Java reflection API. If you compile your sources using the -parameters compiler flag, the actual parameter names as in the source code will be returned. Otherwise synthetic names in the form of arg0, arg1 etc. will be used.

要使用自定义参数名称供应商,要么在自举期间传递供应商的一个实例,如 Example 9.10, “Using a custom ParameterNameProvider 中所示,要么指定 META-INF/validation.xml 文件中 <parameter-name-provider> 元素的完全限定类名称作为值(请参阅 Section 8.1, “Configuring the validator factory in validation.xml )。这在 Example 9.10, “Using a custom ParameterNameProvider 中进行了演示。

To use a custom parameter name provider either pass an instance of the provider during bootstrapping as shown in Example 9.10, “Using a custom ParameterNameProvider, or specify the fully qualified class name of the provider as value for the <parameter-name-provider> element in the META-INF/validation.xml file (see Section 8.1, “Configuring the validator factory in validation.xml). This is demonstrated in Example 9.10, “Using a custom ParameterNameProvider.

示例 9.10:使用自定义 ParameterNameProvider

. Example 9.10: Using a custom ParameterNameProvider

package org.hibernate.validator.referenceguide.chapter09;

public class MyParameterNameProvider implements ParameterNameProvider {

    @Override
    public List<String> getParameterNames(Constructor<?> constructor) {
        //...
        return null;
    }

    @Override
    public List<String> getParameterNames(Method method) {
        //...
        return null;
    }
}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .parameterNameProvider( new MyParameterNameProvider() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

Hibernate Validator 提供了一个基于 ParaNamer 库的自定义 ParameterNameProvider 实现,它提供了在运行时获取参数名称的多种方法。请参阅 Section 12.14, “Paranamer based ParameterNameProvider 以了解有关此特定实现的更多信息。

Hibernate Validator comes with a custom ParameterNameProvider implementation based on the ParaNamer library which provides several ways for obtaining parameter names at runtime. Refer to Section 12.14, “Paranamer based ParameterNameProvider to learn more about this specific implementation.

9.2.5. ClockProvider and temporal validation tolerance

对于与时间相关的验证(例如 @Past@Future 约束),定义什么被认为是 now 可能是很有用的。

For time related validation (@Past and @Future constraints for instance), it might be useful to define what is considered now.

当您要可靠地测试约束时,这一点尤其重要。

This is especially important when you want to test your constraints in a reliable manner.

参考时间由 ClockProvider 合约定义。ClockProvider 的职责是提供一个 java.time.Clock,为时间相关验证器定义 now

The reference time is defined by the ClockProvider contract. The responsibility of the ClockProvider is to provide a java.time.Clock defining now for time related validators.

示例 9.11:使用自定义 ClockProvider

. Example 9.11: Using a custom ClockProvider

package org.hibernate.validator.referenceguide.chapter09;

public class FixedClockProvider implements ClockProvider {

    private Clock clock;

    public FixedClockProvider(ZonedDateTime dateTime) {
        clock = Clock.fixed( dateTime.toInstant(), dateTime.getZone() );
    }

    @Override
    public Clock getClock() {
        return clock;
    }

}
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .clockProvider( new FixedClockProvider( ZonedDateTime.of( 2016, 6, 15, 0, 0, 0, 0, ZoneId.of( "Europe/Paris" ) ) ) )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

或者,你可以在通过 META-INF/validation.xml 配置默认校验器工厂时使用 <clock-provider> 元素指定 ClockProvider 实现的全限定类名(见 Chapter 8, Configuring via XML )。

Alternatively, you can specify the fully-qualified classname of a ClockProvider implementation using the <clock-provider> element when configuring the default validator factory via META-INF/validation.xml (see Chapter 8, Configuring via XML).

在校验 @Future@Past 约束时,你可能得到当前时间。

When validating @Future and @Past constraints, you might want to obtain the current time.

你可以通过调用 ConstraintValidatorContext#getClockProvider() 方法在校验器中获取 ClockProvider

You can obtain the ClockProvider in your validators by calling the ConstraintValidatorContext#getClockProvider() method.

例如,如果你想使用更明确的信息替换 @Future 约束的默认信息,这可能很有用。

For instance, this might be useful if you want to replace the default message of the @Future constraint with a more explicit one.

在处理分布式架构时,在应用时间约束(例如 @Past@Future)时,你可能需要一些容忍度。

When dealing with distributed architectures, you might need some tolerance when applying temporal constraints such as @Past or @Future.

可以通过如下方式引导您的 ValidatorFactory 设置一个时间验证公差:

You can set a temporal validation tolerance by bootstrapping your ValidatorFactory as below:

示例 9.12:使用时间校验公差

. Example 9.12: Using temporal validation tolerance

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .temporalValidationTolerance( Duration.ofMillis( 10 ) )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

或者,可以通过在 META-INF/validation.xml 中设置 hibernate.validator.temporal_validation_tolerance 属性,在 XML 配置中定义它。

Alternatively, you can define it in the XML configuration by setting the hibernate.validator.temporal_validation_tolerance property in your META-INF/validation.xml.

此属性的值必须是一个 long,定义以毫秒为单位的公差。

The value of this property must be a long defining the tolerance in milliseconds.

在实现自己的时间约束时,你可能需要使用时间校验公差。

When implementing your own temporal constraints, you might need to have access to the temporal validation tolerance.

可以通过调用 HibernateConstraintValidatorInitializationContext#getTemporalValidationTolerance() 方法获取它。

It can be obtained by calling the HibernateConstraintValidatorInitializationContext#getTemporalValidationTolerance() method.

请注意,为了在你初始化时能够访问该信息,你的约束校验器必须实现 HibernateConstraintValidator 合约(见 Section 6.1.2.2, “The HibernateConstraintValidator extension” )。此合约当前标记为潜伏的:将来可能会更改。

Note that to get access to this context at initialization, your constraint validator has to implement the HibernateConstraintValidator contract (see Section 6.1.2.2, “The HibernateConstraintValidator extension”). This contract is currently marked as incubating: it might be subject to change in the future.

9.2.6. Registering _ValueExtractor_s

Chapter 7, Value extraction 中所述,在启动期间可以注册附加的值提取器(参见 Section 7.5, “Registering a ValueExtractor 以了解注册值提取器的其他方法)。

As mentioned in Chapter 7, Value extraction, additional value extractors can be registered during bootstrapping (see Section 7.5, “Registering a ValueExtractor for the other ways to register a value extractor).

Example 9.13, “Registering additional value extractors” 展示了我们如何注册先前创建的用于提取 Guava Multimap 的键和值的值提取器。

Example 9.13, “Registering additional value extractors” shows how we would register the value extractors we previously created to extract the keys and the values of Guava’s Multimap.

示例 9.13:注册附加的值提取器

. Example 9.13: Registering additional value extractors

ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .addValueExtractor( new MultimapKeyValueExtractor() )
        .addValueExtractor( new MultimapValueValueExtractor() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2.7. Adding mapping streams

如前所述,您可以使用基于 XML 的约束映射配置应用于 Java bean 的约束。

As discussed earlier, you can configure the constraints applied to your Java beans using XML based constraint mappings.

除了在 META-INF/validation.xml 中指定映射文件之外,你可以通过 Configuration#addMapping() 进一步映射(参见 Example 9.14, “Adding constraint mapping streams” )。请注意,传入的输入流必须遵守 Section 8.2, “Mapping constraints via constraint-mappings 中所示的约束映射的 XML 架构。

Besides the mapping files specified in META-INF/validation.xml, you can add further mappings via Configuration#addMapping() (see Example 9.14, “Adding constraint mapping streams”). Note that the passed input stream(s) must adhere to the XML schema for constraint mappings presented in Section 8.2, “Mapping constraints via constraint-mappings.

示例 9.14:添加约束映射流

. Example 9.14: Adding constraint mapping streams

InputStream constraintMapping1 = null;
InputStream constraintMapping2 = null;
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .addMapping( constraintMapping1 )
        .addMapping( constraintMapping2 )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

在创建验证器工厂后,您应该关闭任何传递的输入流。

You should close any passed input stream after the validator factory has been created.

9.2.8. Provider-specific settings

通过 Validation#byProvider() 返回的配置对象,可以配置特定于提供方的选项。

Via the configuration object returned by Validation#byProvider(), provider specific options can be configured.

在 Hibernate 验证器中,这例如允许你启用快速失败模式,并传递一个或多个编程约束映射,如 Example 9.15, “Setting Hibernate Validator specific options” 中所演示。

In the case of Hibernate Validator, this e.g. allows you to enable the fail fast mode and pass one or more programmatic constraint mappings as demonstrated in Example 9.15, “Setting Hibernate Validator specific options”.

示例 9.15:设定 Hibernate Validator 特定的选项

. Example 9.15: Setting Hibernate Validator specific options

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .failFast( true )
        .addMapping( (ConstraintMapping) null )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

或者,也可以通过 Configuration#addProperty() 传递特定于提供方的选项。Hibernate Validator 也支持以这种方式启用快速失败模式:

Alternatively, provider-specific options can be passed via Configuration#addProperty(). Hibernate Validator supports enabling the fail fast mode that way, too:

示例 9.16:通过 addProperty() 启用 Hibernate Validator 特定的选项

. Example 9.16: Enabling a Hibernate Validator specific option via addProperty()

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .addProperty( "hibernate.validator.fail_fast", "true" )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

请参阅 Section 12.2, “Fail fast mode”Section 12.4, “Programmatic constraint definition and declaration” 以了解有关快速失败模式和约束声明 API 的更多信息。

Refer to Section 12.2, “Fail fast mode” and Section 12.4, “Programmatic constraint definition and declaration” to learn more about the fail fast mode and the constraint declaration API.

9.2.9. Configuring the ScriptEvaluatorFactory

对于 @ScriptAssert@ParameterScriptAssert 等约束,配置脚本引擎的初始化方式以及脚本评估器的构建方式可能很有用。这可以通过设置 ScriptEvaluatorFactory 的自定义实现来实现。

For constraints like @ScriptAssert and @ParameterScriptAssert, it might be useful to configure how the script engines are initialized and how the script evaluators are built. This can be done by setting a custom implementation of ScriptEvaluatorFactory.

特别是,这对模块化环境(例如 OSGi)很重要,在该环境中,用户可能面临模块化类加载和 JSR 223方面的问题。它还允许使用任何自定义脚本引擎,而不必基于 JSR 223(例如 Spring 表达式语言)。

In particular, this is important for modular environments (e.g. OSGi), where user might face issues with modular class loading and JSR 223. It also allows to use any custom script engine, not necessarily based on the JSR 223 (e.g. Spring Expression Language).

9.2.9.1. XML configuration

若要通过 XML 指定 ScriptEvaluatorFactory,您需要定义 hibernate.validator.script_evaluator_factory 属性。

To specify the ScriptEvaluatorFactory via XML, you need to define the hibernate.validator.script_evaluator_factory property.

示例 9.17:通过 XML 定义 ScriptEvaluatorFactory

. Example 9.17: Defining the ScriptEvaluatorFactory via XML

<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.script_evaluator_factory">
        org.hibernate.validator.referenceguide.chapter09.CustomScriptEvaluatorFactory
    </property>

</validation-config>

在这种情况下,指定的 ScriptEvaluatorFactory 必须具有无参数构造函数。

In this case, the specified ScriptEvaluatorFactory must have a no-arg constructor.

9.2.9.2. Programmatic configuration

要对其进行编程配置,你需要将 ScriptEvaluatorFactory 的一个实例传至 ValidatorFactory 。这在 ScriptEvaluatorFactory 的配置中提供了更大的灵活性。 Example 9.18, “Defining the ScriptEvaluatorFactory programmatically” 显示如何执行此操作。

To configure it programmatically, you need to pass an instance of ScriptEvaluatorFactory to the ValidatorFactory. This gives more flexibility in the configuration of the ScriptEvaluatorFactory. Example 9.18, “Defining the ScriptEvaluatorFactory programmatically” shows how this can be done.

示例 9.18:以编程方式定义 ScriptEvaluatorFactory

. Example 9.18: Defining the ScriptEvaluatorFactory programmatically

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .scriptEvaluatorFactory( new CustomScriptEvaluatorFactory() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2.9.3. Custom ScriptEvaluatorFactory implementation examples

本节展示了一些自定义 _ScriptEvaluatorFactory_实现,这些实现可以在模块化环境中使用,还展示了一个使用 Spring Expression Language编写约束脚本的示例。

This section shows a couple of custom ScriptEvaluatorFactory implementations that can be used in modular environments as well as one using the Spring Expression Language for writing constraint scripts.

模块化环境与此处的 JSR 223 问题源于类加载。提供脚本引擎的类加载器可能与 Hibernate Validator 的加载器不同。因此,使用默认策略将无法找到脚本引擎。

Problems with modular environments and JSR 223 come from the class loading. The class loader where the script engine is available might be different from the one of Hibernate Validator. Thus the script engine wouldn’t be found using the default strategy.

要解决这个问题,可以引入以下 MultiClassLoaderScriptEvaluatorFactory 类:

To solve this issue, the MultiClassLoaderScriptEvaluatorFactory class below can be introduced:

/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
 */
package org.hibernate.validator.osgi.scripting;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluationException;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;

/**
 * {@link ScriptEvaluatorFactory} that allows you to pass multiple {@link ClassLoader}s that will be used
 * to search for {@link ScriptEngine}s. Useful in environments similar to OSGi, where script engines can be
 * found only in {@link ClassLoader}s different from default one.
 *
 * @author Marko Bekhta
 */
public class MultiClassLoaderScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {

    private final ClassLoader[] classLoaders;

    public MultiClassLoaderScriptEvaluatorFactory(ClassLoader... classLoaders) {
        if ( classLoaders.length == 0 ) {
            throw new IllegalArgumentException( "No class loaders were passed" );
        }
        this.classLoaders = classLoaders;
    }

    @Override
    protected ScriptEvaluator createNewScriptEvaluator(String languageName) {
        for ( ClassLoader classLoader : classLoaders ) {
            ScriptEngine engine = new ScriptEngineManager( classLoader ).getEngineByName( languageName );
            if ( engine != null ) {
                return new ScriptEngineScriptEvaluator( engine );
            }
        }
        throw new ScriptEvaluationException( "No JSR 223 script engine found for language " + languageName );
    }
}

然后通过以下方式声明:

and then declared with:

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .scriptEvaluatorFactory(
                new MultiClassLoaderScriptEvaluatorFactory( GroovyScriptEngineFactory.class.getClassLoader() )
        )
        .buildValidatorFactory()
        .getValidator();

这样,就可以传递多个 ClassLoader 实例:通常是所需的 ScriptEngine 的类加载器。

This way, it is possible to pass multiple ClassLoader instances: typically the class loaders of the wanted _ScriptEngine_s.

适用于 OSGi 环境的另一种方法是使用下面定义的 OsgiScriptEvaluatorFactory

An alternative approach for OSGi environments can be to use the OsgiScriptEvaluatorFactory defined below:

/*
 * Hibernate Validator, declare and validate application constraints
 *
 * License: Apache License, Version 2.0
 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
 */
package org.hibernate.validator.osgi.scripting;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import jakarta.validation.ValidationException;

import org.hibernate.validator.spi.scripting.AbstractCachingScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEngineScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorNotFoundException;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

/**
 * {@link ScriptEvaluatorFactory} suitable for OSGi environments. It is created
 * based on the {@code BundleContext} which is used to iterate through {@code Bundle}s and find all {@link ScriptEngineFactory}
 * candidates.
 *
 * @author Marko Bekhta
 */
public class OsgiScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {

    private final List<ScriptEngineManager> scriptEngineManagers;

    public OsgiScriptEvaluatorFactory(BundleContext context) {
        this.scriptEngineManagers = Collections.unmodifiableList( findManagers( context ) );
    }

    @Override
    protected ScriptEvaluator createNewScriptEvaluator(String languageName) throws ScriptEvaluatorNotFoundException {
        return scriptEngineManagers.stream()
                .map( manager -> manager.getEngineByName( languageName ) )
                .filter( Objects::nonNull )
                .map( engine -> new ScriptEngineScriptEvaluator( engine ) )
                .findFirst()
                .orElseThrow( () -> new ValidationException( String.format( "Unable to find script evaluator for '%s'.", languageName ) ) );
    }

    private List<ScriptEngineManager> findManagers(BundleContext context) {
        return findFactoryCandidates( context ).stream()
                .map( className -> {
                    try {
                        return new ScriptEngineManager( Class.forName( className ).getClassLoader() );
                    }
                    catch (ClassNotFoundException e) {
                        throw new ValidationException( "Unable to instantiate '" + className + "' based engine factory manager.", e );
                    }
                } ).collect( Collectors.toList() );
    }

    /**
     * Iterates through all bundles to get the available {@link ScriptEngineFactory} classes
     *
     * @return the names of the available ScriptEngineFactory classes
     *
     * @throws IOException
     */
    private List<String> findFactoryCandidates(BundleContext context) {
        return Arrays.stream( context.getBundles() )
                .filter( Objects::nonNull )
                .filter( bundle -> !"system.bundle".equals( bundle.getSymbolicName() ) )
                .flatMap( this::toStreamOfResourcesURL )
                .filter( Objects::nonNull )
                .flatMap( url -> toListOfFactoryCandidates( url ).stream() )
                .collect( Collectors.toList() );
    }

    private Stream<URL> toStreamOfResourcesURL(Bundle bundle) {
        Enumeration<URL> entries = bundle.findEntries(
                "META-INF/services",
                "javax.script.ScriptEngineFactory",
                false
        );
        return entries != null ? Collections.list( entries ).stream() : Stream.empty();
    }

    private List<String> toListOfFactoryCandidates(URL url) {
        try ( BufferedReader reader = new BufferedReader( new InputStreamReader( url.openStream(), "UTF-8" ) ) ) {
            return reader.lines()
                    .map( String::trim )
                    .filter( line -> !line.isEmpty() )
                    .filter( line -> !line.startsWith( "#" ) )
                    .collect( Collectors.toList() );
        }
        catch (IOException e) {
            throw new ValidationException( "Unable to read the ScriptEngineFactory resource file", e );
        }
    }
}

然后通过以下方式声明:

and then declared with:

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .scriptEvaluatorFactory(
                new OsgiScriptEvaluatorFactory( FrameworkUtil.getBundle( this.getClass() ).getBundleContext() )
        )
        .buildValidatorFactory()
        .getValidator();

它专为 OSGi 环境而设计,允许您传递作为参数将用于搜索 ScriptEngineFactoryBundleContext

It is designed specifically for OSGi environments and allows you to pass the BundleContext which will be used to search for ScriptEngineFactory as a parameter.

正如已提到的,您还可以使用不基于 JSR 223 的脚本引擎。

As already mentioned, you can also use script engines that are not based on JSR 223.

例如,若要使用 Spring Expression Language,可以将 SpringELScriptEvaluatorFactory 定义如下:

For instance, to use the Spring Expression Language, you can define a SpringELScriptEvaluatorFactory as:

package org.hibernate.validator.referenceguide.chapter09;

public class SpringELScriptEvaluatorFactory extends AbstractCachingScriptEvaluatorFactory {

    @Override
    public ScriptEvaluator createNewScriptEvaluator(String languageName) {
        if ( !"spring".equalsIgnoreCase( languageName ) ) {
            throw new IllegalStateException( "Only Spring EL is supported" );
        }

        return new SpringELScriptEvaluator();
    }

    private static class SpringELScriptEvaluator implements ScriptEvaluator {

        private final ExpressionParser expressionParser = new SpelExpressionParser();

        @Override
        public Object evaluate(String script, Map<String, Object> bindings) throws ScriptEvaluationException {
            try {
                Expression expression = expressionParser.parseExpression( script );
                EvaluationContext context = new StandardEvaluationContext( bindings.values().iterator().next() );
                for ( Entry<String, Object> binding : bindings.entrySet() ) {
                    context.setVariable( binding.getKey(), binding.getValue() );
                }
                return expression.getValue( context );
            }
            catch (ParseException | EvaluationException e) {
                throw new ScriptEvaluationException( "Unable to evaluate SpEL script", e );
            }
        }
    }
}

此工厂允许在 ScriptAssertParameterScriptAssert 约束中使用 Spring 表达式语言:

This factory allows to use Spring Expression Language in ScriptAssert and ParameterScriptAssert constraints:

@ScriptAssert(script = "value > 0", lang = "spring")
public class Foo {

    private final int value;

    private Foo(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

9.2.10. Logging of values under validation

在某些情况下,也许需要检查由 Hibernate Validator 生成的日志。当日志级别设置为 TRACE 时,验证器将生成日志条目(包括其他条目),其中包含对所评估约束的描述符。默认情况下,正在验证的值将不会显示在这些消息中,以防止敏感数据被泄露。如果需要,可以将 Hibernate Validator 配置为也打印这些值。通常,有以下几种方法可以做到这一点:

In some cases it might be useful to inspect logs produced by Hibernate Validator. When the log level is set to TRACE validator will produce, among others, log entries containing the descriptor of constraints that are evaluated. By default, values under validation are not going to be visible in these messages to prevent sensitive data from getting exposed. If required, Hibernate Validator can be configured to print these values as well. As usual, there are a few ways to do it:

示例 9.25:编程配置

. Example 9.25: Programmatic configuration

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .showValidatedValuesInTraceLogs( true )
        .buildValidatorFactory()
        .getValidator();
示例 9.26:通过属性进行编程配置

. Example 9.26: Programmatic configuration via property

Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .addProperty( "hibernate.validator.show_validated_value_in_trace_logs", "true" )
        .buildValidatorFactory()
        .getValidator();
示例 9.27:通过属性进行 XML 配置

. Example 9.27: XML configuration via property

<?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.show_validated_value_in_trace_logs">true</property>
</validation-config>

8.0 之前的 Hibernate Validator 版本会在将日志级别设置为 TRACE 的情况下记录值和约束描述符。

Hibernate Validator versions, prior to 8.0, logged both value and constraint descriptor if logging level was set to TRACE.

9.3. Configuring a Validator

使用已配置验证器工厂时,偶尔需要对单个 Validator 实例应用不同的配置。 Example 9.28, “Configuring a Validator instance via usingContext() 展示了如何通过调用 ValidatorFactory#usingContext() 来实现此目的。

When working with a configured validator factory it can occasionally be required to apply a different configuration to a single Validator instance. Example 9.28, “Configuring a Validator instance via usingContext() shows how this can be achieved by calling ValidatorFactory#usingContext().

示例 9.28:通过 usingContext() 配置 Validator 实例

. Example 9.28: Configuring a Validator instance via usingContext()

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

Validator validator = validatorFactory.usingContext()
        .messageInterpolator( new MyMessageInterpolator() )
        .traversableResolver( new MyTraversableResolver() )
        .getValidator();