Hibernate Validator 中文操作指南

11. Integrating with other frameworks

Hibernate Validator 旨在用于实现多层数据验证,其中约束在单个位置(带注解的域模型)中表示,并在应用程序的不同层中进行检查。为此,它具有与其他技术的多重集成点。

11.1. ORM integration

Hibernate Validator 与 Hibernate ORM 和所有纯粹的 Java 持久化提供程序集成。

当必须验证延迟加载的联想时,建议在联想的 getter 上放置约束。Hibernate ORM 将延迟加载的联想替换为通过 getter 请求时会初始化/加载的代理实例。如果在这种情况下,约束置于字段级别,则将使用实际的代理实例,这会导致验证错误。

11.1.1. Database schema-level validation

开箱即用,Hibernate ORM 将你为实体定义的约束转换为映射元数据。例如,如果实体的属性带注解 @NotNull,则其列将在 Hibernate ORM 生成的 DDL 模式中声明为 not null

如果出于某种原因需要禁用此功能,请将 hibernate.validator.apply_to_ddl 设置为 false。另请参见 Section 2.3.1, “Jakarta Bean Validation constraints”Section 2.3.2, “Additional constraints”

你还可以通过设置属性 org.hibernate.validator.group.ddl 将 DDL 约束生成限制为定义约束的一个子集。该属性指定了约束必须是其中的成员才能被考虑用于 DDL 模式生成的组的完全限定类名(以逗号分隔)。

11.1.2. Hibernate ORM event-based validation

Hibernate Validator 具有内置的 Hibernate 事件侦听器 - org.hibernate.cfg.beanvalidation.BeanValidationEventListener - 它属于 Hibernate ORM 的一部分。每当出现 PreInsertEventPreUpdateEventPreDeleteEvent 时,该侦听器将验证实体实例的所有约束,并在违反任何约束时抛出异常。默认情况下,将在 Hibernate ORM 进行任何插入或更新之前检查对象。默认情况下,删除前事件不会触发验证。您可以使用属性 jakarta.persistence.validation.group.pre-persistjakarta.persistence.validation.group.pre-updatejakarta.persistence.validation.group.pre-remove 为每种事件类型配置要验证的组。这些属性的值是组的逗号分隔的全限定类名,要验证。 Example 11.1, “Manual configuration of BeanValidationEvenListener 显示了这些属性的默认值。在这种情况下,也可以省略它们。

对于约束违规,事件会触发一个运行时 ConstraintViolationException,其中包含一组 ConstraintViolation 实例,描述了每个故障。

如果 Hibernate Validator 存在于类路径中,Hibernate ORM 将透明地使用它。为了避免验证即使 Hibernate Validator 存在于类路径中,将 jakarta.persistence.validation.mode 设置为无。

如果 Bean 未使用验证注释进行注释,则没有运行时性能成本。

如果你需要为 Hibernate ORM 手动设置事件侦听器,在 hibernate.cfg.xml 中使用以下配置:

示例 11.1:手动配置 BeanValidationEvenListener
<hibernate-configuration>
    <session-factory>
        ...
        <property name="jakarta.persistence.validation.group.pre-persist">
            jakarta.validation.groups.Default
        </property>
        <property name="jakarta.persistence.validation.group.pre-update">
            jakarta.validation.groups.Default
        </property>
        <property name="jakarta.persistence.validation.group.pre-remove"></property>
        ...
        <event type="pre-update">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>
        <event type="pre-insert">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>
        <event type="pre-delete">
            <listener class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener"/>
        </event>
    </session-factory>
</hibernate-configuration>

11.1.3. JPA

如果您正在使用 JPA 2,并且 Hibernate Validator 位于类路径中,JPA2 规范要求启用 Jakarta Bean Validation。在这种情况下,可以按 Section 11.1.2, “Hibernate ORM event-based validation” 中所述在 persistence.xml 中配置属性 jakarta.persistence.validation.group.pre-persistjakarta.persistence.validation.group.pre-updatejakarta.persistence.validation.group.pre-removepersistence.xml 还定义了一个 node validation-mode,可以将其设置为 AUTOCALLBACKNONE。默认值为 AUTO

11.2. JSF & Seam

使用 JSF2 或 JBoss Seam 和 Hibernate Validator(Jakarta Bean Validation)时,验证将针对应用程序中的每个字段触发。 Example 11.2, “Usage of Jakarta Bean Validation within JSF2” 显示了 JSF 页面中 f:validateBean 标记的示例。validationGroups 属性是可选的,可用于指定验证组的逗号分隔列表。默认值为 jakarta.validation.groups.Default。有关更多信息,请参阅 Seam 文档或 JSF 2 规范。

示例 11.2:在 JSF2 中使用 Jakarta Bean Validation
<h:form>

  <f:validateBean validationGroups="jakarta.validation.groups.Default">

    <h:inputText value=#{model.property}/>
    <h:selectOneRadio value=#{model.radioProperty}> ... </h:selectOneRadio>
    <!-- other input components here -->

  </f:validateBean>

</h:form>

JSF 2 和 Jakarta Bean Validation 之间的集成在 JSR-314 的“Jakarta Bean Validation Integration”章节中进行了描述。值得注意的是,JSF 2 实现了一个自定义 MessageInterpolator 以确保正确的本地化。为了鼓励使用 Jakarta Bean Validation 消息工具,JSF 2 默认只显示生成的 Bean Validation 消息。然而,这可以通过应用程序资源包进行配置,方法是提供以下配置( {0} 替换为 Jakarta Bean Validation 消息, {1} 替换为 JSF 组件标签):

jakarta.faces.validator.BeanValidator.MESSAGE={1}: {0}jakarta.faces.validator.BeanValidator.MESSAGE={1:{0},默认是:

jakarta.faces.validator.BeanValidator.MESSAGE={0}jakarta.faces.validator.BeanValidator.MESSAGE={0}

11.3. CDI

从 1.1 版本开始,Bean Validation(因此 Jakarta Bean Validation)与 CDI(Contexts and Dependency Injection for Jakarta EE)集成。

这种集成提供由 ValidatorValidatorFactory 管理的 CDI 管理 Bean,并在约束验证器以及自定义消息插补器、可遍历解析器、约束验证器工厂、参数名称提供程序、时钟提供程序和值提取器中启用依赖项注入。

此外,对于 CDI 管理的 Bean 的方法和构造函数中的参数和返回值约束,在调用后将自动验证。

当您的应用程序在 Jakarta EE 容器上运行时,此集成默认启用。当在 Servlet 容器或纯 Java SE 环境中使用 CDI 时,您可以使用 Hibernate Validator 提供的 CDI 便携式扩展。为此,请将便携式扩展添加到您的类路径中,如 Section 1.1.2, “CDI” 中所述。

11.3.1. Dependency injection

CDI 的依赖注入机制使检索 ValidatorFactoryValidator 实例并在受管 bean 中使用它们变得非常容易。 只要使用 @jakarta.inject.Inject 为 Bean 的实例字段添加注释,如 Example 11.3, “Retrieving validator factory and validator via @Inject 中所示。

示例 11.3:通过 @Inject 检索校验程序工厂和校验程序

package org.hibernate.validator.referenceguide.chapter11.cdi.validator;

@ApplicationScoped
public class RentalStation {

    @Inject
    private ValidatorFactory validatorFactory;

    @Inject
    private Validator validator;

    //...
}

注入的 Bean 是默认校验程序工厂和校验程序实例。 要对其进行配置(例如,使用自定义消息插值器),可以使用 Chapter 8, Configuring via XML 中讨论的 Jakarta Bean 验证 XML 描述符。

如果你正在使用多个 Jakarta Bean 验证程序,你可以通过使用 @HibernateValidator 限定符为注入点添加注释来确保从 Hibernate Validator 注入工厂和校验程序,如 Example 11.4, “Using the @HibernateValidator qualifier annotation” 中演示的那样。

示例 11.4:使用 @HibernateValidator 限定符注释

package org.hibernate.validator.referenceguide.chapter11.cdi.validator.qualifier;

@ApplicationScoped
public class RentalStation {

    @Inject
    @HibernateValidator
    private ValidatorFactory validatorFactory;

    @Inject
    @HibernateValidator
    private Validator validator;

    //...
}

限定符注释的全限定名称是 org.hibernate.validator.cdi.HibernateValidator 。 务必不要导入 org.hibernate.validator.HibernateValidator ,而是导入与引导 API 一起使用时用于选择 Hibernate Validator 的 ValidationProvider 实现(请参阅 Section 9.1, “Retrieving ValidatorFactory and Validator )。

通过 @Inject,你还可以向约束验证器和其他 Jakarta Bean 验证对象(例如 MessageInterpolator 实现等)注入依赖项。

Example 11.5, “Constraint validator with injected bean” 演示了如何在 ConstraintValidator 实现中使用注入的 CDI Bean 来确定给定约束是否有效。如示例所示,您还可以使用 @PostConstruct@PreDestroy 回调来实现任何所需的构造和销毁逻辑。

示例 11.5:具有注入 Bean 的约束校验程序

package org.hibernate.validator.referenceguide.chapter11.cdi.injection;

public class ValidLicensePlateValidator
        implements ConstraintValidator<ValidLicensePlate, String> {

    @Inject
    private VehicleRegistry vehicleRegistry;

    @PostConstruct
    public void postConstruct() {
        //do initialization logic...
    }

    @PreDestroy
    public void preDestroy() {
        //do destruction logic...
    }

    @Override
    public void initialize(ValidLicensePlate constraintAnnotation) {
    }

    @Override
    public boolean isValid(String licensePlate, ConstraintValidatorContext constraintContext) {
        return vehicleRegistry.isValidLicensePlate( licensePlate );
    }
}

11.3.2. Method validation

CDI 的方法拦截机制允许与 Jakarta Bean Validation 的方法验证功能非常紧密地集成。只需将约束注释添加到 CDI Bean 的可执行文件的参数和返回值上,它们将在调用方法或构造函数之前(参数约束)和之后(返回值约束)自动验证。

请注意,不需要显式拦截器绑定,而是所需的验证函数拦截器将自动为所有具有约束方法和构造函数的托管 Bean 注册。

拦截器 org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptororg.hibernate.validator.cdi.internal.ValidationExtension 注册。这在 Jakarta EE 运行时环境中会隐式发生,或通过显式添加 hibernate-validator-cdi 工件发生 - 请参阅 Section 1.1.2, “CDI”

示例 11.6:具有方法级约束的 CDI 受管 Bean

package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;

@ApplicationScoped
public class RentalStation {

    @Valid
    public RentalStation() {
        //...
    }

    @NotNull
    @Valid
    public Car rentCar(
            @NotNull Customer customer,
            @NotNull @Future Date startDate,
            @Min(1) int durationInDays) {
        //...
        return null;
    }

    @NotNull
    List<Car> getAvailableCars() {
        //...
        return null;
    }
}
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation;

@RequestScoped
public class RentCarRequest {

    @Inject
    private RentalStation rentalStation;

    public void rentCar(String customerId, Date startDate, int duration) {
        //causes ConstraintViolationException
        rentalStation.rentCar( null, null, -1 );
    }
}

此处 RentalStation Bean 充当多个方法约束。当从另一个 Bean 如 RentCarRequest 调用 RentalStation 方法时,将自动验证所调用方法的约束。如果如示例中所示那样传递了任何非法的参数值,方法拦截器将抛出 ConstraintViolationException 异常,提供有关违规约束的详细信息。如果方法的返回值违反任何返回值约束,则也是如此。

同样,在调用时将自动验证构造函数约束。在示例中,将验证构造函数返回的 RentalStation 对象,因为构造函数返回值标记为 @Valid

11.3.2.1. Validated executable types

Jakarta Bean Validation 允许对自动验证的可执行类型进行细粒度的控制。默认情况下,验证构造函数和非 getter 方法上的约束。因此,当调用 Example 11.6, “CDI managed beans with method-level constraints” 中的 RentalStation#getAvailableCars() 方法时,不会验证该方法上的 @NotNull 约束。

您可以使用以下选项来配置在调用时验证哪些类型的可执行文件:

  1. 通过 XML 描述符 META-INF/validation.xml 在全局范围内配置可执行类型; 请参阅 Section 8.1, “Configuring the validator factory in validation.xml 中的示例

  2. 在可执行或类型级别上使用 @ValidateOnExecution 注释

如果为给定可执行文件指定了多个配置源,则可执行级别上的 @ValidateOnExecution 优先于类型级别上的 @ValidateOnExecution@ValidateOnExecution 通常优先于 META- INF/validation.xml 中全局配置的类型。

Example 11.7, “Using @ValidateOnExecution 展示了如何使用 @ValidateOnExecution 注释:

示例 11.7:使用 @ValidateOnExecution

package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.configuration;

@ApplicationScoped
@ValidateOnExecution(type = ExecutableType.ALL)
public class RentalStation {

    @Valid
    public RentalStation() {
        //...
    }

    @NotNull
    @Valid
    @ValidateOnExecution(type = ExecutableType.NONE)
    public Car rentCar(
            @NotNull Customer customer,
            @NotNull @Future Date startDate,
            @Min(1) int durationInDays) {
        //...
        return null;
    }

    @NotNull
    public List<Car> getAvailableCars() {
        //...
        return null;
    }
}

这里,方法 rentCar()_不会在调用时被验证,因为它使用@ValidateOnExecution(type = ExecutableType.NONE)进行了注释。相比之下,构造函数和方法 _getAvailableCars()_将被验证,因为在类型级别中给出了@ValidateOnExecution(type = ExecutableType.ALL)。_ExecutableType.ALL_是显式指定所有类型_CONSTRUCTORS、_GETTER_METHODS_和_NON_GETTER_METHODS_的更简洁的形式。

可以通过在 META-INF/validation.xml 中指定 <executable-validation enabled="false"/> 全局关闭可执行验证。 在这种情况下,会忽略所有 @ValidateOnExecution 注释。

请注意,当一个方法重写或实现一个超类型方法时,配置将从该被重写或实现的方法中获取(如通过 @ValidateOnExecution 在方法本身或超类型)。这会保护超类型方法的客户端免受配置的意外更改,例如在子类型中禁用对被重写可执行项的验证。

如果 CDI 受管 Bean 覆盖或实现了一个超类型方法,并且此超类型方法承载任何约束,那么可能会发生验证拦截器未正确注册到 Bean 的情况,导致 Bean 的方法在调用时未得到验证。 在这种情况下,你可以如 Example 11.8, “Using ExecutableType.IMPLICIT 中所示在子类上指定可执行类型 IMPLICIT ,这可确保发现所有必需的元数据,并在调用 ExpressRentalStation 上的方法时启动验证拦截器。

示例 11.8:使用 ExecutableType.IMPLICIT

package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;

@ValidateOnExecution(type = ExecutableType.ALL)
public interface RentalStation {

    @NotNull
    @Valid
    Car rentCar(
            @NotNull Customer customer,
            @NotNull @Future Date startDate,
            @Min(1) int durationInDays);

    @NotNull
    List<Car> getAvailableCars();
}
package org.hibernate.validator.referenceguide.chapter11.cdi.methodvalidation.implicit;

@ApplicationScoped
@ValidateOnExecution(type = ExecutableType.IMPLICIT)
public class ExpressRentalStation implements RentalStation {

    @Override
    public Car rentCar(Customer customer, Date startDate, @Min(1) int durationInDays) {
        //...
        return null;
    }

    @Override
    public List<Car> getAvailableCars() {
        //...
        return null;
    }
}

11.4. Jakarta EE

当你的应用程序在 WildFly 等 Jakarta EE 应用程序服务器上运行时,你还可以通过在 EJB 等受管对象中进行 @Resource 注入来获取 ValidatorValidatorFactory 实例,如 Example 11.9, “Retrieving Validator and ValidatorFactory via @Resource injection” 中所示。

示例 11.9:通过 @Resource 注入检索 ValidatorValidatorFactory

package org.hibernate.validator.referenceguide.chapter11.javaee;

public class RentalStationBean {

    @Resource
    private ValidatorFactory validatorFactory;

    @Resource
    private Validator validator;

    //...
}

或者,您可以从 JNDI 中获取验证程序和验证程序工厂,其名称分别为 "java:comp/Validator" 和 "java:comp/ValidatorFactory”。

类似于 @Inject 中基于 CDI 的注入,这些对象表示默认的校验程序和校验程序工厂,因此可以使用 XML 描述符 META-INF/validation.xml 对其进行配置(请参阅 Chapter 8, Configuring via XML )。

当您的应用程序启用 CDI 时,注入的对象也是 CDI 感知的,例如,支持在约束验证程序中进行依赖注入。

11.5. JavaFX

Hibernate Validator 还提供对 JavaFX 属性解包的支持。如果 JavaFX 存在于类路径中,将自动注册 JavaFX 属性的 _ValueExtractor_s。请参阅 Section 7.4, “JavaFX value extractors” 以获取示例和进一步的讨论。