Hibernate Validator 中文操作指南

9. Bootstrapping

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

9.1. Retrieving ValidatorFactory and Validator

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

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

示例 9.1: 自举默认的 ValidatorFactoryValidator
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

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

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

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

示例 9.2:使用特定供应商自举 ValidatorFactoryValidator
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

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

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

示例 9.3:检索用于配置的默认 ValidatorFactory
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

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

9.1.1. ValidationProviderResolver

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

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

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

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

示例 9.4:使用自定义 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 ),如果存在。

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

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

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

9.2.1. MessageInterpolator

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

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

示例 9.5:使用自定义 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 实体的延迟加载属性或关联。验证此延迟加载属性或关联意味着必须访问其状态,从而从数据库中触发加载。

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

示例 9.6:使用自定义 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)结合使用时,只会将持久性提供程序已加载的那些属性视为可访问的,并且所有属性都将被视为可级联的。

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

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

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

示例 9.7:通过 XML 配置禁用 TraversableResolver 结果缓存
<?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:

示例 9.8:通过编程 API 禁用 TraversableResolver 结果缓存
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .traversableResolver( new MyFastTraversableResolver() )
        .enableTraversableResolverResultCache( false )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2.3. ConstraintValidatorFactory

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

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

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

示例 9.9:使用自定义 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 特定于实现(依赖注入、没有无参构造函数等等)的行为的约束实现都不被认为是可移植的。

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

9.2.4. ParameterNameProvider

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

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

要使用自定义参数名称供应商,要么在自举期间传递供应商的一个实例,如 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 中进行了演示。

示例 9.10:使用自定义 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 以了解有关此特定实现的更多信息。

9.2.5. ClockProvider and temporal validation tolerance

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

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

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

示例 9.11:使用自定义 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 )。

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

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

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

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

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

示例 9.12:使用时间校验公差
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 配置中定义它。

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

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

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

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

9.2.6. Registering _ValueExtractor_s

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

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

示例 9.13:注册附加的值提取器
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 的约束。

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

示例 9.14:添加约束映射流
InputStream constraintMapping1 = null;
InputStream constraintMapping2 = null;
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .addMapping( constraintMapping1 )
        .addMapping( constraintMapping2 )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

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

9.2.8. Provider-specific settings

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

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

示例 9.15:设定 Hibernate Validator 特定的选项
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .failFast( true )
        .addMapping( (ConstraintMapping) null )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

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

示例 9.16:通过 addProperty() 启用 Hibernate Validator 特定的选项
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 的更多信息。

9.2.9. Configuring the ScriptEvaluatorFactory

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

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

9.2.9.1. XML configuration

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

示例 9.17:通过 XML 定义 ScriptEvaluatorFactory
<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 必须具有无参数构造函数。

9.2.9.2. Programmatic configuration

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

示例 9.18:以编程方式定义 ScriptEvaluatorFactory
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编写约束脚本的示例。

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

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

/*
 * 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 );
    }
}

然后通过以下方式声明:

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

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

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

/*
 * 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 );
        }
    }
}

然后通过以下方式声明:

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

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

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

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

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 表达式语言:

@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 配置为也打印这些值。通常,有以下几种方法可以做到这一点:

示例 9.25:编程配置
Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .showValidatedValuesInTraceLogs( true )
        .buildValidatorFactory()
        .getValidator();
示例 9.26:通过属性进行编程配置
Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .addProperty( "hibernate.validator.show_validated_value_in_trace_logs", "true" )
        .buildValidatorFactory()
        .getValidator();
示例 9.27:通过属性进行 XML 配置
<?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 的情况下记录值和约束描述符。

9.3. Configuring a Validator

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

示例 9.28:通过 usingContext() 配置 Validator 实例
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();

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