SpringBoot使用Hibernate-validate

前言

验证数据是贯穿所有应用程序层的常见任务,从表示到持久层。通常,在每个层中实现相同的验证逻辑,这是耗时和容易出错的。为了避免这些验证的重复,开发人员经常将验证逻辑直接绑定到域模型中,将域类与验证代码(实际上是关于类本身的元数据)混合在一起。

 

 

JakartaBean验证2.0-为实体和方法验证定义了元数据模型和API。默认的元数据源是注释,可以通过使用XML覆盖和扩展元数据。API不绑定到特定的应用层或编程模型。它与web或持久化层都没有特别的联系,它既可用于服务器端应用程序编程,也可用于富客户端Swing应用程序开发人员。

 

 

Hibernate Validator是JakartaBean验证的参考实现。实现本身以及JakartaBean验证API和TCK都是在Apache软件许可证2.0.

Hibernate Validator 6和JakartaBean验证2.0需要Java 8或更高版本。

1.开始

本章将向您展示如何开始使用Hibernate Validator,这是JakartaBean验证的参考实现(RI)。对于以下快速启动,您需要:

  • JDK 8

  • Apache Maven

  • Internet连接(Maven必须下载所有必需的库)

1.1.项目设立

为了在Maven项目中使用Hibernate Validator,只需将以下依赖项添加到您的Pom.xml:

示例1.1:Hibernate Validator Maven依赖项
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.1.Final</version>
</dependency>

 

这种过渡性引入了对JakartaBean验证API的依赖(jakarta.validation:jakarta.validation-api:3.0.0).

1.1.1.统一EL

Hibernate Validator需要实现雅加达表达式语言有关在约束冲突消息中计算动态表达式的信息(请参见4.1节,“默认消息插值”)。当应用程序在JavaEE容器(如JBossAS)中运行时,容器已经提供了EL实现。然而,在JavaSE环境中,您必须将一个实现作为依赖项添加到POM文件中。例如,您可以添加以下依赖项来使用JakartaEL参考实施:

示例1.2:统一EL参考实现的maven依赖项
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>jakarta.el</artifactId>
    <version>4.0.0</version>
</dependency>

 

 

对于无法提供EL实现的环境,Hibernate Validator将提供一个第12.10条,“ParameterMessageInterpolator。但是,使用此内插器并不符合JakartaBean验证规范。

1.1.2.CDI

JakartaBean验证使用CDI定义了集成点(JakartaEE的上下文和依赖注入)。如果您的应用程序运行在一个没有直接提供这种集成的环境中,您可以通过在POM中添加以下Maven依赖项来使用Hibernate Validator CDI可移植扩展:

示例1.3:Hibernate Validator CDI可移植扩展Maven依赖项
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-cdi</artifactId>
    <version>7.0.1.Final</version>
</dependency>

 

注意,在JavaEE应用服务器上运行的应用程序通常不需要添加此依赖项。您可以了解更多关于JakartaBean验证和CDI集成的信息。第11.3款,“CDI”.

1.1.3.与安全管理器一起运行

Hibernate Validator支持使用安全管理器正在启用。为此,必须将多个权限分配给Hibernate Validator、JakartaBean验证API、classMate和JBossLogging的代码库,以及调用JakartaBean验证的代码库。下面说明如何通过策略文件由Java默认策略实现处理:

示例1.4:与安全管理器一起使用Hibernate Validator的策略文件
grant codeBase "file:path/to/hibernate-validator-7.0.1.Final.jar" {
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
    permission java.lang.RuntimePermission "accessDeclaredMembers";
    permission java.lang.RuntimePermission "setContextClassLoader";

    permission org.hibernate.validator.HibernateValidatorPermission "accessPrivateMembers";

    // Only needed when working with XML descriptors (validation.xml or XML constraint mappings)
    permission java.util.PropertyPermission "mapAnyUriToUri", "read";
};

grant codeBase "file:path/to/jakarta.validation-api-3.0.0.jar" {
    permission java.io.FilePermission "path/to/hibernate-validator-7.0.1.Final.jar", "read";
};

grant codeBase "file:path/to/jboss-logging-3.4.1.Final.jar" {
    permission java.util.PropertyPermission "org.jboss.logging.provider", "read";
    permission java.util.PropertyPermission "org.jboss.logging.locale", "read";
};

grant codeBase "file:path/to/classmate-1.5.1.jar" {
    permission java.lang.RuntimePermission "accessDeclaredMembers";
};

grant codeBase "file:path/to/validation-caller-x.y.z.jar" {
    permission org.hibernate.validator.HibernateValidatorPermission "accessPrivateMembers";
};

  

1.1.4.更新WildFly中的Hibernate Validator

这个WildFly应用服务器包含开箱即用的Hibernate Validator。为了将JakartaBean验证API和Hibernate Validator的服务器模块更新到最新和最大的版本,可以使用WildFly的补丁机制。

您可以从SourceForge或来自Maven Central,使用以下依赖项:

示例1.5:WildFly 22.0.0.Final修补程序文件的maven依赖项
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-modules</artifactId>
    <version>7.0.1.Final</version>
    <classifier>wildfly-22.0.0.Final-patch</classifier>
    <type>zip</type>
</dependency>

 

我们还为WildFly提供了一个修补程序:

示例1.6:WildFly修补程序文件的maven依赖项
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-modules</artifactId>
    <version>7.0.1.Final</version>
    <classifier>wildfly--patch</classifier>
    <type>zip</type>
</dependency>

 

下载了修补程序文件后,可以通过运行以下命令将其应用于WildFly:

例1.7:应用WildFly修补程序
$JBOSS_HOME/bin/jboss-cli.sh patch apply hibernate-validator-modules-7.0.1.Final-wildfly-22.0.0.Final-patch.zip

 

如果您想要撤消修补程序并返回到最初随服务器一起运行的Hibernate Validator版本,请运行以下命令:

示例1.8:回滚WildFly修补程序
$JBOSS_HOME/bin/jboss-cli.sh patch rollback --reset-configuration=true

 

您可以了解更多有关WildFly修补基础设施的信息。这里这里.

1.1.5.运行在Java 9上

最后,对Java 9和Java平台模块系统(JPMS)的支持是实验性的。还没有提供JPMS模块描述符,但是Hibernate Validator可以作为自动模块使用。

这些是使用Automatic-Module-Name标题:

  • JakartaBean验证API:java.validation

  • Hibernate Validator核心:org.hibernate.validator

  • Hibernate Validator CDI扩展:org.hibernate.validator.cdi

  • Hibernate Validator测试实用程序:org.hibernate.validator.testutils

  • Hibernate Validator注释处理器:org.hibernate.validator.annotationprocessor

这些模块名称是初步的,在将来的版本中提供真正的模块描述符时可能会更改。

1.2.应用约束

让我们直接进入一个示例,看看如何应用约束。

示例1.9:带约束注释的类汽车
package org.hibernate.validator.referenceguide.chapter01;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    //getters and setters ...
}

  

这个@NotNull@Size@Min注释用于声明应该应用于CAR实例字段的约束:

  • manufacturer绝不能null

  • licensePlate绝不能null必须长在2到14个字符之间。

  • seatCount必须至少有2

 

您可以在Hibernate validator中找到本参考指南中使用的所有示例的完整源代码。源库在GitHub上。

1.3.验证约束

若要执行这些约束的验证,请使用Validator举个例子。让我们看看单元测试Car:

示例1.10:类CarTest显示验证示例
package org.hibernate.validator.referenceguide.chapter01;

import java.util.Set;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

import org.junit.BeforeClass;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class CarTest {

    private static Validator validator;

    @BeforeClass
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    public void manufacturerIsNull() {
        Car car = new Car( null, "DD-AB-123", 4 );

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

        assertEquals( 1, constraintViolations.size() );
        assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
    }

    @Test
    public void licensePlateTooShort() {
        Car car = new Car( "Morris", "D", 4 );

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

        assertEquals( 1, constraintViolations.size() );
        assertEquals(
                "size must be between 2 and 14",
                constraintViolations.iterator().next().getMessage()
        );
    }

    @Test
    public void seatCountTooLow() {
        Car car = new Car( "Morris", "DD-AB-123", 1 );

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

        assertEquals( 1, constraintViolations.size() );
        assertEquals(
                "must be greater than or equal to 2",
                constraintViolations.iterator().next().getMessage()
        );
    }

    @Test
    public void carIsValid() {
        Car car = new Car( "Morris", "DD-AB-123", 2 );

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

        assertEquals( 0, constraintViolations.size() );
    }
}

  

setUp()方法aValidator对象从ValidatorFactory。一个Validator实例是线程安全的,可以多次重用.因此,它可以安全地存储在静态字段中,并在测试方法中用于验证不同的Car实例。

这个validate()方法返回一组ConstraintViolation实例,您可以迭代这些实例,以查看发生了哪些验证错误。前三种测试方法显示了一些预期的违反约束的情况:

  • 这个@NotNull对.的约束manufacturer被违反manufacturerIsNull()

  • 这个@Size对.的约束licensePlate被违反licensePlateTooShort()

  • 这个@Min对.的约束seatCount被违反seatCountTooLow()

如果对象成功验证,validate()返回空集,如您在carIsValid().

注意,只有包中的类jakarta.validation都是用的。这些都是从Bean验证API提供的。没有直接引用Hibernate Validator的类,从而生成可移植代码。

1.4.接下来去哪里?

这结束了Hibernate、Validator和JakartaBean验证世界的5分钟之旅。继续探索代码示例或查看在第14章,再读.

要了解有关bean和属性验证的更多信息,只需继续阅读第二章,声明和验证bean约束。如果您有兴趣使用JakartaBean验证来验证方法的前后条件,请参阅第三章,声明和验证方法约束。如果您的应用程序有特定的验证要求,请查看第六章,创建自定义约束.

2.声明和验证bean约束

在本章中,您将学习如何声明(请参阅第2.1节,“声明bean约束”)和验证(见第2.2节,“验证bean约束”)bean约束。第2.3节,“内置约束”提供Hibernate Validator附带的所有内置约束的概述。

如果您对将约束应用于方法参数和返回值感兴趣,请参阅第三章,声明和验证方法约束.

2.1.声明bean约束

JakartaBean验证中的约束是通过Java注释来表示的。在本节中,您将学习如何使用这些注释增强对象模型。有四种类型的bean约束:

  • 字段约束

  • 财产约束

  • 容器元素约束

  • 类约束

 

并不是所有的约束都可以放在所有这些级别上。实际上,JakartaBean验证定义的任何默认约束都不能放在类级别上。这个java.lang.annotation.Target约束注释本身中的注释确定可以在哪些元素上放置约束。看见第六章,创建自定义约束想了解更多信息。

2.1.1.字段级约束

约束可以通过注释类的字段来表示。例2.1,“字段级约束”显示字段级配置示例:

例2.1:字段级约束
package org.hibernate.validator.referenceguide.chapter02.fieldlevel;

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...
}

  

 

在使用字段级约束时,使用字段访问策略访问要验证的值。这意味着验证引擎直接访问实例变量,即使存在属性访问器,也不会调用属性访问器方法。

约束可以应用于任何访问类型(公共、私有等)的字段。但是,不支持静态字段上的约束。

 

在验证字节码增强对象时,应该使用属性级别的约束,因为字节代码增强库将无法通过反射确定字段访问。

2.1.2.属性级约束

如果模型类遵循JavaBeans标准情况下,还可以对bean类的属性进行注释,而不是对其字段进行注释。例2.2,“属性级约束”使用与例2.1,“字段级约束”但是,使用了属性级别约束。

例2.2:属性级约束
package org.hibernate.validator.referenceguide.chapter02.propertylevel;

public class Car {

    private String manufacturer;

    private boolean isRegistered;

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

    @NotNull
    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    @AssertTrue
    public boolean isRegistered() {
        return isRegistered;
    }

    public void setRegistered(boolean isRegistered) {
        this.isRegistered = isRegistered;
    }
}

  

 

属性的getter方法必须进行注释,而不是其setter。这样,也可以限制只读属性,而这些属性没有setter方法。

当使用属性级约束时,属性访问策略用于访问要验证的值,即验证引擎通过属性访问器方法访问状态。

 

建议两种方法中的任何一种都坚持使用。属性注释在一个类中。不建议对字段进行注释。附带的getter方法将导致对字段进行两次验证。

2.1.3.容器元素约束

可以直接在参数化类型的类型参数上指定约束:这些约束称为容器元素约束。

这要求ElementType.TYPE_USE被指定为@Target在约束定义中。在JakartaBean验证2.0中,内置的JakartaBean验证以及Hibernate Validator特定的约束指定ElementType.TYPE_USE并且可以直接在这个上下文中使用。

Hibernate Validator验证在以下标准Java容器上指定的容器元素约束:

  • 实现java.util.Iterable(如:ListS,Sets),

  • 实现java.util.Map支持键和值,

  • java.util.Optionaljava.util.OptionalIntjava.util.OptionalDoublejava.util.OptionalLong,

  • JavaFX的各种实现javafx.beans.observable.ObservableValue.

它还支持自定义容器类型上的容器元素约束(请参阅第七章,值提取).

 

在6之前的版本中,支持容器元素约束的子集。一个@Valid在容器级别需要注释来启用它们。从Hibernate Validator 6开始,这不再是必需的了。

下面是几个示例,演示各种Java类型上的容器元素约束。

在这些例子中,@ValidPart控件中允许使用的自定义约束。TYPE_USE背景。

2.1.3.1.带着Iterable

当将约束应用于Iterable类型参数,Hibernate Validator将验证每个元素。示例2.3,“容器元素对Set显示了一个Set具有容器元素约束。

例2.3:容器元素对Set
package org.hibernate.validator.referenceguide.chapter02.containerelement.set;

import java.util.HashSet;
import java.util.Set;

public class Car {

    private Set<@ValidPart String> parts = new HashSet<>();

    public void addPart(String part) {
        parts.add( part );
    }

    //...

}
Car car = new Car();
car.addPart( "Wheel" );
car.addPart( null );

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

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation =
        constraintViolations.iterator().next();
assertEquals(
        "'null' is not a valid car part.",
        constraintViolation.getMessage()
);
assertEquals( "parts[].<iterable element>",
        constraintViolation.getPropertyPath().toString() );

  

请注意属性路径如何清楚地声明冲突来自可迭代的元素。

2.1.3.2.带着List

当将约束应用于List类型参数,Hibernate Validator将验证每个元素。示例2.4,“容器元素对List显示了一个List具有容器元素约束。

示例2.4:容器元素对List
package org.hibernate.validator.referenceguide.chapter02.containerelement.list;

public class Car {

    private List<@ValidPart String> parts = new ArrayList<>();

    public void addPart(String part) {
        parts.add( part );
    }

    //...

}
Car car = new Car();
car.addPart( "Wheel" );
car.addPart( null );

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

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation =
        constraintViolations.iterator().next();
assertEquals(
        "'null' is not a valid car part.",
        constraintViolation.getMessage()
);
assertEquals( "parts[1].<list element>",
        constraintViolation.getPropertyPath().toString() );

  

在这里,属性路径还包含无效元素的索引。

2.1.3.3.带着Map

容器元素约束也在映射键和值上进行验证。示例2.5,“映射键和值的容器元素约束”显示了一个Map具有键的约束和值的约束。

示例2.5:映射键和值的容器元素约束
package org.hibernate.validator.referenceguide.chapter02.containerelement.map;

import java.util.HashMap;
import java.util.Map;

import jakarta.validation.constraints.NotNull;

public class Car {

    public enum FuelConsumption {
        CITY,
        HIGHWAY
    }

    private Map<@NotNull FuelConsumption, @MaxAllowedFuelConsumption Integer> fuelConsumption = new HashMap<>();

    public void setFuelConsumption(FuelConsumption consumption, int value) {
        fuelConsumption.put( consumption, value );
    }

    //...

}
Car car = new Car();
car.setFuelConsumption( Car.FuelConsumption.HIGHWAY, 20 );

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

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation =
        constraintViolations.iterator().next();
assertEquals(
        "20 is outside the max fuel consumption.",
        constraintViolation.getMessage()
);
assertEquals(
        "fuelConsumption[HIGHWAY].<map value>",
        constraintViolation.getPropertyPath().toString()
);
Car car = new Car();
car.setFuelConsumption( null, 5 );

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

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation =
        constraintViolations.iterator().next();
assertEquals(
        "must not be null",
        constraintViolation.getMessage()
);
assertEquals(
        "fuelConsumption<K>[].<map key>",
        constraintViolation.getPropertyPath().toString()
);

  

违反行为的财产路径特别令人感兴趣:

  • 无效元素的键包含在属性路径中(在第二个示例中,键是null).

  • 在第一个例子中,违规行为涉及<map value>,在第二个版本中,<map key>.

  • 在第二个示例中,您可能已经注意到类型参数的存在。<K>,稍后再详细介绍。

2.1.3.4.带着java.util.Optional

对类型参数应用约束时,Optional,Hibernate Validator将自动展开类型并验证内部值。示例2.6,“对可选容器元素的约束”显示了一个Optional具有容器元素约束。

示例2.6:可选容器元素约束
package org.hibernate.validator.referenceguide.chapter02.containerelement.optional;

public class Car {

    private Optional<@MinTowingCapacity(1000) Integer> towingCapacity = Optional.empty();

    public void setTowingCapacity(Integer alias) {
        towingCapacity = Optional.of( alias );
    }

    //...

}
Car car = new Car();
car.setTowingCapacity( 100 );

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

assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation = constraintViolations.iterator().next();
assertEquals(
        "Not enough towing capacity.",
        constraintViolation.getMessage()
);
assertEquals(
        "towingCapacity",
        constraintViolation.getPropertyPath().toString()
);

  

在这里,属性路径只包含我们正在考虑的属性的名称。Optional作为一个“透明”容器。

2.1.3.5.具有自定义容器类型

容器元素约束也可以与自定义容器一起使用。

ValueExtractor必须为允许检索要验证的值的自定义类型注册(请参阅第七章,值提取有关如何实现自己的ValueExtractor以及如何注册)。

示例2.7,“对自定义容器类型的容器元素约束”显示带有类型参数约束的自定义参数化类型的示例。

示例2.7:对自定义容器类型的容器元素约束
package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;

public class Car {

    private GearBox<@MinTorque(100) Gear> gearBox;

    public void setGearBox(GearBox<Gear> gearBox) {
        this.gearBox = gearBox;
    }

    //...

}
package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;

public class GearBox<T extends Gear> {

    private final T gear;

    public GearBox(T gear) {
        this.gear = gear;
    }

    public Gear getGear() {
        return this.gear;
    }
}
package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;

public class Gear {
    private final Integer torque;

    public Gear(Integer torque) {
        this.torque = torque;
    }

    public Integer getTorque() {
        return torque;
    }

    public static class AcmeGear extends Gear {
        public AcmeGear() {
            super( 60 );
        }
    }
}
package org.hibernate.validator.referenceguide.chapter02.containerelement.custom;

public class GearBoxValueExtractor implements ValueExtractor<GearBox<@ExtractedValue ?>> {

    @Override
    public void extractValues(GearBox<@ExtractedValue ?> originalValue, ValueExtractor.ValueReceiver receiver) {
        receiver.value( null, originalValue.getGear() );
    }
}
Car car = new Car();
car.setGearBox( new GearBox<>( new Gear.AcmeGear() ) );

Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );

ConstraintViolation<Car> constraintViolation =
        constraintViolations.iterator().next();
assertEquals(
        "Gear is not providing enough torque.",
        constraintViolation.getMessage()
);
assertEquals(
        "gearBox",
        constraintViolation.getPropertyPath().toString()
);
2.1.3.6.嵌套容器元素

  

嵌套容器元素也支持约束。

验证Car对象,如示例2.8,“嵌套容器元素的约束”,都是@NotNull对.的限制PartManufacturer将被强制执行。

示例2.8:嵌套容器元素的约束
package org.hibernate.validator.referenceguide.chapter02.containerelement.nested;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.validation.constraints.NotNull;

public class Car {

    private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers =
            new HashMap<>();

    //...
}

  

2.1.4.类级约束

最后但并非最不重要的是,约束也可以放在类级别上。在这种情况下,没有一个属性是验证的主题,而是完整的对象。如果验证依赖于一个对象的多个属性之间的相关性,那么类级约束是有用的。

这个Car例2.9,“类级约束”有两个属性seatCountpassengers此外,我们亦应确保乘客名单上的乘客数目不会超过现有座位数目。为此目的@ValidPassengerCount约束在类级别上添加。该约束的验证器可以访问完整的Car反对,允许比较座位数和乘客数。

请参阅第6.2节,“类级约束”若要详细了解如何实现此自定义约束,请执行以下操作。

例2.9:类级约束
package org.hibernate.validator.referenceguide.chapter02.classlevel;

@ValidPassengerCount
public class Car {

    private int seatCount;

    private List<Person> passengers;

    //...
}

  

2.1.5.约束继承

当一个类实现一个接口或扩展另一个类时,在超级类型上声明的所有约束注释都以与类本身指定的约束相同的方式应用。为了使事情更清楚,让我们看看下面的示例:

示例2.10:约束继承
package org.hibernate.validator.referenceguide.chapter02.inheritance;

public class Car {

    private String manufacturer;

    @NotNull
    public String getManufacturer() {
        return manufacturer;
    }

    //...
}
package org.hibernate.validator.referenceguide.chapter02.inheritance;

public class RentalCar extends Car {

    private String rentalStation;

    @NotNull
    public String getRentalStation() {
        return rentalStation;
    }

    //...
}

  

在这里上课RentalCarCar并添加属性rentalStation。如果RentalCar被验证,而不仅仅是@NotNull对.的约束rentalStation的约束。manufacturer来自父类。

如果Car不是超类,而是由RentalCar.

如果重写方法,则聚合约束注释。所以如果RentalCar俯冲getManufacturer()Car,在重写方法中注释的任何约束都将在@NotNull来自超类的约束。

2.1.6.对象图

JakartaBean验证API不仅允许验证单个类实例,还允许完成对象图(级联验证)。为此,只需对表示对另一个对象的引用的字段或属性进行注释。@Valid如上文所示示例2.11,“级联验证”.

示例2.11:级联验证
package org.hibernate.validator.referenceguide.chapter02.objectgraph;

public class Car {

    @NotNull
    @Valid
    private Person driver;

    //...
}
package org.hibernate.validator.referenceguide.chapter02.objectgraph;

public class Person {

    @NotNull
    private String name;

    //...
}

  

如果Car被验证,则引用Person对象也将被验证,因为driver字段被注释为@Valid。因此,验证Car如果name引用的字段Person实例是null.

对象图的验证是递归的,也就是说,如果标记为级联验证的引用指向本身具有带有注释的属性的对象。@Valid,验证引擎也将对这些引用进行跟踪。验证引擎将确保在级联验证期间不会出现无限循环,例如,如果两个对象相互保存引用。

请注意null在级联验证期间,值将被忽略。

作为约束,对象图验证也适用于容器元素。这意味着容器的任何类型参数都可以用@Valid,这将导致在验证父对象时验证每个包含的元素。

 

嵌套容器元素也支持级联验证。

示例2.12:容器的级联验证
package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;

public class Car {

    private List<@NotNull @Valid Person> passengers = new ArrayList<Person>();

    private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers = new HashMap<>();

    //...
}
package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;

public class Part {

    @NotNull
    private String name;

    //...
}
package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement;

public class Manufacturer {

    @NotNull
    private String name;

    //...
}

  

验证Car中所示的类。示例2.12,“容器的级联验证”...ConstraintViolation将创建:

  • 如果任何Person包含在乘客列表中的对象具有null姓名;

  • 如果任何Part包含在映射键中的对象具有null姓名;

  • 如果任何Manufacturer嵌套在映射值中的列表中包含的对象具有null名字。

 

在6之前的版本中,Hibernate Validator支持容器元素子集的级联验证,并且它是在容器级别实现的(例如,您可以使用@Valid private List<Person>若要启用级联验证,请执行以下操作:Person).

这仍然是支持的,但不建议这样做。请使用容器元素级别@Valid注解反而更有表现力。

2.2.验证bean约束

这个Validator接口是JakartaBean验证中最重要的对象。下一节演示如何获得Validator举个例子。之后,您将学习如何使用Validator接口。

2.2.1.获得Validator实例

验证实体实例的第一步是获取Validator举个例子。通往此实例的道路通过Validation类和aValidatorFactory。最简单的方法是使用静态方法。Validation#buildDefaultValidatorFactory():

例2.13:Validation#buildDefaultValidatorFactory()
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();

  

此引导在默认配置中引导验证器。请参阅第九章,自举了解更多有关不同的引导方法以及如何获得特定配置的Validator举个例子。

2.2.2.验证器方法

这个Validator接口包含三个方法,可用于验证整个实体或实体的单个属性。

这三个方法都返回一个Set<ConstraintViolation>。如果验证成功,则集为空。否则ConstraintViolation为每个违反的约束添加实例。

所有验证方法都有一个var-args参数,该参数可用于指定在执行验证时应考虑哪些验证组。如果未指定参数,则默认验证组(jakarta.validation.groups.Default)使用。验证组的主题将在第五章,分组约束.

2.2.2.1. Validator#validate()

使用validate()方法来执行对给定bean的所有约束的验证。示例2.14,“使用Validator#validate()的实例的验证。Car来自例2.2,“属性级约束”无法满足@NotNull对象的约束。manufacturer财产。因此,验证调用返回一个ConstraintViolation对象。

例2.14:使用Validator#validate()
Car car = new Car( null, true );

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

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

  

2.2.2.2. Validator#validateProperty()

在.的帮助下validateProperty()可以验证给定对象的单个命名属性。属性名是JavaBeans属性名。

示例2.15:使用Validator#validateProperty()
Car car = new Car( null, true );

Set<ConstraintViolation<Car>> constraintViolations = validator.validateProperty(
        car,
        "manufacturer"
);

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

  

2.2.2.3. Validator#validateValue()

通过使用validateValue()方法可以检查给定类的单个属性是否可以成功验证,如果该属性具有指定的值:

例2.16:使用Validator#validateValue()
Set<ConstraintViolation<Car>> constraintViolations = validator.validateValue(
        Car.class,
        "manufacturer",
        null
);

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

  

 

@Valid不受尊敬validateProperty()validateValue().

Validator#validateProperty()例如,用于将JakartaBean验证集成到JSF 2中(请参见第11.2节,“JSF&Seam”)在将值传播到模型之前,对输入到表单中的值执行验证。

2.2.3. ConstraintViolation

2.2.3.1. ConstraintViolation方法

现在是时候仔细看看ConstraintViolation是。使用不同的方法ConstraintViolation可以确定许多关于验证失败原因的有用信息。下面概述了这些方法。“示例”列下的值指的是示例2.14,“使用Validator#validate().

getMessage()

内插错误消息

“不得为空”

getMessageTemplate()

非插值错误消息

“{…​NotNull.Message}”

getRootBean()

正在验证的根bean

小汽车

getRootBeanClass()

正在验证的根bean的类。

Car.class

getLeafBean()

如果是bean约束,则在bean实例上应用约束;如果是属性约束,则应用该约束的bean实例承载该属性。

car

getPropertyPath()

根bean的验证值的属性路径。

包含一个具有类型的节点。PROPERTY并命名为“制造商”

getInvalidValue()

未能传递约束的值。

null

getConstraintDescriptor()

报告失败的约束元数据

描述符@NotNull

2.2.3.2.利用属性路径

若要确定触发违规的元素,需要利用getPropertyPath()方法。

回归Path是由NodeS描述元素的路径。

的结构的更多信息。Path以及各种类型的NodeS可以在这个ConstraintViolation剖面雅加达Bean验证规范。

2.3.内置约束

Hibernate Validator包含一组常用的基本约束。这些都是由JakartaBean验证规范定义的约束(请参见第2.3.1节,“JakartaBean验证约束”)。此外,Hibernate Validator提供了有用的自定义约束(请参阅第2.3.2节,“附加限制”).

2.3.1.JakartaBean验证约束

下面可以找到JakartaBean验证API中指定的所有约束的列表。所有这些约束都适用于字段/属性级别,在JakartaBean验证规范中没有定义任何类级约束。如果使用Hibernate对象-关系映射程序,则在为模型创建DDL时会考虑到一些约束(参见“Hibernate元数据影响”)。

 

Hibernate Validator允许将一些约束应用于比JakartaBean验证规范所要求的更多的数据类型(例如,@Max可以应用于字符串)。依赖此特性会影响您的应用程序在JakartaBean验证提供者之间的可移植性。

@AssertFalse

检查带注释的元素是否为false

支持的数据类型

Booleanboolean

Hibernate元数据影响

@AssertTrue

检查带注释的元素是否为真

支持的数据类型

Booleanboolean

Hibernate元数据影响

@DecimalMax(value=, inclusive=)

时,检查带注释的值是否小于指定的最大值。inclusive=假。否则,该值是否小于或等于指定的最大值。参数值是根据BigDecimal字符串表示

支持的数据类型

BigDecimalBigIntegerCharSequencebyteshortintlong以及原语类型的相应包装器;另外还受HV支持:Numberjavax.money.MonetaryAmount(如果JSR 354 API实现在类路径上)

Hibernate元数据影响

@DecimalMin(value=, inclusive=)

时,检查带注释的值是否大于指定的最小值。inclusive=假。否则,该值是否大于或等于指定的最小值。参数值是根据BigDecimal字符串表示

支持的数据类型

BigDecimalBigIntegerCharSequencebyteshortintlong以及原语类型的相应包装器;另外还受HV支持:Numberjavax.money.MonetaryAmount

Hibernate元数据影响

@Digits(integer=, fraction=)

检查带注释的值是否是一个最多可达integer数字和fraction小数位

支持的数据类型

BigDecimalBigIntegerCharSequencebyteshortintlong以及原语类型的相应包装器;另外还受HV支持:Numberjavax.money.MonetaryAmount

Hibernate元数据影响

定义列精度和刻度

@Email

检查指定的字符序列是否为有效的电子邮件地址。可选参数regexpflags允许指定电子邮件必须匹配的附加正则表达式(包括正则表达式标志)。

支持的数据类型

CharSequence

Hibernate元数据影响

@Future

检查注释日期是否在将来。

支持的数据类型

java.util.Datejava.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate;附加由HV支持,如果Joda时间日期/时间API在类路径上:ReadablePartialReadableInstant

Hibernate元数据影响

@FutureOrPresent

检查注释日期是在现在还是将来。

支持的数据类型

java.util.Datejava.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate;附加由HV支持,如果Joda时间日期/时间API在类路径上:ReadablePartialReadableInstant

Hibernate元数据影响

@Max(value=)

检查带注释的值是否小于或等于指定的最大值。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

在列上添加检查约束。

@Min(value=)

检查带注释的值是否大于或等于指定的最小值。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

在列上添加检查约束。

@NotBlank

检查注释的字符序列不为NULL,并且修整的长度大于0。差异@NotEmpty该约束只能应用于字符序列,并且忽略了尾随空格。

支持的数据类型

CharSequence

Hibernate元数据影响

@NotEmpty

检查带注释的元素是否为空或空。

支持的数据类型

CharSequenceCollectionMap和阵列

Hibernate元数据影响

@NotNull

检查注释的值是否为null

支持的数据类型

任何类型

Hibernate元数据影响

列不可空

@Negative

检查元素是否严格为负值。零值被认为无效。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

@NegativeOrZero

检查元素是负还是零。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

@Null

检查带注释的值是否为null

支持的数据类型

任何类型

Hibernate元数据影响

@Past

检查带注释的日期是否已过去。

支持的数据类型

java.util.Date,java.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate;附加由HV支持,如果Joda时间日期/时间API在类路径上:ReadablePartialReadableInstant

Hibernate元数据影响

@PastOrPresent

检查注释日期是过去的还是现在的

支持的数据类型

java.util.Date,java.util.Calendarjava.time.Instantjava.time.LocalDatejava.time.LocalDateTimejava.time.LocalTimejava.time.MonthDayjava.time.OffsetDateTimejava.time.OffsetTimejava.time.Yearjava.time.YearMonthjava.time.ZonedDateTimejava.time.chrono.HijrahDatejava.time.chrono.JapaneseDatejava.time.chrono.MinguoDatejava.time.chrono.ThaiBuddhistDate;附加由HV支持,如果Joda时间日期/时间API在类路径上:ReadablePartialReadableInstant

Hibernate元数据影响

@Pattern(regex=, flags=)

检查带注释的字符串是否与正则表达式匹配。regex考虑给定的标志match

支持的数据类型

CharSequence

Hibernate元数据影响

@Positive

检查元素是否严格为正。零值被认为无效。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

@PositiveOrZero

检查元素是正还是零。

支持的数据类型

BigDecimalBigIntegerbyteshortintlong以及原语类型的相应包装器;另外还受HV支持:CharSequence(由字符序列表示的数值),任何子类型的Numberjavax.money.MonetaryAmount

Hibernate元数据影响

@Size(min=, max=)

检查带注释的元素的大小是否介于minmax(包括)

支持的数据类型

CharSequenceCollectionMap和阵列

Hibernate元数据影响

列长度将设置为max

 

在上面列出的参数之上,每个约束都有参数消息、组和有效载荷。这是JakartaBean验证规范的要求。

2.3.2.附加约束

除了JakartaBean验证API定义的约束之外,Hibernate Validator还提供了以下几个有用的自定义约束。除了一个例外,这些约束也适用于字段/属性级别,仅适用于@ScriptAssert是类级约束。

@CreditCardNumber(ignoreNonDigitCharacters=)

检查带注释的字符序列是否通过Luhn校验和测试。注意,这个验证的目的是检查用户的错误,而不是信用卡的有效性!另见信用卡号码剖析ignoreNonDigitCharacters允许忽略非数字字符。默认情况是false.

支持的数据类型

CharSequence

Hibernate元数据影响

@Currency(value=)

检查注释的货币单位。javax.money.MonetaryAmount是指定货币单位的一部分。

支持的数据类型

任何亚型javax.money.MonetaryAmount(如果JSR 354 API实现在类路径上)

Hibernate元数据影响

@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)

带注释的检查java.time.Duration元素不大于由注释参数构造的元素。在下列情况下允许平等inclusive标志设置为true.

支持的数据类型

java.time.Duration

Hibernate元数据影响

@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)

带注释的检查java.time.Duration元素不少于由注释参数构造的元素。在下列情况下允许平等inclusive标志设置为true.

支持的数据类型

java.time.Duration

Hibernate元数据影响

@EAN

检查带注释的字符序列是否有效。艾恩条形码。类型确定条形码的类型。默认情况是EAN-13。

支持的数据类型

CharSequence

Hibernate元数据影响

@ISBN

检查带注释的字符序列是否有效。ISBNtype确定ISBN的类型。默认情况是ISBN-13。

支持的数据类型

CharSequence

Hibernate元数据影响

@Length(min=, max=)

验证带注释的字符序列是否在minmax包括在内

支持的数据类型

CharSequence

Hibernate元数据影响

列长度将设置为最大。

@CodePointLength(min=, max=, normalizationStrategy=)

验证带注释的字符序列的代码点长度在minmax包括在内。验证归一化值,如果normalizationStrategy已经设定好了。

支持的数据类型

CharSequence

Hibernate元数据影响

@LuhnCheck(startIndex= , endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)

检查注释字符序列中的数字是否通过Luhn校验和算法(另请参阅Luhn算法). startIndexendIndex只允许在指定的子字符串上运行算法。checkDigitIndex允许将字符序列中的任意数字用作检查数字。如果未指定,则假定检查数字是指定范围的一部分。最后但并非最不重要,ignoreNonDigitCharacters允许忽略非数字字符。

支持的数据类型

CharSequence

Hibernate元数据影响

@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)

检查注释字符序列中的数字是否通过泛型mod 10校验和算法。multiplier确定奇数的乘数(默认值为3),weight偶数的权重(默认为1)。startIndexendIndex只允许在指定的子字符串上运行算法。checkDigitIndex允许将字符序列中的任意数字用作检查数字。如果未指定,则假定检查数字是指定范围的一部分。最后但并非最不重要,ignoreNonDigitCharacters允许忽略非数字字符。

支持的数据类型

CharSequence

Hibernate元数据影响

@Mod11Check(threshold=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=, treatCheck10As=, treatCheck11As=)

检查注释字符序列中的数字是否通过mod 11校验和算法。threshold指定mod11乘法器增长的阈值;如果没有指定值,则乘法器将无限期地增长。treatCheck10AstreatCheck11As当mod 11校验和分别等于10或11时,指定要使用的校验位数。默认值分别为X和0。startIndexendIndex checkDigitIndexignoreNonDigitCharacters的语义与@Mod10Check.

支持的数据类型

CharSequence

Hibernate元数据影响

@Normalized(form=)

验证注释的字符序列是否根据给定的form.

支持的数据类型

CharSequence

Hibernate元数据影响

@Range(min=, max=)

检查注释的值是否位于(包括)指定的最小值和最大值之间。

支持的数据类型

BigDecimalBigIntegerCharSequencebyteshortintlong以及原语类型的相应包装器。

Hibernate元数据影响

@ScriptAssert(lang=, script=, alias=, reportOn=)

检查是否可以根据带注释的元素成功地计算给定的脚本。为了使用这个约束,JSR 223定义的Java脚本API的实现(“Java脚本编写”)TM)必须是类路径的一部分。要计算的表达式可以用任何脚本或表达式语言编写,在类路径中可以找到与jsr 223兼容的引擎。reportOn属性报告特定属性(而不是整个对象)上的约束冲突。

支持的数据类型

任何类型

Hibernate元数据影响

@UniqueElements

检查带注释的集合是否仅包含唯一元素。相等值是使用equals()方法。默认消息不包括重复元素列表,但可以通过重写消息并使用{duplicates}消息参数重复元素的列表也包含在约束冲突的动态有效负载中。

支持的数据类型

Collection

Hibernate元数据影响

@URL(protocol=, host=, port=, regexp=, flags=)

根据RFC 2396检查注释的字符序列是否是有效的URL。如果任何可选参数protocolhostport则对应的URL片段必须与指定的值匹配。可选参数regexpflags允许指定URL必须匹配的附加正则表达式(包括正则表达式标志)。默认情况下,此约束使用java.net.URL构造函数来验证给定字符串是否表示有效的URL。基于正则表达式的版本也是可用的-RegexpURLValidator-可以通过XML进行配置(请参见第8.2节,“通过constraint-mappings)或编程API(请参见第12.15.2节,“以编程方式添加约束定义”).

支持的数据类型

CharSequence

Hibernate元数据影响

2.3.2.1.具体国家的制约因素

Hibernate Validator还提供了一些特定于国家的限制,例如社会保障号码的验证。

 

如果您必须实现特定于国家的约束,请考虑将其作为Hibernate Validator的一个贡献!

@CNPJ

检查附加注释的字符序列是否代表巴西企业纳税者登记号(地籍登记编号为“Pessoa Jurídica”)

支持的数据类型

CharSequence

Hibernate元数据影响

国家

巴西

@CPF

检查附加注释的字符序列是否代表巴西纳税人个人注册号码(地籍编号为“Pessoa Física”)

支持的数据类型

CharSequence

Hibernate元数据影响

国家

巴西

@TituloEleitoral

检查注释字符序列是否代表巴西选民身份证号码(蒂图洛·埃莱托拉尔)

支持的数据类型

CharSequence

Hibernate元数据影响

国家

巴西

@NIP

检查注释的字符序列是否代表波兰增值税标识号(尼普)

支持的数据类型

CharSequence

Hibernate元数据影响

国家

波兰

@PESEL

检查注释的字符序列是否代表波兰国民识别号(白塞)

支持的数据类型

CharSequence

Hibernate元数据影响

国家

波兰

@REGON

检查注释字符序列是否代表波兰纳税人的识别号(雷贡)。可应用于regon的9位和14位版本。

支持的数据类型

CharSequence

Hibernate元数据影响

国家

波兰

@INN

检查注释字符序列是否代表俄罗斯纳税人的识别号(客栈)。可适用于国际客栈的个人版本和法律版本。

支持的数据类型

CharSequence

Hibernate元数据影响

国家

俄罗斯

 

在某些情况下,JakartaBean验证约束和Hibernate Validator提供的自定义约束都不能满足您的需求。在这种情况下,您可以轻松地编写自己的约束。您可以在第六章,创建自定义约束.

3.声明和验证方法约束

在Bean验证1.1中,约束不仅可以应用于JavaBean及其属性,还可以应用于任何Java类型的方法和构造函数的参数和返回值。这样,JakartaBean验证约束就可以用来指定

  • 调用方在调用方法或构造函数之前必须满足的先决条件(对可执行文件的参数应用约束)

  • 方法或构造函数调用后向调用方保证的后置条件(通过将约束应用于可执行文件的返回值)

 

为本参考指南的目的,术语方法约束同时引用方法和构造函数约束,如果没有另外说明。偶尔,这个词可执行在引用方法和构造函数时使用。

与检查参数和返回值正确性的传统方法相比,这种方法有几个优点:

  • 检查不需要手动执行(例如,通过抛出IllegalArgumentException,导致编写和维护代码减少。

  • 可执行文件的前后条件不必在文档中再次表示,因为约束注释将自动包含在生成的JavaDoc中。这避免了冗余,并减少了实现和文档之间不一致的可能性。

 

为了使注释在带注释的元素的JavaDoc中显示出来,注释类型本身必须使用元注释@文档进行注释。这是所有内置约束的情况,并且被认为是任何自定义约束的最佳实践。

在本章的其余部分中,您将学习如何声明参数和返回值约束,以及如何使用ExecutableValidatorAPI

3.1.声明方法约束

3.1.1.参数约束

通过向方法或构造函数的参数添加约束注释来指定方法或构造函数的先决条件,如示例3.1,“声明方法和构造函数参数约束”.

示例3.1:声明方法和构造函数参数约束
package org.hibernate.validator.referenceguide.chapter03.parameter;

public class RentalStation {

    public RentalStation(@NotNull String name) {
        //...
    }

    public void rentCar(
            @NotNull Customer customer,
            @NotNull @Future Date startDate,
            @Min(1) int durationInDays) {
        //...
    }
}

  

这里宣布了以下先决条件:

  • 这个name传递给RentalCar构造函数不能是null

  • 调用rentCar()方法,给定customer不可null,租房的开始日期不得为null以及将来,最后的租期必须是至少一天。

注意,声明方法或构造函数约束本身并不会在调用可执行文件时自动导致它们的验证。相反,ExecutableValidatorAPI(见第3.2节,“验证方法约束”)必须用于执行验证,这通常是使用方法拦截工具(如AOP、代理对象等)完成的。

约束只能应用于实例方法,即不支持在静态方法上声明约束。根据用于触发方法验证的拦截工具的不同,可能会应用其他限制,例如,对于作为拦截目标所支持的方法的可见性。请参阅拦截技术的文档,以确定是否存在任何此类限制。

3.1.1.1.交叉参数约束

有时,验证不仅依赖于单个参数,还取决于方法或构造函数的几个甚至所有参数。这种要求可以通过跨参数约束来满足。

交叉参数约束可视为与类级约束等价的方法验证.这两种方法都可以用于实现基于几个元素的验证需求。类级约束适用于bean的几个属性,而跨参数约束则应用于可执行文件的多个参数。

与单参数约束不同,跨参数约束是在方法或构造函数上声明的,如您在示例3.2,“声明跨参数约束”。这里,交叉参数约束@LuggageCountMatchesPassengerCountload()方法是保证旅客没有超过两件行李。

示例3.2:声明跨参数约束
package org.hibernate.validator.referenceguide.chapter03.crossparameter;

public class Car {

    @LuggageCountMatchesPassengerCount(piecesOfLuggagePerPassenger = 2)
    public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
        //...
    }
}

 

正如您将在下一节中了解到的,返回值约束也是在方法级别上声明的。为了区分跨参数约束和返回值约束,在ConstraintValidator实现使用@SupportedValidationTarget注释您可以在第6.3节,“跨参数约束”它显示了如何实现您自己的跨参数约束。

在某些情况下,约束可以应用于可执行文件的参数(即跨参数约束),也可以应用于返回值。其中一个例子是自定义约束,它允许使用表达式或脚本语言指定验证规则。

这样的约束必须定义一个成员。validationAppliesTo()可以在声明时使用它来指定约束目标。如图所示示例3.3,“指定约束的目标”通过指定以下方法将约束应用于可执行文件的参数validationAppliesTo = ConstraintTarget.PARAMETERS,同时ConstraintTarget.RETURN_VALUE用于将约束应用于可执行的返回值。

示例3.3:指定约束的目标
package org.hibernate.validator.referenceguide.chapter03.crossparameter.constrainttarget;

public class Garage {

    @ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.PARAMETERS)
    public Car buildCar(List<Part> parts) {
        //...
        return null;
    }

    @ELAssert(expression = "...", validationAppliesTo = ConstraintTarget.RETURN_VALUE)
    public Car paintCar(int color) {
        //...
        return null;
    }
}

 

虽然这样的约束适用于可执行文件的参数和返回值,但通常可以自动推断目标。如果将约束声明为

  • 带参数的void方法(约束适用于参数)

  • 一个具有返回值但没有参数的可执行文件(约束适用于返回值)

  • 既不是方法也不是构造函数,而是字段、参数等(约束适用于带注释的元素)

在这些情况下,您不必指定约束目标。如果增加了源代码的可读性,仍然建议这样做。如果在无法自动确定约束目标的情况下未指定约束目标,则ConstraintDeclarationException已经长大了。

3.1.2.返回值约束

方法或构造函数的后置条件是通过向可执行文件中添加约束注释来声明的,如示例3.4,“声明方法和构造函数返回值约束”.

示例3.4:声明方法和构造函数返回值约束
package org.hibernate.validator.referenceguide.chapter03.returnvalue;

public class RentalStation {

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

    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getCustomers() {
        //...
        return null;
    }
}

 

以下约束适用于RentalStation:

  • 任何新创建的RentalStation对象必须满足@ValidRentalStation约束

  • 返回的客户列表getCustomers()不可null并且必须至少包含on元素。

  • 返回的客户列表getCustomers()绝不能包含null对象

 

正如您在上面的示例中所看到的,方法返回值支持容器元素约束。方法参数也支持它们。

3.1.3.级联验证

类似于JavaBeans属性的级联验证(请参见第2.1.6节,“对象图”),@Valid注释可用于标记可执行参数并返回级联验证的值。在验证参数或返回值时,用@Valid,还将验证参数或返回值对象上声明的约束。

在……里面示例3.5,“为级联验证标记可执行参数和返回值”car方法参数Garage#checkCar()的返回值。Garage构造函数标记为级联验证。

示例3.5:为级联验证标记可执行参数和返回值
package org.hibernate.validator.referenceguide.chapter03.cascaded;

public class Garage {

    @NotNull
    private String name;

    @Valid
    public Garage(String name) {
        this.name = name;
    }

    public boolean checkCar(@Valid @NotNull Car car) {
        //...
        return false;
    }
}
package org.hibernate.validator.referenceguide.chapter03.cascaded;

public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    public Car(String manufacturer, String licencePlate) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
    }

    //getters and setters ...
}

 

验证checkCar()方法的属性的约束。Car对象也进行了计算。类似地,@NotNull的名称字段的约束。Garage的返回值时,将检查Garage构造函数

通常,级联验证对可执行文件的工作方式与对JavaBeans属性的验证完全一样。

特别是,null值在级联验证过程中被忽略(这在构造函数返回值验证期间自然不会发生),级联验证是递归执行的,也就是说,如果标记为级联验证的参数或返回值对象本身具有标记为@Valid,在引用元素上声明的约束也将被验证。

与字段和属性一样,还可以对返回值和参数的容器元素(例如集合元素、映射或自定义容器)声明级联验证。

在这种情况下,容器包含的每个元素都会得到验证。因此,在验证checkCars()方法示例3.6,“标记为级联验证的方法参数的容器元素”,传递列表的每个元素实例都将被验证,并且ConstraintViolation当任何包含的Car实例无效。

示例3.6:标记为级联验证的方法参数的容器元素
package org.hibernate.validator.referenceguide.chapter03.cascaded.containerelement;

public class Garage {

    public boolean checkCars(@NotNull List<@Valid Car> cars) {
        //...
        return false;
    }
}

 

3.1.4.继承层次中的方法约束

在继承层次结构中声明方法约束时,必须注意以下规则:

  • 方法的调用方要满足的先决条件可能不会在子类型中得到加强。

  • 方法的调用方保证的后置条件不会在子类型中减弱。

这些规则的动机是行为亚型这就要求无论哪种类型T,也是一个子类型。ST可以在不改变程序行为的情况下使用。

作为一个示例,考虑使用静态类型调用对象上的方法的类。T。如果该对象的运行时类型是SS加上附加的先决条件,客户端类可能无法满足这些先决条件,因为它们是不知道的。行为子类型的规则也称为Liskov代换原理.

JakartaBean验证规范实现了第一个规则,它不允许对重写或实现超类型(超类或接口)中声明的方法的方法进行参数约束。示例3.7,“子类型中的非法方法参数约束”显示违反此规则。

示例3.7:子类型中的非法方法参数约束
package org.hibernate.validator.referenceguide.chapter03.inheritance.parameter;

public interface Vehicle {

    void drive(@Max(75) int speedInMph);
}
package org.hibernate.validator.referenceguide.chapter03.inheritance.parameter;

public class Car implements Vehicle {

    @Override
    public void drive(@Max(55) int speedInMph) {
        //...
    }
}

 

这个@Max对.的约束Car#drive()是非法的,因为此方法实现了接口方法。Vehicle#drive()。注意,如果父类型方法本身不声明任何参数约束,则重写方法上的参数约束也是不允许的。

此外,如果一个方法覆盖或实现了在几个并行超级类型中声明的方法(例如,两个接口不互相扩展或一个类,以及一个接口没有由该类实现),则在任何涉及的类型中都不能为该方法指定任何参数约束。中的类型示例3.8,“层次结构的并行类型中的非法方法参数约束”证明违反了这条规则。方法RacingCar#drive()覆盖Vehicle#drive()以及Car#drive()。因此,对Vehicle#drive()是非法的。

示例3.8:层次结构的并行类型中的非法方法参数约束
package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;

public interface Vehicle {

    void drive(@Max(75) int speedInMph);
}
package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;

public interface Car {

    void drive(int speedInMph);
}
package org.hibernate.validator.referenceguide.chapter03.inheritance.parallel;

public class RacingCar implements Car, Vehicle {

    @Override
    public void drive(int speedInMph) {
        //...
    }
}

 

前面描述的限制仅适用于参数约束。相反,可以在重写或实现任何超类型方法的方法中添加返回值约束。

在这种情况下,所有方法的返回值约束都适用于子类型方法,即在子类型方法本身上声明的约束以及重写/实现的超类型方法上的任何返回值约束。这是合法的,因为设置额外的返回值约束可能永远不会削弱向方法调用方保证的后置条件。

因此,在验证方法的返回值时Car#getPassengers()显示在示例3.9,“父类型和子类型方法的返回值约束”@Size对方法本身以及@NotNull对实现的接口方法的约束Vehicle#getPassengers()申请。

示例3.9:父类型和子类型方法的返回值约束
package org.hibernate.validator.referenceguide.chapter03.inheritance.returnvalue;

public interface Vehicle {

    @NotNull
    List<Person> getPassengers();
}
package org.hibernate.validator.referenceguide.chapter03.inheritance.returnvalue;

public class Car implements Vehicle {

    @Override
    @Size(min = 1)
    public List<Person> getPassengers() {
        //...
        return null;
    }
}

 

如果验证引擎检测到任何违反上述规则的行为,则ConstraintDeclarationException将被提升。

 

本节中描述的规则仅适用于方法,而不适用于构造函数。根据定义,构造函数从不覆盖超类型构造函数。因此,在验证构造函数调用的参数或返回值时,只应用在构造函数本身上声明的约束,而不应用在父类型构造函数上声明的任何约束。

 

控件中包含的配置参数可以放松这些规则的强制执行。MethodValidationConfiguration的属性HibernateValidatorConfiguration在创建Validator举个例子。另见第12.3节,“放宽类层次结构中方法验证的要求”.

3.2.验证方法约束

方法约束的验证使用ExecutableValidator接口。

在……里面第3.2.1节,“获取ExecutableValidator实例“您将学习如何获得ExecutableValidator实例同时第3.2.2节,“ExecutableValidator方法“演示如何使用此接口提供的不同方法。

而不是调用ExecutableValidator方法直接从应用程序代码中调用,通常通过方法拦截技术(如AOP、代理对象等)调用,这将使可执行约束在方法或构造函数调用时自动和透明地被验证。典型的ConstraintViolationException如果违反任何约束,则由集成层引发。

3.2.1.获得ExecutableValidator实例

您可以检索ExecutableValidator实例通过Validator#forExecutables()如图所示示例3.10,“获取ExecutableValidator实例“.

示例3.10:获取ExecutableValidator实例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();

 

在本例中,可执行验证器从默认的验证器工厂中检索,但如果需要,还可以引导特定配置的工厂,如第九章,自举,例如,为了使用特定的参数名称提供程序(请参阅第9.2.4节,“ParameterNameProvider).

3.2.2. ExecutableValidator方法

这个ExecutableValidator接口共提供四种方法:

  • validateParameters()validateReturnValue()用于方法验证

  • validateConstructorParameters()validateConstructorReturnValue()用于构造函数验证

就像Validator,所有这些方法都返回Set<ConstraintViolation>,其中包含ConstraintViolation实例,如果验证成功,则该约束为空。而且,所有方法都有一个var-args组参数,通过该参数可以通过要考虑验证的验证组。

下面几节中的示例是基于Car中所示的类。示例3.11,“类”Car用约束方法和构造函数“.

示例3.11:类Car带约束方法和构造函数
package org.hibernate.validator.referenceguide.chapter03.validation;

public class Car {

    public Car(@NotNull String manufacturer) {
        //...
    }

    @ValidRacingCar
    public Car(String manufacturer, String team) {
        //...
    }

    public void drive(@Max(75) int speedInMph) {
        //...
    }

    @Size(min = 1)
    public List<Passenger> getPassengers() {
        //...
        return Collections.emptyList();
    }
}

 

3.2.2.1. ExecutableValidator#validateParameters()

方法validateParameters()用于验证方法调用的参数。示例3.12,“使用ExecutableValidator#validateParameters()举个例子。验证导致违反@Max对象的参数的约束。drive()方法。

示例3.12:使用ExecutableValidator#validateParameters()
Car object = new Car( "Morris" );
Method method = Car.class.getMethod( "drive", int.class );
Object[] parameterValues = { 80 };
Set<ConstraintViolation<Car>> violations = executableValidator.validateParameters(
        object,
        method,
        parameterValues
);

assertEquals( 1, violations.size() );
Class<? extends Annotation> constraintType = violations.iterator()
        .next()
        .getConstraintDescriptor()
        .getAnnotation()
        .annotationType();
assertEquals( Max.class, constraintType );

 

请注意validateParameters()验证方法的所有参数约束,即对单个参数和交叉参数约束的约束。

3.2.2.2. ExecutableValidator#validateReturnValue()

使用validateReturnValue()可以验证方法的返回值。中的验证示例3.13,“使用ExecutableValidator#validateReturnValue()生成一次违反约束的情况,因为getPassengers()方法至少返回一个Passenger举个例子。

例3.13:使用ExecutableValidator#validateReturnValue()
Car object = new Car( "Morris" );
Method method = Car.class.getMethod( "getPassengers" );
Object returnValue = Collections.<Passenger>emptyList();
Set<ConstraintViolation<Car>> violations = executableValidator.validateReturnValue(
        object,
        method,
        returnValue
);

assertEquals( 1, violations.size() );
Class<? extends Annotation> constraintType = violations.iterator()
        .next()
        .getConstraintDescriptor()
        .getAnnotation()
        .annotationType();
assertEquals( Size.class, constraintType );

 

3.2.2.3. ExecutableValidator#validateConstructorParameters()

构造函数调用的参数可以用validateConstructorParameters()如方法所示示例3.14,“使用ExecutableValidator#validateConstructorParameters()。由于@NotNull对象的约束。manufacturer参数时,验证调用返回一个约束冲突。

例3.14:使用ExecutableValidator#validateConstructorParameters()
Constructor<Car> constructor = Car.class.getConstructor( String.class );
Object[] parameterValues = { null };
Set<ConstraintViolation<Car>> violations = executableValidator.validateConstructorParameters(
        constructor,
        parameterValues
);

assertEquals( 1, violations.size() );
Class<? extends Annotation> constraintType = violations.iterator()
        .next()
        .getConstraintDescriptor()
        .getAnnotation()
        .annotationType();
assertEquals( NotNull.class, constraintType );

 

3.2.2.4. ExecutableValidator#validateConstructorReturnValue()

最后,通过使用validateConstructorReturnValue()可以验证构造函数的返回值。在……里面示例3.15,“使用ExecutableValidator#validateConstructorReturnValue()validateConstructorReturnValue()返回一个违反约束的情况,因为Car构造函数返回的实例不满足@ValidRacingCar约束(未显示)。

示例3.15:使用ExecutableValidator#validateConstructorReturnValue()
//constructor for creating racing cars
Constructor<Car> constructor = Car.class.getConstructor( String.class, String.class );
Car createdObject = new Car( "Morris", null );
Set<ConstraintViolation<Car>> violations = executableValidator.validateConstructorReturnValue(
        constructor,
        createdObject
);

assertEquals( 1, violations.size() );
Class<? extends Annotation> constraintType = violations.iterator()
        .next()
        .getConstraintDescriptor()
        .getAnnotation()
        .annotationType();
assertEquals( ValidRacingCar.class, constraintType );

 

3.2.3. ConstraintViolation方法验证方法

中引入的方法之外,第2.2.3节,“ConstraintViolationConstraintViolation提供另外两个特定于可执行参数和返回值验证的方法。

ConstraintViolation#getExecutableParameters()在方法或构造函数参数验证的情况下返回已验证的参数数组,而ConstraintViolation#getExecutableReturnValue()在返回值验证的情况下提供对已验证对象的访问。

所有其他ConstraintViolation方法通常以与bean验证相同的方式进行方法验证。参考JavaDoc要了解更多有关单个方法的行为及其在bean和方法验证过程中返回值的信息,请执行以下操作。

请注意getPropertyPath()可以非常有用,以便获得有关已验证的参数或返回值的详细信息,例如用于日志记录。特别是,您可以从路径节点检索相关方法的名称和参数类型以及相关参数的索引。如何做到这一点,如示例3.16,“检索方法和参数信息”.

示例3.16:检索方法和参数信息
Car object = new Car( "Morris" );
Method method = Car.class.getMethod( "drive", int.class );
Object[] parameterValues = { 80 };
Set<ConstraintViolation<Car>> violations = executableValidator.validateParameters(
        object,
        method,
        parameterValues
);

assertEquals( 1, violations.size() );
Iterator<Node> propertyPath = violations.iterator()
        .next()
        .getPropertyPath()
        .iterator();

MethodNode methodNode = propertyPath.next().as( MethodNode.class );
assertEquals( "drive", methodNode.getName() );
assertEquals( Arrays.<Class<?>>asList( int.class ), methodNode.getParameterTypes() );

ParameterNode parameterNode = propertyPath.next().as( ParameterNode.class );
assertEquals( "speedInMph", parameterNode.getName() );
assertEquals( 0, parameterNode.getParameterIndex() );

 

参数名使用当前ParameterNameProvider(见第9.2.4节,“ParameterNameProvider).

3.3.内建方法约束

中讨论的内置bean和属性级约束之外,第2.3节,“内置约束”,Hibernate Validator当前提供了一个方法级别的约束,@ParameterScriptAssert。这是一个通用的跨参数约束,允许使用任何与jsr 223兼容的验证例程(“Java脚本编写”)。TM)脚本语言,为这种语言提供了一个引擎,可以在类路径上使用。

若要从表达式中引用可执行文件的参数,请使用从活动参数名称提供程序获得的名称(请参见第9.2.4节,“ParameterNameProvider). 示例3.17,“使用@ParameterScriptAssert的验证逻辑。@LuggageCountMatchesPassengerCount来自示例3.2,“声明跨参数约束”可以在@ParameterScriptAssert.

示例3.17:使用@ParameterScriptAssert
package org.hibernate.validator.referenceguide.chapter03.parameterscriptassert;

public class Car {

    @ParameterScriptAssert(lang = "javascript", script = "luggage.size() <= passengers.size() * 2")
    public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
        //...
    }
}

 

4.内插约束错误消息

消息内插是为违反的JakartaBean验证约束创建错误消息的过程。在本章中,您将了解如何定义和解析此类消息,以及如何在默认算法不足以满足您的需求时插入自定义消息内插器。

4.1.默认消息插值

约束冲突消息从所谓的消息描述符中检索。每个约束使用Message属性定义其默认消息描述符。在声明时,可以用一个特定的值覆盖默认描述符,如示例4.1,“使用Message属性指定消息描述符”.

示例4.1:使用Message属性指定消息描述符
package org.hibernate.validator.referenceguide.chapter04;

public class Car {

    @NotNull(message = "The manufacturer name must not be null")
    private String manufacturer;

    //constructor, getters and setters ...
}

 

如果违反约束,则其描述符将由验证引擎使用当前配置的MessageInterpolator。然后,可以通过以下方式从结果的约束冲突中检索内插错误消息:ConstraintViolation#getMessage().

消息描述符可以包含消息参数以及消息表达式这将在插值过程中得到解决。中包含的字符串文本。{},而消息表达式是包含在${}。在方法插值过程中应用了以下算法:

  1. 通过将消息参数用作资源包的键来解析任何消息参数验证信息。如果此包包含给定消息参数的条目,则该参数将在消息中替换为来自包的相应值。如果替换的值再次包含消息参数,则将递归执行此步骤。资源包将由应用程序开发人员提供,例如通过添加名为ValidationMessages.properties到类路径。还可以通过提供此包的特定区域设置变体来创建本地化错误消息,如ValidationMessages_en_US.properties。默认情况下,JVM的默认区域设置(Locale#getDefault())将在查找包中的消息时使用。

  2. 通过使用它们作为资源包的关键,解析任何消息参数,该包包含内置约束的标准错误消息,如JakartaBean验证规范附录B中定义的那样。在Hibernate Validator的情况下,这个包名为org.hibernate.validator.ValidationMessages。如果此步骤触发替换,则再次执行步骤1,否则将应用步骤3。

  3. 通过将任何消息参数替换为同名的约束注释成员的值来解析任何消息参数。这允许引用约束的属性值(例如,Size#min())在错误消息中(例如,“必须至少是${min}”)。

  4. 通过将任何消息表达式计算为统一表达式语言的表达式来解析它们。看见第4.1.2节,“带有消息表达式的插值”了解有关在错误消息中使用统一EL的更多信息。

 

您可以在节中找到插值算法的形式定义。6.3.1.1雅加达Bean验证规范。

4.1.1.特殊字符

因为人物{}$在消息描述符中有一个特殊的含义,如果要按字面意思使用它们,就需要对它们进行转义。适用下列规则:

  • \{被认为是文字。{

  • \}被认为是文字。}

  • \$被认为是文字。$

  • \\被认为是文字。\

4.1.2.带消息表达式的插值

从Hibernate Validator 5(Bean验证1.1)开始,就可以使用雅加达表达式语言在违反约束的消息中。这允许基于条件逻辑定义错误消息,并启用高级格式选项。验证引擎使下列对象在EL上下文中可用:

  • 映射到属性名称的约束的属性值。

  • 当前验证的值(属性、bean、方法参数等)以这个名字验证值

  • 映射到暴露var-arg方法的名称格式化程序的bean。format(String format, Object…​ args)它的行为就像java.util.Formatter.format(String format, Object…​ args).

表达式语言非常灵活,Hibernate Validator提供了几个功能级别,您可以使用这些功能级别通过ExpressionLanguageFeatureLevel枚举:

  • NONE表达式语言内插完全禁用。

  • VARIABLES*允许通过addExpressionVariable()的资源包和使用formatter对象。

  • BEAN_PROPERTIES*允许一切VARIABLES允许加上bean属性的插值。

  • BEAN_METHODS*还允许执行bean方法。可以认为硬编码约束消息是安全的,但对于海关违规行为需要额外照顾的地方。

约束消息的默认功能级别为BEAN_PROPERTIES.

时,可以定义表达式语言功能级别。引导ValidatorFactory.

以下部分提供了在错误消息中使用EL表达式的几个示例。

4.1.3.实例

例4.2,“指定消息描述符”演示如何使用不同的选项指定消息描述符。

示例4.2:指定消息描述符
package org.hibernate.validator.referenceguide.chapter04.complete;

public class Car {

    @NotNull
    private String manufacturer;

    @Size(
            min = 2,
            max = 14,
            message = "The license plate '${validatedValue}' must be between {min} and {max} characters long"
    )
    private String licensePlate;

    @Min(
            value = 2,
            message = "There must be at least {value} seat${value > 1 ? 's' : ''}"
    )
    private int seatCount;

    @DecimalMax(
            value = "350",
            message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher " +
                    "than {value}"
    )
    private double topSpeed;

    @DecimalMax(value = "100000", message = "Price must not be higher than ${value}")
    private BigDecimal price;

    public Car(
            String manufacturer,
            String licensePlate,
            int seatCount,
            double topSpeed,
            BigDecimal price) {
        this.manufacturer = manufacturer;
        this.licensePlate = licensePlate;
        this.seatCount = seatCount;
        this.topSpeed = topSpeed;
        this.price = price;
    }

    //getters and setters ...
}

 

验证无效Car实例中的断言所显示的消息的约束冲突。例4.3,“预期错误消息”:

  • 这个@NotNull对象的约束。manufacturer字段导致错误消息“不能为空”,因为这是由JakartaBean验证规范定义的默认消息,并且在Message属性中没有给出特定的描述符。

  • 这个@Size对象的约束。licensePlate字段显示消息参数的插值({min}{max}),以及如何使用EL表达式将已验证的值添加到错误消息中。${validatedValue}

  • 这个@Min对.的约束seatCount演示如何使用带有三元表达式的EL表达式动态选择单数或复数形式,具体取决于约束的属性(“必须至少有一个座位”)。“必须有至少两个席位”)

  • 的消息。@DecimalMax对.的约束topSpeed演示如何使用格式化程序实例格式化已验证的值。

  • 最后,@DecimalMax对.的约束price显示参数内插优先于表达式计算,从而导致$在最高价格前签字

 

只有实际的约束属性才能使用表单中的消息参数进行内插。{attributeName}。当引用已验证的值或自定义表达式变量时,添加到内插上下文中(请参见第12.13.1条,“HibernateConstraintValidatorContext),形式中的EL表达式。${attributeName}一定要用。

例4.3:预期错误消息
Car car = new Car( null, "A", 1, 400.123456, BigDecimal.valueOf( 200000 ) );

String message = validator.validateProperty( car, "manufacturer" )
        .iterator()
        .next()
        .getMessage();
assertEquals( "must not be null", message );

message = validator.validateProperty( car, "licensePlate" )
        .iterator()
        .next()
        .getMessage();
assertEquals(
        "The license plate 'A' must be between 2 and 14 characters long",
        message
);

message = validator.validateProperty( car, "seatCount" ).iterator().next().getMessage();
assertEquals( "There must be at least 2 seats", message );

message = validator.validateProperty( car, "topSpeed" ).iterator().next().getMessage();
assertEquals( "The top speed 400.12 is higher than 350", message );

message = validator.validateProperty( car, "price" ).iterator().next().getMessage();
assertEquals( "Price must not be higher than $100000", message );

 

4.2.自定义消息插值

如果默认的消息插值算法不符合您的要求,也可以插入自定义。MessageInterpolator执行。

自定义内插器必须实现接口。jakarta.validation.MessageInterpolator。注意,实现必须是线程安全的。建议自定义消息内插器将最终实现委托给默认的内插器,该实现可以通过Configuration#getDefaultMessageInterpolator().

为了使用自定义消息内插器,必须通过在JakartaBean验证xml描述符中配置它来注册它。Meta-INF/validation.xml(见第8.1节,“在Validation.xml),或者在引导ValidatorFactoryValidator(见第9.2.1节,“MessageInterpolator第9.3节,“配置一个Validator”分别)。

4.2.1. ResourceBundleLocator

在某些用例中,您希望使用Bean验证规范定义的消息内插算法,但是要从其他资源包中检索错误消息,而不是验证信息。在这种情况下,Hibernate Validator的ResourceBundleLocatorSPI能帮上忙。

Hibernate Validator中的默认消息内插器,ResourceBundleMessageInterpolator,将资源包的检索委托给该SPI。使用另一个包只需要传递PlatformResourceBundleLocator在引导时使用包名。ValidatorFactory如图所示示例4.4,“使用特定的资源包”.

示例4.4:使用特定的资源包
Validator validator = Validation.byDefaultProvider()
        .configure()
        .messageInterpolator(
                new ResourceBundleMessageInterpolator(
                        new PlatformResourceBundleLocator( "MyMessages" )
                )
        )
        .buildValidatorFactory()
        .getValidator();

 

当然,您也可以实现一个完全不同的ResourceBundleLocator,例如,它返回由数据库中的记录支持的包。在这种情况下,您可以通过HibernateValidatorConfiguration#getDefaultResourceBundleLocator(),例如,您可以使用它作为自定义定位器的后退。

除了PlatformResourceBundleLocator,Hibernate Validator提供了另一个资源包定位器实现,即AggregateResourceBundleLocator,它允许从多个资源包检索错误消息。例如,您可以在多模块应用程序中使用此实现,在该应用程序中,每个模块需要一个消息包。例4.5,“使用AggregateResourceBundleLocator演示如何使用AggregateResourceBundleLocator.

例4.5:使用AggregateResourceBundleLocator
Validator validator = Validation.byDefaultProvider()
        .configure()
        .messageInterpolator(
                new ResourceBundleMessageInterpolator(
                        new AggregateResourceBundleLocator(
                                Arrays.asList(
                                        "MyMessages",
                                        "MyOtherMessages"
                                )
                        )
                )
        )
        .buildValidatorFactory()
        .getValidator();

 

注意,这些包是按照传递给构造函数的顺序处理的。这意味着,如果多个包包含一个给定消息键的条目,则该值将从包含该键的列表中的第一个包中获取。

5.分组限制

的所有验证方法ValidatorExecutableValidator前面几章也讨论了var-arg参数。groups。到目前为止,我们一直忽略这个参数,但现在是时候仔细看看了。

5.1.请求组

组允许您限制在验证期间应用的约束集。验证组的一个用例是UI向导,在每个步骤中,只有指定的约束子集应该得到验证。目标组作为var-arg参数传递给适当的验证方法。

让我们看看一个例子。全班Person在……里面示例5.1,“示例类”Person有一个@NotNull对.的约束name。由于没有为此注释指定组,所以默认组jakarta.validation.groups.Default都是假设的。

 

当请求一个以上的组时,计算组的顺序不是确定性的。如果未指定组,则为默认组。jakarta.validation.groups.Default都是假设的。

示例5.1:示例类Person
package org.hibernate.validator.referenceguide.chapter05;

public class Person {

    @NotNull
    private String name;

    public Person(String name) {
        this.name = name;
    }

    // getters and setters ...
}

 

全班Driver在……里面例5.2,“司机”延展Person并添加属性agehasDrivingLicense。司机必须年满18岁(@Min(18))并持有驾驶执照(@AssertTrue)。在这些属性上定义的两个约束都属于组。DriverChecks这只是一个简单的标签界面。

 

使用接口可以使组的使用是安全的,并且可以方便地重构。这也意味着组可以通过类继承彼此继承。看见第5.2节,“群体继承”.

实例5.2:驱动程序
package org.hibernate.validator.referenceguide.chapter05;

public class Driver extends Person {

    @Min(
            value = 18,
            message = "You have to be 18 to drive a car",
            groups = DriverChecks.class
    )
    public int age;

    @AssertTrue(
            message = "You first have to pass the driving test",
            groups = DriverChecks.class
    )
    public boolean hasDrivingLicense;

    public Driver(String name) {
        super( name );
    }

    public void passedDrivingTest(boolean b) {
        hasDrivingLicense = b;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
package org.hibernate.validator.referenceguide.chapter05;

public interface DriverChecks {
}

 

最后一堂课Car (例5.3,“汽车”)具有一些约束,这些约束既是默认组的一部分,也是@AssertTrue在小组中CarChecks财产论passedVehicleInspection这表明一辆车是否通过了路况测试。

实例5.3:CAR
package org.hibernate.validator.referenceguide.chapter05;

public class Car {
    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    @AssertTrue(
            message = "The car has to pass the vehicle inspection first",
            groups = CarChecks.class
    )
    private boolean passedVehicleInspection;

    @Valid
    private Driver driver;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    public boolean isPassedVehicleInspection() {
        return passedVehicleInspection;
    }

    public void setPassedVehicleInspection(boolean passedVehicleInspection) {
        this.passedVehicleInspection = passedVehicleInspection;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }

    // getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter05;

public interface CarChecks {
}

 

总的来说,在示例中使用了三个不同的组:

  • 对.的限制Person.nameCar.manufacturerCar.licensePlateCar.seatCount都属于Default

  • 对.的限制Driver.ageDriver.hasDrivingLicense属于DriverChecks

  • 对.的约束Car.passedVehicleInspection属于集团CarChecks

示例5.4,“使用验证组”显示如何将不同的组组合传递到Validator#validate()方法得到不同的验证结果。

示例5.4:使用验证组
// create a car and check that everything is ok with it.
Car car = new Car( "Morris", "DD-AB-123", 2 );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 0, constraintViolations.size() );

// but has it passed the vehicle inspection?
constraintViolations = validator.validate( car, CarChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
        "The car has to pass the vehicle inspection first",
        constraintViolations.iterator().next().getMessage()
);

// let's go to the vehicle inspection
car.setPassedVehicleInspection( true );
assertEquals( 0, validator.validate( car, CarChecks.class ).size() );

// now let's add a driver. He is 18, but has not passed the driving test yet
Driver john = new Driver( "John Doe" );
john.setAge( 18 );
car.setDriver( john );
constraintViolations = validator.validate( car, DriverChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
        "You first have to pass the driving test",
        constraintViolations.iterator().next().getMessage()
);

// ok, John passes the test
john.passedDrivingTest( true );
assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );

// just checking that everything is in order now
assertEquals(
        0, validator.validate(
        car,
        Default.class,
        CarChecks.class,
        DriverChecks.class
).size()
);

 

第一validate()叫进来示例5.4,“使用验证组”不使用显式组完成。没有验证错误,即使属性passedVehicleInspection是每一个缺省值。false因为在此属性上定义的约束不属于默认组。

使用CarChecks在汽车通过车辆检查之前,小组会失败。向汽车中添加一个司机并验证DriverChecks同样,由于驾驶员尚未通过驾驶考试,因此产生了一次违反约束的情况。只在设置之后passedDrivingTesttrue对.的验证DriverChecks传球。

最后一次validate()Call最后显示,所有约束都是通过对所有定义的组进行验证来传递的。

5.2.群体继承

在……里面示例5.4,“使用验证组”,我们需要打电话validate()对于每个验证组,或逐一指定所有验证组。

在某些情况下,您可能希望定义一组约束,其中包括另一组约束。您可以使用组继承来实现这一点。

在……里面例5.5,“超级跑车”,我们定义SuperCar和一群人RaceCarChecks扩展Default小组。一个SuperCar必须有安全带才能在比赛中运行。

例5.5:超级跑车
package org.hibernate.validator.referenceguide.chapter05.groupinheritance;

public class SuperCar extends Car {

    @AssertTrue(
            message = "Race car must have a safety belt",
            groups = RaceCarChecks.class
    )
    private boolean safetyBelt;

    // getters and setters ...

}
package org.hibernate.validator.referenceguide.chapter05.groupinheritance;

import jakarta.validation.groups.Default;

public interface RaceCarChecks extends Default {
}

 

在下面的示例中,我们将检查SuperCar只有一个座位,没有安全带是一辆有效的汽车,如果它是一辆有效的赛车。

示例5.6:使用组继承
// create a supercar and check that it's valid as a generic Car
SuperCar superCar = new SuperCar( "Morris", "DD-AB-123", 1  );
assertEquals( "must be greater than or equal to 2", validator.validate( superCar ).iterator().next().getMessage() );

// check that this supercar is valid as generic car and also as race car
Set<ConstraintViolation<SuperCar>> constraintViolations = validator.validate( superCar, RaceCarChecks.class );

assertThat( constraintViolations ).extracting( "message" ).containsOnly(
        "Race car must have a safety belt",
        "must be greater than or equal to 2"
);

 

在第一次打电话到validate(),我们没有指定一个组。有一个验证错误,因为汽车必须至少有一个座位。它是来自Default小组。

在第二个调用中,我们只指定组。RaceCarChecks。有两个验证错误:一个是关于Default小组,另一个关于没有安全带从RaceCarChecks小组。

5.3.定义组序列

默认情况下,不按特定顺序计算约束,而不管它们属于哪个组。然而,在某些情况下,控制计算约束的顺序是有用的。

在示例中,来自示例5.4,“使用验证组”例如,可以要求首先通过所有默认的汽车约束,然后再检查汽车的适配性。最后,在开车之前,应该检查实际的司机约束。

为了实现这样的验证顺序,只需定义一个接口并用@GroupSequence,定义必须对组进行验证的顺序(请参见例5.7,“定义组序列”)。如果序列组中至少有一个约束失败,则序列中下列组的约束都不会得到验证。

例5.7:定义组序列
package org.hibernate.validator.referenceguide.chapter05;

import jakarta.validation.GroupSequence;
import jakarta.validation.groups.Default;

@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })
public interface OrderedChecks {
}

 

 

定义序列的组和组成序列的组不能直接或间接地涉及循环依赖关系,无论是通过级联序列定义还是通过组继承。如果计算包含这种循环性的组,则GroupDefinitionException已经长大了。

然后,您可以使用新的序列,如例5.8,“使用组序列”.

示例5.8:使用组序列
Car car = new Car( "Morris", "DD-AB-123", 2 );
car.setPassedVehicleInspection( true );

Driver john = new Driver( "John Doe" );
john.setAge( 18 );
john.passedDrivingTest( true );
car.setDriver( john );

assertEquals( 0, validator.validate( car, OrderedChecks.class ).size() );

 

5.4.重新定义默认组序列

5.4.1. @GroupSequence

除了定义组序列之外,@GroupSequence注释还允许重新定义给定类的默认组。为此,只需添加@GroupSequence对类进行注释,并指定替换的组序列。Default对于注释中的该类。

例5.9,“类”RentalCar使用重新定义的默认组“介绍了一个新的类RentalCar使用重新定义的默认组。

例5.9:类RentalCar具有重新定义的默认组
package org.hibernate.validator.referenceguide.chapter05;

@GroupSequence({ RentalChecks.class, CarChecks.class, RentalCar.class })
public class RentalCar extends Car {
    @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)
    private boolean rented;

    public RentalCar(String manufacturer, String licencePlate, int seatCount) {
        super( manufacturer, licencePlate, seatCount );
    }

    public boolean isRented() {
        return rented;
    }

    public void setRented(boolean rented) {
        this.rented = rented;
    }
}
package org.hibernate.validator.referenceguide.chapter05;

public interface RentalChecks {
}

 

使用此定义,您可以计算属于RentalChecksCarChecksRentalCar只是请求Default如图所示的组示例5.10,“使用重新定义的默认组验证对象”.

示例5.10:使用重新定义的默认组验证对象
RentalCar rentalCar = new RentalCar( "Morris", "DD-AB-123", 2 );
rentalCar.setPassedVehicleInspection( true );
rentalCar.setRented( true );

Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validate( rentalCar );

assertEquals( 1, constraintViolations.size() );
assertEquals(
        "Wrong message",
        "The car is currently rented out",
        constraintViolations.iterator().next().getMessage()
);

rentalCar.setRented( false );
constraintViolations = validator.validate( rentalCar );

assertEquals( 0, constraintViolations.size() );

 

 

由于组和组序列定义中不存在循环依赖关系,所以不能仅仅添加Default对序列进行重新定义Default去上课。相反,必须添加类本身。

这个Default组序列重写是定义在其上的类的本地对象,而不是传播到关联对象。例如,这意味着添加DriverChecks的默认组序列RentalCar不会有任何影响。只有团体Default将被传播到驱动程序关联。

请注意,您可以通过声明组转换规则来控制传播的组(请参阅第5.5节,“组转换”).

5.4.2. @GroupSequenceProvider

除了静态地重新定义默认的组序列之外,@GroupSequence,Hibernate Validator还提供了一个SPI,用于根据对象状态动态地重新定义默认组序列。

为此,您需要实现接口。DefaultGroupSequenceProvider并通过@GroupSequenceProvider注释例如,在租车场景中,可以动态添加CarChecks如图所示示例5.11,“实现和使用默认组序列提供程序”.

示例5.11:实现和使用默认组序列提供程序
package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;

public class RentalCarGroupSequenceProvider
        implements DefaultGroupSequenceProvider<RentalCar> {

    @Override
    public List<Class<?>> getValidationGroups(RentalCar car) {
        List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
        defaultGroupSequence.add( RentalCar.class );

        if ( car != null && !car.isRented() ) {
            defaultGroupSequence.add( CarChecks.class );
        }

        return defaultGroupSequence;
    }
}
package org.hibernate.validator.referenceguide.chapter05.groupsequenceprovider;

@GroupSequenceProvider(RentalCarGroupSequenceProvider.class)
public class RentalCar extends Car {

    @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)
    private boolean rented;

    public RentalCar(String manufacturer, String licencePlate, int seatCount) {
        super( manufacturer, licencePlate, seatCount );
    }

    public boolean isRented() {
        return rented;
    }

    public void setRented(boolean rented) {
        this.rented = rented;
    }
}

 

5.5.组转换

如果您想验证与汽车相关的检查以及司机检查,怎么办?当然,您可以显式地将所需的组传递给验证调用,但是如果希望将这些验证作为Default小组验证?这里@ConvertGroup它允许您在级联验证期间使用与最初请求的组不同的组。

让我们看看例5.12,“@ConvertGroup使用“。这里@GroupSequence({ CarChecks.class, Car.class })控件下的与汽车相关的约束组合使用。Default组(见第5.4节,“重新定义默认组序列”)。还有一个@ConvertGroup(from = Default.class, to = DriverChecks.class)确保Default组转换为DriverChecks组在驱动程序关联的级联验证期间。

例5.12:@ConvertGroup使用
package org.hibernate.validator.referenceguide.chapter05.groupconversion;

public class Driver {

    @NotNull
    private String name;

    @Min(
            value = 18,
            message = "You have to be 18 to drive a car",
            groups = DriverChecks.class
    )
    public int age;

    @AssertTrue(
            message = "You first have to pass the driving test",
            groups = DriverChecks.class
    )
    public boolean hasDrivingLicense;

    public Driver(String name) {
        this.name = name;
    }

    public void passedDrivingTest(boolean b) {
        hasDrivingLicense = b;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    // getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter05.groupconversion;

@GroupSequence({ CarChecks.class, Car.class })
public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    @AssertTrue(
            message = "The car has to pass the vehicle inspection first",
            groups = CarChecks.class
    )
    private boolean passedVehicleInspection;

    @Valid
    @ConvertGroup(from = Default.class, to = DriverChecks.class)
    private Driver driver;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    public boolean isPassedVehicleInspection() {
        return passedVehicleInspection;
    }

    public void setPassedVehicleInspection(boolean passedVehicleInspection) {
        this.passedVehicleInspection = passedVehicleInspection;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver(Driver driver) {
        this.driver = driver;
    }

    // getters and setters ...
}

 

中的验证结果例5.13,“测试用例”@ConvertGroup成功,即使对hasDrivingLicense属于DriverChecks组,并且只有Default组请求在validate()打电话。

例5.13:测试用例@ConvertGroup
// create a car and validate. The Driver is still null and does not get validated
Car car = new Car( "VW", "USD-123", 4 );
car.setPassedVehicleInspection( true );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );
assertEquals( 0, constraintViolations.size() );

// create a driver who has not passed the driving test
Driver john = new Driver( "John Doe" );
john.setAge( 18 );

// now let's add a driver to the car
car.setDriver( john );
constraintViolations = validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
        "The driver constraint should also be validated as part of the default group",
        constraintViolations.iterator().next().getMessage(),
        "You first have to pass the driving test"
);

 

您可以在任何地方定义组转换。@Valid可以使用,即关联以及方法和构造函数参数以及返回值。可以使用@ConvertGroup.List.

然而,适用下列限制:

  • @ConvertGroup必须仅与@Valid。如果不使用,则为ConstraintDeclarationException被扔了。

  • 在同一元素上设置多个转换规则与值相同是不合法的。在这种情况下,ConstraintDeclarationException已经长大了。

  • 这个from属性不能引用组序列。一个ConstraintDeclarationException就是在这种情况下提出的。

 

规则不是递归执行的。使用第一个匹配转换规则,忽略后续规则。例如,如果一组@ConvertGroup声明链组ABBC、团体A将被转换为B而不是C.

6.创建自定义约束

JakartaBean验证API定义了一整套标准约束注释,如@NotNull@Size在这些内置约束不够的情况下,您可以很容易地根据特定的验证需求创建定制约束。

6.1.创建一个简单的约束

要创建自定义约束,需要执行以下三个步骤:

  • 创建约束注释

  • 实现验证器

  • 定义默认错误消息

6.1.1.约束注释

本节演示如何编写约束注释,该注释可用于确保给定字符串完全大写或小写。稍后,此约束将应用于licensePlate字段Car来自第一章,开始以确保字段始终是大写字符串。

首先需要的是一种表达这两种情况模式的方法。而你可以用String常量,更好的方法是为此目的使用枚举:

例6.1:EnumCaseMode表示大小写
package org.hibernate.validator.referenceguide.chapter06;

public enum CaseMode {
    UPPER,
    LOWER;
}

 

下一步是定义实际的约束注释。如果您以前从未设计过注释,这看起来可能有点吓人,但实际上并不难:

示例6.2:定义@CheckCase约束注释
package org.hibernate.validator.referenceguide.chapter06;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(List.class)
public @interface CheckCase {

    String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." +
            "message}";

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

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

    CaseMode value();

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CheckCase[] value();
    }
}

 

注释类型使用@interface关键词。注释类型的所有属性都以类似方法的方式声明。JakartaBean验证API的规范要求,任何约束注释都定义:

  • 属性message返回用于在违反约束时创建错误消息的默认键。

  • 属性groups,它允许指定此约束所属的验证组(请参阅第五章,分组约束)。这必须默认为类型为Class<?>的空数组。

  • 属性payload可以由JakartaBean验证API的客户端使用,以便将自定义有效负载对象分配给约束。API本身不使用此属性。自定义有效负载的一个示例可以是严重性的定义:

    public class Severity {
        public interface Info extends Payload {
        }
    
        public interface Error extends Payload {
        }
    }
    public class ContactDetails {
        @NotNull(message = "Name is mandatory", payload = Severity.Error.class)
        private String name;
    
        @NotNull(message = "Phone number not specified, but not mandatory",
                payload = Severity.Info.class)
        private String phoneNumber;
    
        // ...
    }

     

    现在,客户端可以在验证ContactDetails实例访问约束的严重性。ConstraintViolation.getConstraintDescriptor().getPayload()并根据严重程度调整其行为。

除了这三个强制性属性,还有一个,value,允许指定所需的案例模式。名字value是一个特殊的属性,如果它是指定的唯一属性,则在使用注释时可以省略,例如@CheckCase(CaseMode.UPPER).

此外,约束注释使用几个元注释进行修饰:

  • @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})::为约束定义受支持的目标元素类型。@CheckCase可用于字段(元素类型)。FIELD),JavaBeans属性以及方法返回值(METHOD)、方法/构造函数参数(PARAMETER)和参数化类型的类型参数(TYPE_USE)。元素类型ANNOTATION_TYPE允许创建组合约束(请参见第6.4节,“约束组合”)基于@CheckCase.

    创建类级约束时(请参阅第2.1.4节,“类级约束”),元素类型TYPE必须使用。针对构造函数返回值的约束需要支持元素类型。CONSTRUCTOR。跨参数约束(请参见第6.3节,“跨参数约束”)用于一起验证方法或构造函数的所有参数,必须支持METHODCONSTRUCTOR分别。

  • @Retention(RUNTIME)::指定此类型的注释将通过反射方式在运行时可用。

  • @Constraint(validatedBy = CheckCaseValidator.class)::将注释类型标记为约束注释,并指定用于验证带注释的元素的验证器。@CheckCase。如果可以对多个数据类型使用约束,则可以指定多个验证器,每个数据类型一个。

  • @Documented*说,使用@CheckCase将包含在带有注释的元素的JavaDoc中。

  • @Repeatable(List.class)*指示注释可以在同一位置重复多次,通常使用不同的配置。List包含注释类型。

此包含的注释类型命名为List也在示例中显示。它允许指定几个@CheckCase对同一元素进行注释,例如使用不同的验证组和消息。虽然可以使用另一个名称,但JakartaBean验证规范建议使用该名称List并将注释作为对应约束类型的内部注释。

6.1.2.约束验证器

定义了注释之后,您需要创建一个约束验证器,它可以使用@CheckCase注释为此,实现JakartaBean验证接口ConstraintValidator如下所示:

示例6.3:为约束实现约束验证器@CheckCase
package org.hibernate.validator.referenceguide.chapter06;

public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }

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

        if ( caseMode == CaseMode.UPPER ) {
            return object.equals( object.toUpperCase() );
        }
        else {
            return object.equals( object.toLowerCase() );
        }
    }
}

 

这个ConstraintValidator接口定义在实现中设置的两个类型参数。第一个指定要验证的注释类型(CheckCase),第二个类型是验证器可以处理的元素类型(String)。如果约束支持多个数据类型,则ConstraintValidator对于每个允许的类型,必须在约束注释中实现和注册,如上面所示。

验证器的实现非常简单。这个initialize()方法允许您访问已验证约束的属性值,并允许您将它们存储在验证器的字段中,如示例所示。

这个isValid()方法包含实际的验证逻辑。为@CheckCase这是检查给定字符串是完全小写还是大写,取决于检索到的CASE模式。initialize()。注意,JakartaBean验证规范建议将空值视为有效值。如果null不是元素的有效值,应该用@NotNull明文规定。

6.1.2.1.这个ConstraintValidatorContext

示例6.3,“为约束实现约束验证器@CheckCase依赖于默认的错误消息生成,只需返回truefalseisValid()方法。使用传递ConstraintValidatorContext对象时,可以添加其他错误消息或完全禁用默认错误消息生成,并仅定义自定义错误消息。这个ConstraintValidatorContextAPI被建模为FLUENT接口,最好用一个示例演示:

例6.4:使用ConstraintValidatorContext定义自定义错误消息
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorcontext;

public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }

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

        boolean isValid;
        if ( caseMode == CaseMode.UPPER ) {
            isValid = object.equals( object.toUpperCase() );
        }
        else {
            isValid = object.equals( object.toLowerCase() );
        }

        if ( !isValid ) {
            constraintContext.disableDefaultConstraintViolation();
            constraintContext.buildConstraintViolationWithTemplate(
                    "{org.hibernate.validator.referenceguide.chapter06." +
                    "constraintvalidatorcontext.CheckCase.message}"
            )
            .addConstraintViolation();
        }

        return isValid;
    }
}

 

例6.4,“使用ConstraintValidatorContext若要定义自定义错误消息,请执行以下操作演示如何禁用默认错误消息生成,并使用指定的邮件模板添加自定义错误消息。在本例中,使用ConstraintValidatorContext结果将产生与默认错误消息生成相同的错误消息。

 

通过调用addConstraintViolation()。只有在此之后,才会创建新的约束冲突。

中创建的自定义冲突,默认情况下不启用表达式语言。ConstraintValidatorContext.

然而,对于一些高级需求,使用表达式语言可能是必要的。

在这种情况下,您需要打开HibernateConstraintValidatorContext并显式启用表达式语言。看见第12.13.1条,“HibernateConstraintValidatorContext想了解更多信息。

请参阅第6.2.1节,“自定义属性路径”学习如何使用ConstraintValidatorContext用于控制类级约束违反约束的属性路径的API。

6.1.2.2.这个HibernateConstraintValidator延拓

Hibernate Validator提供了对ConstraintValidator合同:HibernateConstraintValidator.

此扩展的目的是向initialize()方法,在当前ConstraintValidator契约,只有注释作为参数传递。

这个initialize()方法HibernateConstraintValidator需要两个参数:

  • 这个ConstraintDescriptor手边的约束。您可以使用ConstraintDescriptor#getAnnotation().

  • 这个HibernateConstraintValidatorInitializationContext它提供有用的帮助和上下文信息,例如时钟提供程序或时间验证容忍度。

此扩展被标记为孵化器,因此可能会发生更改。其计划是将其标准化,并在将来将其纳入JakartaBean验证中。

下面的示例演示如何将验证器建立在HibernateConstraintValidator:

示例6.5:使用HibernateConstraintValidator合同
package org.hibernate.validator.referenceguide.chapter06;

public class MyFutureValidator implements HibernateConstraintValidator<MyFuture, Instant> {

    private Clock clock;

    private boolean orPresent;

    @Override
    public void initialize(ConstraintDescriptor<MyFuture> constraintDescriptor,
            HibernateConstraintValidatorInitializationContext initializationContext) {
        this.orPresent = constraintDescriptor.getAnnotation().orPresent();
        this.clock = initializationContext.getClockProvider().getClock();
    }

    @Override
    public boolean isValid(Instant instant, ConstraintValidatorContext constraintContext) {
        //...

        return false;
    }
}

 

 

您应该只实现其中一个initialize()方法。请注意,初始化验证器时都会调用两者。

6.1.2.3.将有效负载传递给约束验证器

有时,您可能希望在某些外部参数上为约束验证器行为设置条件。

例如,如果每个国家有一个实例,则邮政编码验证程序可能会因应用程序实例的区域设置而有所不同。另一个要求可能是在特定环境上有不同的行为:暂存环境可能无法访问某些外部生产资源,这是验证器正确运行所必需的。

所有这些用例都引入了约束验证器有效负载的概念。它是从Validator通过HibernateConstraintValidatorContext.

下面的示例演示如何在ValidatorFactory初始化。除非重写此默认值,否则所有ValidatorS由此创建的ValidatorFactory将具有此约束验证器有效负载值集。

示例6.6:在ValidatorFactory初始化
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .constraintValidatorPayload( "US" )
        .buildValidatorFactory();

Validator validator = validatorFactory.getValidator();

 

另一个选项是将约束验证器的有效负载设置为每个Validator使用上下文:

示例6.7:使用Validator语境
HibernateValidatorFactory hibernateValidatorFactory = Validation.byDefaultProvider()
        .configure()
        .buildValidatorFactory()
        .unwrap( HibernateValidatorFactory.class );

Validator validator = hibernateValidatorFactory.usingContext()
        .constraintValidatorPayload( "US" )
        .getValidator();

// [...] US specific validation checks

validator = hibernateValidatorFactory.usingContext()
        .constraintValidatorPayload( "FR" )
        .getValidator();

// [...] France specific validation checks

 

一旦设置了约束验证器有效负载,就可以在约束验证器中使用它,如下面的示例所示:

示例6.8:在约束验证器中使用约束验证器有效负载
package org.hibernate.validator.referenceguide.chapter06.constraintvalidatorpayload;

public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> {

    public String countryCode;

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

        boolean isValid = false;

        String countryCode = constraintContext
                .unwrap( HibernateConstraintValidatorContext.class )
                .getConstraintValidatorPayload( String.class );

        if ( "US".equals( countryCode ) ) {
            // checks specific to the United States
        }
        else if ( "FR".equals( countryCode ) ) {
            // checks specific to France
        }
        else {
            // ...
        }

        return isValid;
    }
}

 

HibernateConstraintValidatorContext#getConstraintValidatorPayload()具有类型参数,并且只有在有效负载为给定类型时才返回有效负载。

 

需要注意的是,约束验证器的有效负载不同于可以包含在引发的约束冲突中的动态有效负载。

此约束验证器有效负载的全部用途是为约束验证器的行为设置条件。它不包含在违反约束的行为中,除非有特定的ConstraintValidator实现将有效负载传递给发出的约束冲突,方法是使用约束违反动态有效载荷机制.

6.1.3.错误信息

最后一个缺失的构建块是一个错误消息,应该在@CheckCase违反了约束。要定义这一点,请创建一个文件ValidationMessages.properties包含以下内容(另请参阅4.1节,“默认消息插值”):

示例6.9:为CheckCase约束
org.hibernate.validator.referenceguide.chapter06.CheckCase.message=Case mode must be {value}.

如果发生验证错误,验证运行库将使用为@CheckCase注释来查找此资源包中的错误消息。

6.1.4.使用约束

控件中的约束。Car类的第一章,开始一章,以指定licensePlate字段只应包含大写字符串:

示例6.10:应用@CheckCase约束
package org.hibernate.validator.referenceguide.chapter06;

public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    @CheckCase(CaseMode.UPPER)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    //getters and setters ...
}

 

最后,示例6.11,“使用@CheckCase约束“演示如何验证Car实例中具有无效牌照的@CheckCase将被违反的约束。

示例6.11:使用@CheckCase约束
//invalid license plate
Car car = new Car( "Morris", "dd-ab-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
        validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
        "Case mode must be UPPER.",
        constraintViolations.iterator().next().getMessage()
);

//valid license plate
car = new Car( "Morris", "DD-AB-123", 4 );

constraintViolations = validator.validate( car );

assertEquals( 0, constraintViolations.size() );

 

6.2.类级约束

如前所述,还可以在类级别上应用约束来验证整个对象的状态。类级约束的定义方式与属性约束相同.示例6.12,“实现类级约束”的约束注释和验证器。@ValidPassengerCount中已经使用的约束。例2.9,“类级约束”.

示例6.12:实现类级约束
package org.hibernate.validator.referenceguide.chapter06.classlevel;

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { ValidPassengerCountValidator.class })
@Documented
public @interface ValidPassengerCount {

    String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." +
            "ValidPassengerCount.message}";

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

    Class<? extends Payload>[] payload() default { };
}
package org.hibernate.validator.referenceguide.chapter06.classlevel;

public class ValidPassengerCountValidator
        implements ConstraintValidator<ValidPassengerCount, Car> {

    @Override
    public void initialize(ValidPassengerCount constraintAnnotation) {
    }

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

        return car.getPassengers().size() <= car.getSeatCount();
    }
}

 

如示例所示,您需要使用元素类型。TYPE@Target注释这允许将约束放在类型定义上。示例中约束的验证器接收到CarisValid()方法,并可以访问完整的对象状态,以确定给定实例是否有效。

6.2.1.自定义属性路径

默认情况下,类级约束的约束冲突在带注释的类型级别上报告。Car.

但是,在某些情况下,更可取的做法是,违规行为的属性路径指的是所涉及的属性之一。例如,您可能需要报告@ValidPassengerCount对乘客财产的约束,而不是Car豆子。

示例6.13,“添加一个新的ConstraintViolation使用自定义属性路径“演示如何使用传递给isValid()若要为属性乘客生成与属性节点的自定义约束冲突,请执行以下操作。注意,您还可以添加几个属性节点,指向已验证bean的子实体。

示例6.13:添加一个新的ConstraintViolation具有自定义属性路径
package org.hibernate.validator.referenceguide.chapter06.custompath;

public class ValidPassengerCountValidator
        implements ConstraintValidator<ValidPassengerCount, Car> {

    @Override
    public void initialize(ValidPassengerCount constraintAnnotation) {
    }

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

        boolean isValid = car.getPassengers().size() <= car.getSeatCount();

        if ( !isValid ) {
            constraintValidatorContext.disableDefaultConstraintViolation();
            constraintValidatorContext
                    .buildConstraintViolationWithTemplate( "{my.custom.template}" )
                    .addPropertyNode( "passengers" ).addConstraintViolation();
        }

        return isValid;
    }
}

 

6.3.交叉参数约束

JakartaBean验证区分了两种不同类型的约束。

泛型约束(到目前为止已经讨论过)适用于带注释的元素,例如类型、字段、容器元素、方法参数或返回值等。相反,跨参数约束适用于方法或构造函数的参数数组,可用于表示依赖于多个参数值的验证逻辑。

为了定义跨参数约束,必须用@SupportedValidationTarget(ValidationTarget.PARAMETERS)。类型参数TConstraintValidator接口必须解析为ObjectObject[]中的方法/构造函数参数数组。isValid()方法。

下面的示例显示了跨参数约束的定义,该约束可用于检查两个Date方法的参数按正确的顺序排列:

示例6.14:交叉参数约束
package org.hibernate.validator.referenceguide.chapter06.crossparameter;

@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {

    String message() default "{org.hibernate.validator.referenceguide.chapter04." +
            "crossparameter.ConsistentDateParameters.message}";

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

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

 

跨参数约束的定义与定义泛型约束没有任何不同,即它必须指定成员。message()groups()payload()并以@Constraint。此元注释还指定了相应的验证器,如示例6.15,“泛型和跨参数约束”。注意,除了元素类型METHODCONSTRUCTORANNOTATION_TYPE被指定为注释的目标,以便能够创建基于@ConsistentDateParameters(见第6.4节,“约束组合”).

 

跨参数约束直接在方法或构造函数的声明中指定,返回值约束的情况也是如此。因此,为了提高代码的可读性,建议选择约束名称,例如@ConsistentDateParameters-使约束目标明显化。

示例6.15:泛型和跨参数约束
package org.hibernate.validator.referenceguide.chapter06.crossparameter;

@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParametersValidator implements
        ConstraintValidator<ConsistentDateParameters, Object[]> {

    @Override
    public void initialize(ConsistentDateParameters constraintAnnotation) {
    }

    @Override
    public boolean isValid(Object[] value, ConstraintValidatorContext context) {
        if ( value.length != 2 ) {
            throw new IllegalArgumentException( "Illegal method signature" );
        }

        //leave null-checking to @NotNull on individual parameters
        if ( value[0] == null || value[1] == null ) {
            return true;
        }

        if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
            throw new IllegalArgumentException(
                    "Illegal method signature, expected two " +
                            "parameters of type Date."
            );
        }

        return ( (Date) value[0] ).before( (Date) value[1] );
    }
}

 

如上所述,验证目标PARAMETERS必须使用@SupportedValidationTarget注释由于跨参数约束可以应用于任何方法或构造函数,因此检查验证器实现中参数的预期数量和类型被认为是一种最佳做法。

与一般约束一样,null参数应被认为是有效的,并且@NotNull,则应使用单个参数来确保参数不是null.

 

类似于类级约束,在验证跨参数约束时,可以在单个参数上创建自定义约束冲突,而不是所有参数。只需从ConstraintValidatorContext传给isValid()并通过调用addParameterNode()。在本例中,您可以使用它在已验证方法的结束日期参数上创建约束冲突。

在极少数情况下,约束是泛型约束和交叉参数约束。如果约束有一个验证器类,并使用@SupportedValidationTarget({ValidationTarget.PARAMETERS, ValidationTarget.ANNOTATED_ELEMENT})或者它是否具有泛型和跨参数验证器类。

当在具有参数和返回值的方法上声明这样的约束时,无法确定所要的约束目标。因此,同时具有泛型和交叉参数的约束必须定义成员。validationAppliesTo(),它允许约束用户指定约束的目标,如示例6.16,“泛型和跨参数约束”.

示例6.16:泛型和跨参数约束
package org.hibernate.validator.referenceguide.chapter06.crossparameter;

@Constraint(validatedBy = {
        ScriptAssertObjectValidator.class,
        ScriptAssertParametersValidator.class
})
@Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ScriptAssert {

    String message() default "{org.hibernate.validator.referenceguide.chapter04." +
            "crossparameter.ScriptAssert.message}";

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

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

    String script();

    ConstraintTarget validationAppliesTo() default ConstraintTarget.IMPLICIT;
}

 

这个@ScriptAssert约束有两个验证器(未显示),一个是泛型的,另一个是跨参数的,因此定义了成员。validationAppliesTo()。默认值IMPLICIT允许在可能的情况下自动派生目标(例如,如果约束是在具有参数但没有返回值的字段或方法上声明的)。

如果无法隐式确定目标,则用户必须将其设置为PARAMETERSRETURN_VALUE如图所示示例6.17,“指定泛型和跨参数约束的目标”.

示例6.17:为泛型和跨参数约束指定目标
@ScriptAssert(script = "arg1.size() <= arg0", validationAppliesTo = ConstraintTarget.PARAMETERS)
public Car buildCar(int seatCount, List<Passenger> passengers) {
    //...
    return null;
}

 

6.4.约束合成

看着licensePlate字段Car示例6.10,“应用@CheckCase约束“,您已经看到了三个约束注释。在更复杂的场景中,甚至可以对一个元素应用更多的约束,这可能会变得有些混乱。此外,如果有一个licensePlate在另一个类中,您也必须将所有约束声明复制到另一个类,这违反了DID原则。

您可以通过创建由几个基本约束组成的更高级别的约束来解决此类问题。示例6.18,“创建组合约束”@ValidLicensePlate显示包含约束的组合约束注释。@NotNull@Size@CheckCase:

示例6.18:创建组合约束@ValidLicensePlate
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;

@NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
@Documented
public @interface ValidLicensePlate {

    String message() default "{org.hibernate.validator.referenceguide.chapter06." +
            "constraintcomposition.ValidLicensePlate.message}";

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

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

 

要创建一个组合约束,只需注释约束声明及其包含的约束。如果组合约束本身需要验证器,则将在@Constraint注释对于不需要额外验证器的组合约束,例如@ValidLicensePlate,就这么定了validatedBy()一个空数组。

licensePlate字段完全等价于上一个版本,在该版本中,直接在字段本身声明了三个约束:

示例6.19:组合约束的应用ValidLicensePlate
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition;

public class Car {

    @ValidLicensePlate
    private String licensePlate;

    //...
}

 

一套ConstraintViolation验证Car实例的每个违反的组合约束都包含一个条目。@ValidLicensePlate约束。如果你更喜欢单人房ConstraintViolation如果违反任何组合约束,则@ReportAsSingleViolation元约束可用于以下几个方面:

示例6.20:使用@ReportAsSingleViolation
package org.hibernate.validator.referenceguide.chapter06.constraintcomposition.reportassingle;

//...
@ReportAsSingleViolation
public @interface ValidLicensePlate {

    String message() default "{org.hibernate.validator.referenceguide.chapter06." +
            "constraintcomposition.reportassingle.ValidLicensePlate.message}";

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

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

 

7.价值提取

值提取是从容器中提取值以便进行验证的过程。

7.1.内置价值提取器

Hibernate validator为通常的java容器类型提供了内置的值提取器,除非您正在使用您自己的自定义容器类型(或者外部库的类型,例如番石榴Multimap),您不必添加自己的价值提取器。

所有以下容器类型都存在内置的值提取器:

  • java.util.Iterable;

  • java.util.List;

  • java.util.Map*钥匙和价值;

  • java.util.Optionaljava.util.OptionalIntjava.util.OptionalLongjava.util.OptionalDouble;

  • JavaFXObservableValue(见第7.4节,“JavaFX值提取器”了解更多细节)。

内建值提取器的完整列表及其行为的详细信息可以在JakartaBean验证规范.

7.2.实施ValueExtractor

要从自定义容器中提取值,需要实现ValueExtractor.

 

实施ValueExtractor是不够的,你也需要注册它。看见第7.5节,“登记ValueExtractor更多细节。

ValueExtractor是一个非常简单的api,因为值提取器的唯一目的是将提取的值提供给ValueReceiver.

例如,让我们考虑一下番石榴的例子Optional。这是一个简单的例子,因为我们可以在java.util.Optional第一:

例7.1:aValueExtractor番石榴Optional
package org.hibernate.validator.referenceguide.chapter07.valueextractor;

public class OptionalValueExtractor
        implements ValueExtractor<Optional<@ExtractedValue ?>> {

    @Override
    public void extractValues(Optional<?> originalValue, ValueReceiver receiver) {
        receiver.value( null, originalValue.orNull() );
    }
}

以下是一些解释:

  • 这个@ExtractedValue注释标记正在考虑的类型参数:它将用于解析验证值的类型;

  • 我们使用value()接收方的方法Optional是一种纯包装类型;

  • 我们不希望将节点添加到约束冲突的属性路径,因为我们希望将冲突报告为直接在属性上,因此我们传递一个null节点名value().

一个更有趣的例子是番石榴的例子Multimap我们希望能够同时验证这个容器类型的键和值。

让我们首先考虑值的情况。需要有一个提取它们的值提取器:

例7.2:aValueExtractorMultimap价值
package org.hibernate.validator.referenceguide.chapter07.valueextractor;

public class MultimapValueValueExtractor
        implements ValueExtractor<Multimap<?, @ExtractedValue ?>> {

    @Override
    public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
        for ( Entry<?, ?> entry : originalValue.entries() ) {
            receiver.keyedValue( "<multimap value>", entry.getKey(), entry.getValue() );
        }
    }
}

控件的值的约束。Multimap:

示例7.3:对Multimap
private Multimap<String, @NotBlank String> map1;

另一个值提取器需要能够在Multimap:

例7.4:aValueExtractorMultimap钥匙
package org.hibernate.validator.referenceguide.chapter07.valueextractor;

public class MultimapKeyValueExtractor
        implements ValueExtractor<Multimap<@ExtractedValue ?, ?>> {

    @Override
    public void extractValues(Multimap<?, ?> originalValue, ValueReceiver receiver) {
        for ( Object key : originalValue.keySet() ) {
            receiver.keyedValue( "<multimap key>", key, key );
        }
    }
}

注册这两个值提取器后,可以对Multimap:

示例7.5:键和值的约束Multimap
private Multimap<@NotBlank String, @NotBlank String> map2;

乍一看,这两个值提取器之间的差异可能有点微妙,因此让我们来说明一下它们:

  • 这个@ExtractedValue注释标记目标类型参数(KV在这种情况下)。

  • 我们使用不同的节点名称(<multimap key>V.V.<multimap value>).

  • 在一种情况下,我们将这些值传递给接收方(keyedValue()在另一个,我们通过钥匙。

根据容器类型,应选择ValueReceiver最佳拟合方法:

value()

对于一个简单的包装容器,它用于Optionals

iterableValue()

对于可迭代的容器,它用于Sets

indexedValue()

对于包含索引值的容器,它用于Lists

keyedValue()

对于包含键控值的容器,它用于Map它用于键和值。在键的情况下,键也作为已验证的值传递。

对于所有这些方法,您需要传递一个节点名称:它是包含在添加到违反约束的属性路径的节点中的名称。如前所述,如果节点名为null,在属性路径中没有添加任何节点:它对于类似于Optional.

选择所使用的方法非常重要,因为它将上下文信息添加到违反约束的属性路径中,例如索引或验证值的键。

7.3.非通用容器

您可能已经注意到,到目前为止,我们只为泛型容器实现了值提取器。

Hibernate Validator还支持非泛型容器的值提取。

让我们来看看java.util.OptionalInt它包装了一个原语int变成Optional-就像集装箱

的值提取器的第一次尝试OptionalInt看起来像:

例7.6:aValueExtractorOptionalInt
package org.hibernate.validator.referenceguide.chapter07.nongeneric;

public class OptionalIntValueExtractor
        implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {

    @Override
    public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
        receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
    }
}

对于非泛型容器,有一个明显的遗漏:我们没有类型参数。它有两个后果:

  • 我们不能使用类型参数来确定验证值的类型;

  • 我们不能在类型参数上添加约束(例如,Container<@NotNull String>).

首先,我们需要一种方法来告诉Hibernate Validator,从OptionalInt是类型的Integer。正如您在上面的示例中所看到的,type属性的@ExtractedValue注释允许向验证引擎提供此信息。

然后,您必须告诉验证引擎,Min控件中添加的约束。OptionalInt属性与已包装的值有关,而不是与包装有关。

JakartaBean验证提供了Unwrapping.Unwrap这种情况的有效载荷:

例7.7:使用Unwrapping.Unwrap有效载荷
@Min(value = 5, payload = Unwrapping.Unwrap.class)
private OptionalInt optionalInt1;

如果我们后退一步,我们想要在OptionalInt属性将应用于已包装的值,因此有一种使其成为默认值的方法将是很好的。

这正是@UnwrapByDefault注释用于:

例7.8:aValueExtractorOptionalInt标有@UnwrapByDefault
package org.hibernate.validator.referenceguide.chapter07.nongeneric;

@UnwrapByDefault
public class UnwrapByDefaultOptionalIntValueExtractor
        implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {

    @Override
    public void extractValues(OptionalInt originalValue, ValueReceiver receiver) {
        receiver.value( null, originalValue.isPresent() ? originalValue.getAsInt() : null );
    }
}

声明此值提取器时OptionalInt,约束注释在默认情况下将应用于包装的值:

例7.9:由于@UnwrapByDefault
@Min(5)
private OptionalInt optionalInt2;

注意,仍然可以通过使用Unwrapping.Skip有效载荷:

示例7.10:避免使用隐式展开Unwrapping.Skip
@NotNull(payload = Unwrapping.Skip.class)
@Min(5)
private OptionalInt optionalInt3;
 

这个@UnwrapByDefault价值提取器OptionalInt是内置价值提取器的一部分:不需要添加一个。

7.4.JavaFX值提取器

JavaFX中的bean属性通常不是简单的数据类型,例如Stringint,但却被包裹在Property允许使它们可观察的类型,用于数据绑定等。

因此,需要值提取才能对包装的值应用约束。

JavaFXObservableValue值提取器被标记为@UnwrapByDefault。因此,容器上的约束默认以包装值为目标。

因此,您可以限制StringProperty详情如下:

示例7.11:约束StringProperty
@NotBlank
private StringProperty stringProperty;

或者是LongProperty:

示例7.12:约束LongProperty
@Min(5)
private LongProperty longProperty;

可迭代属性类型,即ReadOnlyListPropertyListProperty以及他们SetMap对应方是泛型的,因此可以使用容器元素约束。因此,它们具有没有标记的特定的值提取器。@UnwrapByDefault.

ReadOnlyListProperty自然会被限制为List:

示例7.13:约束ReadOnlyListProperty
@Size(min = 1)
private ReadOnlyListProperty<@NotBlank String> listProperty;

7.5.注册ValueExtractor

Hibernate Validator不会自动检测类路径中的值提取器,因此必须注册它们。

有几种方法可以登记价值提取器(按优先顺序排列):

由验证引擎本身提供。

看见第7.1节,“内置价值提取器”.

通过Java服务加载器机制

文件META-INF/services/jakarta.validation.valueextraction.ValueExtractor必须提供一个或多个值提取器实现的完全限定名称作为其内容,每个名称位于单独的行上。

META-INF/validation.xml档案

看见第8.1节,“在Validation.xml有关如何在XML配置中注册值提取器的详细信息。

打电话Configuration#addValueExtractor(ValueExtractor<?>)

看见第9.2.6节,“登记ValueExtractorS“想了解更多信息。

通过调用ValidatorContext#addValueExtractor(ValueExtractor<?>)

它只声明了以下内容的值提取器:Validator举个例子。

以较高优先级指定的给定类型和类型参数的值提取器将重写任何其他提取器,用于同一类型和以较低优先级给定的类型参数。

7.6.分辨算法

在大多数情况下,您不必担心这一点,但是,如果要覆盖现有的值提取器,您可以在JakartaBean验证规范中找到有关值提取器解析算法的详细描述:

需要记住的一件重要事情是:

  • 对于容器元素约束,声明的类型用于解析值提取器;

  • 对于级联验证,它是运行时类型。

8.通过XML进行配置

到目前为止,我们已经使用了用于JakartaBean验证的默认配置源,即注释。但是,也存在两种允许通过XML进行配置的XML描述符。第一个描述符描述一般的JakartaBean验证行为,并提供如下Meta-INF/validation.xml。第二个描述约束声明,并通过注释与约束声明方法紧密匹配。让我们来看看这两种文档类型。

 

XSD文件可在Https://jakarta.ee/xml/ns/validation/一页。

8.1.中配置验证器工厂。Validation.xml

为Hibernate Validator启用XML配置的关键是文件Meta-INF/validation.xml。如果该文件存在于类路径上,则当ValidatorFactory被创造出来。图1,“验证配置模式”显示xml架构的模型视图。Validation.xml必须坚持。

validation-configuration-2.0.xsd
图1.验证配置模式

例8.1,“validation.xml的几个配置选项。Validation.xml。所有设置都是可选的,相同的配置选项也可通过以下方式以编程方式获得:jakarta.validation.Configuration。实际上,XML配置将被通过编程API显式指定的值覆盖。甚至可以完全忽略xml配置。Configuration#ignoreXmlConfiguration()。另见第9.2节,“配置ValidatorFactory.

例8.1:validation.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">

    <default-provider>com.acme.ValidationProvider</default-provider>

    <message-interpolator>com.acme.MessageInterpolator</message-interpolator>
    <traversable-resolver>com.acme.TraversableResolver</traversable-resolver>
    <constraint-validator-factory>
        com.acme.ConstraintValidatorFactory
    </constraint-validator-factory>
    <parameter-name-provider>com.acme.ParameterNameProvider</parameter-name-provider>
    <clock-provider>com.acme.ClockProvider</clock-provider>

    <value-extractor>com.acme.ContainerValueExtractor</value-extractor>

    <executable-validation enabled="true">
        <default-validated-executable-types>
            <executable-type>CONSTRUCTORS</executable-type>
            <executable-type>NON_GETTER_METHODS</executable-type>
            <executable-type>GETTER_METHODS</executable-type>
        </default-validated-executable-types>
    </executable-validation>

    <constraint-mapping>META-INF/validation/constraints-car.xml</constraint-mapping>

    <property name="hibernate.validator.fail_fast">false</property>
</validation-config>
 

必须只有一个文件名为Meta-INF/validation.xml在类路径上。如果找到多个异常,则抛出异常。

节点default-provider允许选择JakartaBean验证提供程序。如果类路径上有多个提供程序,这是有用的。message-interpolatortraversable-resolverconstraint-validator-factoryparameter-name-providerclock-provider允许自定义接口的使用实现MessageInterpolatorTraversableResolverConstraintValidatorFactoryParameterNameProviderClockProvider中定义的jakarta.validation包裹。见第9.2节,“配置ValidatorFactory有关这些接口的更多信息。

value-extractor允许声明附加的值提取器从自定义容器类型中提取值或覆盖内置的值提取器。看见第七章,值提取有关如何实现的更多信息,请参见jakarta.validation.valueextraction.ValueExtractor.

executable-validation它的子节点定义方法验证的默认值。JakartaBean验证规范将构造函数和非getter方法定义为默认值。已启用的属性充当全局开关,以打开和关闭方法验证(另请参阅第三章,声明和验证方法约束).

通过constraint-mapping元素,可以列出包含实际约束配置的任意数量的其他XML文件。必须使用类路径上的完全限定名指定映射文件名。有关写入映射文件的详细信息,请参阅下一节。

最后但并非最不重要的是,您可以通过property节点。在本例中,我们使用Hibernate Validator特定的hibernate.validator.fail_fast财产(见第12.2节,“快速失败模式”).

8.2.映射约束constraint-mappings

使用xml表示约束是可能的,可以通过与图2,“验证映射模式”。中的约束映射列出这些映射文件时,才会对这些映射文件进行处理。Validation.xml.

validation-mapping-2.0.xsd
图2.验证映射模式
示例8.2:通过XML配置的bean约束
<constraint-mappings
        xmlns="https://jakarta.ee/xml/ns/validation/mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/mapping
            https://jakarta.ee/xml/ns/validation/validation-mapping-3.0.xsd"
        version="3.0">

    <default-package>org.hibernate.validator.referenceguide.chapter05</default-package>
    <bean class="Car" ignore-annotations="true">
        <field name="manufacturer">
            <constraint annotation="jakarta.validation.constraints.NotNull"/>
        </field>
        <field name="licensePlate">
            <constraint annotation="jakarta.validation.constraints.NotNull"/>
        </field>
        <field name="seatCount">
            <constraint annotation="jakarta.validation.constraints.Min">
                <element name="value">2</element>
            </constraint>
        </field>
        <field name="driver">
            <valid/>
        </field>
        <field name="partManufacturers">
            <container-element-type type-argument-index="0">
                <valid/>
            </container-element-type>
            <container-element-type type-argument-index="1">
                <container-element-type>
                    <valid/>
                    <constraint annotation="jakarta.validation.constraints.NotNull"/>
                </container-element-type>
            </container-element-type>
        </field>
        <getter name="passedVehicleInspection" ignore-annotations="true">
            <constraint annotation="jakarta.validation.constraints.AssertTrue">
                <message>The car has to pass the vehicle inspection first</message>
                <groups>
                    <value>CarChecks</value>
                </groups>
                <element name="max">10</element>
            </constraint>
        </getter>
    </bean>
    <bean class="RentalCar" ignore-annotations="true">
        <class ignore-annotations="true">
            <group-sequence>
                <value>RentalCar</value>
                <value>CarChecks</value>
            </group-sequence>
        </class>
    </bean>
    <constraint-definition annotation="org.mycompany.CheckCase">
        <validated-by include-existing-validators="false">
            <value>org.mycompany.CheckCaseValidator</value>
        </validated-by>
    </constraint-definition>
</constraint-mappings>
示例8.3:通过XML配置的方法约束
<constraint-mappings
        xmlns="https://jakarta.ee/xml/ns/validation/mapping"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/mapping
            https://jakarta.ee/xml/ns/validation/validation-mapping-3.0.xsd"
        version="3.0">

    <default-package>org.hibernate.validator.referenceguide.chapter08</default-package>

    <bean class="RentalStation" ignore-annotations="true">
        <constructor>
            <return-value>
                <constraint annotation="ValidRentalStation"/>
            </return-value>
        </constructor>

        <constructor>
            <parameter type="java.lang.String">
                <constraint annotation="jakarta.validation.constraints.NotNull"/>
            </parameter>
        </constructor>

        <method name="getCustomers">
            <return-value>
                <constraint annotation="jakarta.validation.constraints.NotNull"/>
                <constraint annotation="jakarta.validation.constraints.Size">
                    <element name="min">1</element>
                </constraint>
            </return-value>
        </method>

        <method name="rentCar">
            <parameter type="Customer">
                <constraint annotation="jakarta.validation.constraints.NotNull"/>
            </parameter>
            <parameter type="java.util.Date">
                <constraint annotation="jakarta.validation.constraints.NotNull"/>
                <constraint annotation="jakarta.validation.constraints.Future"/>
            </parameter>
            <parameter type="int">
                <constraint annotation="jakarta.validation.constraints.Min">
                    <element name="value">1</element>
                </constraint>
            </parameter>
        </method>

        <method name="addCars">
            <parameter type="java.util.List">
                <container-element-type>
                    <valid/>
                    <constraint annotation="jakarta.validation.constraints.NotNull"/>
                </container-element-type>
            </parameter>
        </method>
    </bean>

    <bean class="Garage" ignore-annotations="true">
        <method name="buildCar">
            <parameter type="java.util.List"/>
            <cross-parameter>
                <constraint annotation="ELAssert">
                    <element name="expression">...</element>
                    <element name="validationAppliesTo">PARAMETERS</element>
                </constraint>
            </cross-parameter>
        </method>
        <method name="paintCar">
            <parameter type="int"/>
            <return-value>
                <constraint annotation="ELAssert">
                    <element name="expression">...</element>
                    <element name="validationAppliesTo">RETURN_VALUE</element>
                </constraint>
            </return-value>
        </method>
    </bean>

</constraint-mappings>

XML配置与编程API密切相关。因此,只需添加一些评论就足够了。default-package用于所有需要类名的字段。如果指定的类没有完全限定,则将使用配置的默认包。然后,每个映射文件可以有几个bean节点,每个节点用指定的类名描述实体上的约束。

 

给定的类只能在所有配置文件中配置一次。对于给定约束注释的约束定义也是如此。它只能发生在一个映射文件中。如果违反了这些规则,则ValidationException被扔了。

设置ignore-annotationstrue这意味着放置在配置bean上的约束注释将被忽略。此值的默认值为true。ignore-annotations也可用于节点。classfieldsgetterconstructormethodparametercross-parameterreturn-value。如果没有在这些级别上显式指定,则应用配置好的bean值。

节点classfieldgettercontainer-element-typeconstructormethod(及其子节点参数)确定在哪个级别放置约束。这个valid节点用于启用级联验证,并且constraint节点添加相应级别上的约束。每个约束定义必须通过annotation属性。JakartaBean验证规范所需的约束属性(messagegroupspayload)有专门的节点。所有其他特定于约束的属性都使用element节点。

 

container-element-type允许为容器元素定义级联验证行为和约束。在上面的示例中,您可以看到一个嵌套容器元素约束的示例。List嵌套在Maptype-argument-index类的哪种类型的参数。Map与配置有关。如果类型只有一个类型参数(例如,List在我们的例子中)。

这个class节点还允许重新配置默认组序列(请参阅第5.4节,“重新定义默认组序列”)通过group-sequence节点。示例中未显示的是convert-group指定组转换(请参见第5.5节,“组转换”)。此节点可在fieldgettercontainer-element-typeparameterreturn-value并指定from和一个to属性指定组。

最后但并非最不重要的是ConstraintValidator与给定约束关联的实例可以通过constraint-definition节点。注释属性表示正在更改的约束注释。这个validated-by元素表示的(有序)列表。ConstraintValidator与约束关联的实现。如果include-existing-validator设置为false,则忽略在约束注释上定义的验证器。如果设置为true,XML中描述的约束验证器列表将连接到注释中指定的验证器列表。

 

约束定义的一个用例是更改默认的约束定义。@URL。历史上,Hibernate Validator用于此约束的默认约束验证器使用java.net.URL构造函数来验证URL是否有效。但是,也有一个纯基于正则表达式的版本可用,可以使用XML进行配置:

使用XML注册基于正则表达式的约束定义@URL
<constraint-definition annotation="org.hibernate.validator.constraints.URL">
  <validated-by include-existing-validators="false">
    <value>org.hibernate.validator.constraintvalidators.RegexpURLValidator</value>
  </validated-by>
</constraint-definition>

9.自举

在……里面第2.2.1节,“取得Validator实例“,您已经看到了一种创建Validator实例-维亚Validation#buildDefaultValidatorFactory()。在本章中,您将学习如何使用jakarta.validation.Validation为了引导特定配置的验证器。

9.1.检索ValidatorFactoryValidator

你得到一个Validator通过检索ValidatorFactory的静态方法之一jakarta.validation.Validation打电话getValidator()在工厂那件事上。

示例9.1,“引导默认值”ValidatorFactoryValidator演示如何从默认验证器工厂获得验证器:

示例9.1:引导默认值ValidatorFactoryValidator
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
 

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

JakartaBean验证支持在一个应用程序中与多个提供程序(如Hibernate Validator)一起工作。如果类路径上存在多个提供程序,则无法保证在创建工厂时通过buildDefaultValidatorFactory().

在这种情况下,可以显式指定要使用的提供程序Validation#byProvider(),传递提供者的ValidationProvider类,如示例9.2,“引导”ValidatorFactoryValidator使用特定提供者“.

示例9.2:引导ValidatorFactoryValidator使用特定的提供程序
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

请注意,由configure()允许在调用buildValidatorFactory()。本章后面将讨论可用的选项。

类似地,您可以检索用于配置的默认验证器工厂,如示例9.3,“检索默认值ValidatorFactory用于配置“.

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

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

9.1.1. ValidationProviderResolver

默认情况下,可以使用Java服务提供者机制。

为此,每个提供程序都包含文件。Meta-INF/services/jakarta.validation.spi.ValidationProvider,包含其完全限定的类名。ValidationProvider执行。对于Hibernate Validator,这是org.hibernate.validator.HibernateValidator.

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

若要使用自定义提供程序解析程序,请将其传递给providerResolver()如图所示示例9.4,“使用自定义ValidationProviderResolver.

示例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.配置ValidatorFactory

默认情况下,验证器工厂从Validation并且它们创建的任何验证器都按照xml描述符进行配置。Meta-INF/validation.xml(见第八章,通过XML配置),如果存在的话。

如果要禁用基于xml的配置,可以通过调用Configuration#ignoreXmlConfiguration().

可以通过以下方式访问xml配置的不同值:Configuration#getBootstrapConfiguration()。例如,如果您希望将JakartaBean验证集成到托管环境中,并希望创建通过XML配置的对象的托管实例,这可能会有所帮助。

使用FLUENT配置API,您可以在引导工厂时覆盖一个或多个设置。以下各节演示如何使用不同的选项。注意,Configuration类公开不同扩展点的默认实现,如果要将这些实现用作自定义实现的委托,则这些实现可能很有用。

9.2.1. MessageInterpolator

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

中描述的默认消息内插算法。第四章,插值约束错误消息不足以满足您的需要,您可以传入您自己实现的MessageInterpolator界面通过Configuration#messageInterpolator()如图所示示例9.5,“使用自定义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接口。示例9.6,“使用自定义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:禁用TraversableResolver通过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.enable_traversable_resolver_result_cache">false</property>
</validation-config>

或者通过编程API:

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

9.2.3. ConstraintValidatorFactory

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

默认ConstraintValidatorFactoryHibernate validator提供的函数需要一个公共的no-arg构造函数来实例化。ConstraintValidator实例(见第6.1.2节,“约束验证器”)。使用自定义ConstraintValidatorFactory例如,提供了在约束验证器实现中使用依赖项注入的可能性。

配置自定义约束验证器工厂调用Configuration#constraintValidatorFactory()(见示例9.9,“使用自定义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特定于实现的行为(依赖注入、无-Arg构造函数等)不被认为是可移植的。

 

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

9.2.4. ParameterNameProvider

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

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

若要使用自定义参数名称提供程序,请在引导期间传递提供程序的实例,如示例9.10,“使用自定义ParameterNameProvider,或将提供程序的完全限定类名指定为<parameter-name-provider>元素中的Meta-INF/validation.xml档案(见第8.1节,“在Validation.xml)。这在示例9.10,“使用自定义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附带了一个自定义ParameterNameProvider基于帕纳纳库,它提供了几种在运行时获取参数名称的方法。请参阅第12.14节,“以Paranmer为基础的ParameterNameProvider以了解有关此特定实现的更多信息。

9.2.5. ClockProvider和时间验证公差

与时间有关的验证(@Past@Future),定义所考虑的内容可能是有用的。now.

当您想要以可靠的方式测试约束时,这一点尤其重要。

引用时间由ClockProvider合同。的责任ClockProvider是提供一个java.time.Clock定义now用于与时间相关的验证器。

示例9.11:使用自定义ClockProvider
package org.hibernate.validator.referenceguide.chapter09;

import java.time.Clock;
import java.time.ZonedDateTime;

import jakarta.validation.ClockProvider;

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();

或者,您可以指定ClockProvider实现使用<clock-provider>配置默认验证器工厂时,Meta-INF/validation.xml(见第八章,通过XML配置).

 

验证时@Future@Past约束,您可能希望获得当前时间。

您可以获得ClockProvider在验证器中调用ConstraintValidatorContext#getClockProvider()方法。

例如,如果要替换@Future约束和更明确的约束。

在处理分布式体系结构时,在应用诸如@Past@Future.

您可以通过引导ValidatorFactory详情如下:

示例9.12:使用时间验证公差
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .temporalValidationTolerance( Duration.ofMillis( 10 ) )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

或者,您可以在xml配置中通过设置hibernate.validator.temporal_validation_tolerance你的财产Meta-INF/validation.xml.

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

 

在实现自己的时态约束时,可能需要访问时态验证容忍度。

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

注意,要在初始化时访问此上下文,约束验证程序必须实现HibernateConstraintValidator合同(见第6.1.2.2节,“HibernateConstraintValidator延期“)。该合同目前被标记为孵化器:它可能会在未来发生变化。

9.2.6.登记ValueExtractors

如上文所述第七章,值提取,则可以在引导期间注册附加的值提取器(请参阅第7.5节,“登记ValueExtractor(用于注册值提取器的其他方法)。

示例9.13,“注册附加值提取器”演示如何注册先前创建的值提取器,以提取番石榴的键和值。Multimap.

示例9.13:注册附加值提取器
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
        .configure()
        .addValueExtractor( new MultimapKeyValueExtractor() )
        .addValueExtractor( new MultimapValueValueExtractor() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

9.2.7.添加映射流

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

中指定的映射文件。Meta-INF/validation.xml,您可以通过以下方式添加进一步的映射Configuration#addMapping()(见示例9.14,“添加约束映射流”)。请注意,传入的输入流必须坚持XML模式,用于在第8.2节,“通过constraint-mappings.

示例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.特定于提供程序的设置

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

在Hibernate Validator的情况下,这允许您启用快速失败模式,并传递一个或多个编程约束映射,如示例9.15,“设置Hibernate Validator特定选项”.

示例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:通过以下方式启用Hibernate Validator特定选项addProperty()
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .addProperty( "hibernate.validator.fail_fast", "true" )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();

请参阅第12.2节,“快速失败模式”第12.4节,“程序约束定义和声明”以了解更多有关故障快速模式和约束声明API的信息。

9.2.9.配置ScriptEvaluatorFactory

对于像这样的约束@ScriptAssert@ParameterScriptAssert,配置如何初始化脚本引擎和如何构建脚本计算器可能是有用的。可以通过设置ScriptEvaluatorFactory.

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

9.2.9.1.XML配置

若要指定ScriptEvaluatorFactory通过xml,您需要定义hibernate.validator.script_evaluator_factory财产。

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

9.2.9.2.程序配置

若要以编程方式配置它,需要传递ScriptEvaluatorFactoryValidatorFactory。这使配置具有更大的灵活性。ScriptEvaluatorFactory示例9.18,“定义ScriptEvaluatorFactory以编程方式“展示了如何做到这一点。

示例9.18:定义ScriptEvaluatorFactory以编程方式
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .scriptEvaluatorFactory( new CustomScriptEvaluatorFactory() )
        .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
9.2.9.3.习俗ScriptEvaluatorFactory实施实例

本节展示了几个自定义。ScriptEvaluatorFactory可以在模块化环境中使用的实现以及使用弹簧表达式语言用于编写约束脚本。

模块环境和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实例:通常是希望的类加载器。ScriptEngineS.

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环境设计的,允许您传递BundleContext将用于搜索ScriptEngineFactory作为参数。

如前所述,您还可以使用不基于JSR 223.

例如,要使用弹簧表达式语言,您可以定义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制约因素:

@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.3.配置Validator

在使用配置好的验证器工厂时,有时可能需要将不同的配置应用于单个Validator举个例子。示例9.25,“配置Validator实例通过usingContext()演示如何通过调用ValidatorFactory#usingContext().

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

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

10.使用约束元数据

JakartaBean验证规范不仅提供了验证引擎,而且还提供了一个API,用于以统一的方式检索约束元数据,无论约束是使用注释还是通过XML映射声明的。阅读本章,了解更多有关此API及其可能性的信息。您可以在包中找到所有元数据api类型。jakarta.validation.metadata.

本章中给出的示例基于类和约束声明。示例10.1,“示例类”.

示例10.1:示例类
package org.hibernate.validator.referenceguide.chapter10;

public class Person {

    public interface Basic {
    }

    @NotNull
    private String name;

    //getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;

public interface Vehicle {

    public interface Basic {
    }

    @NotNull(groups = Vehicle.Basic.class)
    String getManufacturer();
}
package org.hibernate.validator.referenceguide.chapter10;

@ValidCar
public class Car implements Vehicle {

    public interface SeverityInfo extends Payload {
    }

    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    private Person driver;

    private String modelName;

    public Car() {
    }

    public Car(
            @NotNull String manufacturer,
            String licencePlate,
            Person driver,
            String modelName) {

        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.driver = driver;
        this.modelName = modelName;
    }

    public void driveAway(@Max(75) int speed) {
        //...
    }

    @LuggageCountMatchesPassengerCount(
            piecesOfLuggagePerPassenger = 2,
            validationAppliesTo = ConstraintTarget.PARAMETERS,
            payload = SeverityInfo.class,
            message = "There must not be more than {piecesOfLuggagePerPassenger} pieces " +
                    "of luggage per passenger."
    )
    public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
        //...
    }

    @Override
    @Size(min = 3)
    public String getManufacturer() {
        return manufacturer;
    }

    public void setManufacturer(String manufacturer) {
        this.manufacturer = manufacturer;
    }

    @Valid
    @ConvertGroup(from = Default.class, to = Person.Basic.class)
    public Person getDriver() {
        return driver;
    }

    //further getters and setters...
}
package org.hibernate.validator.referenceguide.chapter10;

public class Library {

    @NotNull
    private String name;

    private List<@NotNull @Valid Book> books;

    //getters and setters ...
}
package org.hibernate.validator.referenceguide.chapter10;

public class Book {

    @NotEmpty
    private String title;

    @NotEmpty
    private String author;

    //getters and setters ...
}

10.1. BeanDescriptor

元数据api的入口点是方法。Validator#getConstraintsForClass(),它返回BeanDescriptor接口。使用此描述符,您可以获得直接在bean本身(类或属性级别)上声明的约束的元数据,但也可以检索表示单个属性、方法和构造函数的元数据描述符。

示例10.2,“使用BeanDescriptor演示如何检索BeanDescriptorCar类以及如何以断言的形式使用此描述符。

 

如果请求类承载的约束声明无效,则ValidationException被扔了。

例10.2:使用BeanDescriptor
BeanDescriptor carDescriptor = validator.getConstraintsForClass( Car.class );

assertTrue( carDescriptor.isBeanConstrained() );

//one class-level constraint
assertEquals( 1, carDescriptor.getConstraintDescriptors().size() );

//manufacturer, licensePlate, driver
assertEquals( 3, carDescriptor.getConstrainedProperties().size() );

//property has constraint
assertNotNull( carDescriptor.getConstraintsForProperty( "licensePlate" ) );

//property is marked with @Valid
assertNotNull( carDescriptor.getConstraintsForProperty( "driver" ) );

//constraints from getter method in interface and implementation class are returned
assertEquals(
        2,
        carDescriptor.getConstraintsForProperty( "manufacturer" )
                .getConstraintDescriptors()
                .size()
);

//property is not constrained
assertNull( carDescriptor.getConstraintsForProperty( "modelName" ) );

//driveAway(int), load(List<Person>, List<PieceOfLuggage>)
assertEquals( 2, carDescriptor.getConstrainedMethods( MethodType.NON_GETTER ).size() );

//driveAway(int), getManufacturer(), getDriver(), load(List<Person>, List<PieceOfLuggage>)
assertEquals(
        4,
        carDescriptor.getConstrainedMethods( MethodType.NON_GETTER, MethodType.GETTER )
                .size()
);

//driveAway(int)
assertNotNull( carDescriptor.getConstraintsForMethod( "driveAway", int.class ) );

//getManufacturer()
assertNotNull( carDescriptor.getConstraintsForMethod( "getManufacturer" ) );

//setManufacturer() is not constrained
assertNull( carDescriptor.getConstraintsForMethod( "setManufacturer", String.class ) );

//Car(String, String, Person, String)
assertEquals( 1, carDescriptor.getConstrainedConstructors().size() );

//Car(String, String, Person, String)
assertNotNull(
        carDescriptor.getConstraintsForConstructor(
                String.class,
                String.class,
                Person.class,
                String.class
        )
);

您可以通过以下方法确定指定的类是否承载任何类或属性级别的约束。isBeanConstrained()。方法或构造函数约束不考虑isBeanConstrained().

方法getConstraintDescriptors()派生的所有描述符都是通用的。ElementDescriptor(见第10.4条,“ElementDescriptor)并返回一组描述符,表示直接声明在给定元素上的约束。如果BeanDescriptor,则返回bean的类级约束。更多关于ConstraintDescriptor可以在第10.7条,“ConstraintDescriptor.

通孔getConstraintsForProperty()getConstraintsForMethod()getConstraintsForConstructor()您可以获得表示一个给定属性或可执行元素的描述符,该描述符由其名称标识,如果是方法和构造函数,则获取参数类型。以下部分将介绍这些方法返回的不同描述符类型。

注意,这些方法根据约束继承的规则考虑在超级类型中声明的约束,如第2.1.5节,“约束继承”。实例是manufacturer属性,该属性提供对Vehicle#getManufacturer()以及实现方法Car#getManufacturer()null如果指定的元素不存在或不受约束,则返回。

方法getConstrainedProperties()getConstrainedMethods()getConstrainedConstructors()返回(可能为空)集,分别具有所有受约束的属性、方法和构造函数。如果元素至少有一个约束,或者标记为级联验证,则该元素被视为受限。当调用getConstrainedMethods(),您可以指定要返回的方法的类型(getter、non-getters或两者兼而有之)。

10.2. PropertyDescriptor

接口PropertyDescriptor表示类的一个给定属性。如果JavaBeans的命名约定得到尊重,那么在字段或属性getter上声明约束是透明的。例10.3,“使用PropertyDescriptor演示如何使用PropertyDescriptor接口。

例10.3:使用PropertyDescriptor
PropertyDescriptor licensePlateDescriptor = carDescriptor.getConstraintsForProperty(
        "licensePlate"
);

//"licensePlate" has two constraints, is not marked with @Valid and defines no group conversions
assertEquals( "licensePlate", licensePlateDescriptor.getPropertyName() );
assertEquals( 2, licensePlateDescriptor.getConstraintDescriptors().size() );
assertTrue( licensePlateDescriptor.hasConstraints() );
assertFalse( licensePlateDescriptor.isCascaded() );
assertTrue( licensePlateDescriptor.getGroupConversions().isEmpty() );

PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );

//"driver" has no constraints, is marked with @Valid and defines one group conversion
assertEquals( "driver", driverDescriptor.getPropertyName() );
assertTrue( driverDescriptor.getConstraintDescriptors().isEmpty() );
assertFalse( driverDescriptor.hasConstraints() );
assertTrue( driverDescriptor.isCascaded() );
assertEquals( 1, driverDescriptor.getGroupConversions().size() );

使用getConstraintDescriptors(),您可以检索一组ConstraintDescriptors提供有关给定属性的单个约束的更多信息。方法isCascaded()回报true如果属性标记为级联验证(使用@Valid注释或通过XML),false不然的话。返回任何已配置的组转换。getGroupConversions()。看见第10.6节,“GroupConversionDescriptor的更多细节GroupConversionDescriptor.

10.3. MethodDescriptorConstructorDescriptor

约束方法和构造函数由接口表示。MethodDescriptor ConstructorDescriptor分别。示例10.4,“使用MethodDescriptorConstructorDescriptor演示如何使用这些描述符。

示例10.4:使用MethodDescriptorConstructorDescriptor
//driveAway(int) has a constrained parameter and an unconstrained return value
MethodDescriptor driveAwayDescriptor = carDescriptor.getConstraintsForMethod(
        "driveAway",
        int.class
);
assertEquals( "driveAway", driveAwayDescriptor.getName() );
assertTrue( driveAwayDescriptor.hasConstrainedParameters() );
assertFalse( driveAwayDescriptor.hasConstrainedReturnValue() );

//always returns an empty set; constraints are retrievable by navigating to
//one of the sub-descriptors, e.g. for the return value
assertTrue( driveAwayDescriptor.getConstraintDescriptors().isEmpty() );

ParameterDescriptor speedDescriptor = driveAwayDescriptor.getParameterDescriptors()
        .get( 0 );

//The "speed" parameter is located at index 0, has one constraint and is not cascaded
//nor does it define group conversions
assertEquals( "speed", speedDescriptor.getName() );
assertEquals( 0, speedDescriptor.getIndex() );
assertEquals( 1, speedDescriptor.getConstraintDescriptors().size() );
assertFalse( speedDescriptor.isCascaded() );
assert speedDescriptor.getGroupConversions().isEmpty();

//getDriver() has no constrained parameters but its return value is marked for cascaded
//validation and declares one group conversion
MethodDescriptor getDriverDescriptor = carDescriptor.getConstraintsForMethod(
        "getDriver"
);
assertFalse( getDriverDescriptor.hasConstrainedParameters() );
assertTrue( getDriverDescriptor.hasConstrainedReturnValue() );

ReturnValueDescriptor returnValueDescriptor = getDriverDescriptor.getReturnValueDescriptor();
assertTrue( returnValueDescriptor.getConstraintDescriptors().isEmpty() );
assertTrue( returnValueDescriptor.isCascaded() );
assertEquals( 1, returnValueDescriptor.getGroupConversions().size() );

//load(List<Person>, List<PieceOfLuggage>) has one cross-parameter constraint
MethodDescriptor loadDescriptor = carDescriptor.getConstraintsForMethod(
        "load",
        List.class,
        List.class
);
assertTrue( loadDescriptor.hasConstrainedParameters() );
assertFalse( loadDescriptor.hasConstrainedReturnValue() );
assertEquals(
        1,
        loadDescriptor.getCrossParameterDescriptor().getConstraintDescriptors().size()
);

//Car(String, String, Person, String) has one constrained parameter
ConstructorDescriptor constructorDescriptor = carDescriptor.getConstraintsForConstructor(
        String.class,
        String.class,
        Person.class,
        String.class
);

assertEquals( "Car", constructorDescriptor.getName() );
assertFalse( constructorDescriptor.hasConstrainedReturnValue() );
assertTrue( constructorDescriptor.hasConstrainedParameters() );
assertEquals(
        1,
        constructorDescriptor.getParameterDescriptors()
                .get( 0 )
                .getConstraintDescriptors()
                .size()
);

getName()返回给定方法或构造函数的名称。方法hasConstrainedParameters()hasConstrainedReturnValue()可用于快速检查可执行元素是否具有任何参数约束(对单个参数的约束或跨参数约束)或返回值约束。

注意,约束不会直接暴露在MethodDescriptorConstructorDescriptor,而是在表示可执行文件的参数、返回值和跨参数约束的专用描述符上。若要获取这些描述符之一,请调用getParameterDescriptors()getReturnValueDescriptor()getCrossParameterDescriptor()分别。

这些描述符提供对元素约束的访问(getConstraintDescriptors()),在参数和返回值的情况下,返回用于级联验证的配置(isValid()getGroupConversions())。对于参数,还可以检索当前使用的参数名称提供程序返回的索引和名称(请参阅第9.2.4节,“ParameterNameProvider)通过getName()getIndex().

 

遵循JavaBeans命名约定的getter方法被视为bean属性,但也被视为受约束的方法。

这意味着您可以通过获取PropertyDescriptor(如:BeanDescriptor.getConstraintsForProperty("foo")),或者检查getter的返回值描述符。MethodDescriptor(如:BeanDescriptor.getConstraintsForMethod("getFoo").getReturnValueDescriptor()).

10.4. ElementDescriptor

这个ElementDescriptor接口是单个描述符类型的公共基类,如BeanDescriptorPropertyDescriptor等等getConstraintDescriptors()它提供了更多对所有描述符通用的方法。

hasConstraints()允许快速检查元素是否有任何直接约束(例如,在BeanDescriptor).

getElementClass()返回由给定描述符表示的元素的Java类型。更具体地说,该方法返回

  • 上调用时的对象类型。BeanDescriptor,

  • 时调用的属性或参数的类型。PropertyDescriptorParameterDescriptor分别

  • Object[].class当被调用时CrossParameterDescriptor,

  • 上调用时的返回类型。ConstructorDescriptorMethodDescriptorReturnValueDescriptorvoid.class将返回没有返回值的方法。

示例10.5,“使用ElementDescriptor methods演示如何使用这些方法。

示例10.5:使用ElementDescriptor methods
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
        "manufacturer"
);

assertTrue( manufacturerDescriptor.hasConstraints() );
assertEquals( String.class, manufacturerDescriptor.getElementClass() );

CrossParameterDescriptor loadCrossParameterDescriptor = carDescriptor.getConstraintsForMethod(
        "load",
        List.class,
        List.class
).getCrossParameterDescriptor();

assertTrue( loadCrossParameterDescriptor.hasConstraints() );
assertEquals( Object[].class, loadCrossParameterDescriptor.getElementClass() );

最后,ElementDescriptor提供对ConstraintFinderAPI,它允许您以细粒度的方式查询约束元数据。例10.6,“使用ConstraintFinder演示如何检索ConstraintFinder实例通过findConstraints()并使用API查询约束元数据。

例10.6:使用ConstraintFinder
PropertyDescriptor manufacturerDescriptor = carDescriptor.getConstraintsForProperty(
        "manufacturer"
);

//"manufacturer" constraints are declared on the getter, not the field
assertTrue(
        manufacturerDescriptor.findConstraints()
                .declaredOn( ElementType.FIELD )
                .getConstraintDescriptors()
                .isEmpty()
);

//@NotNull on Vehicle#getManufacturer() is part of another group
assertEquals(
        1,
        manufacturerDescriptor.findConstraints()
                .unorderedAndMatchingGroups( Default.class )
                .getConstraintDescriptors()
                .size()
);

//@Size on Car#getManufacturer()
assertEquals(
        1,
        manufacturerDescriptor.findConstraints()
                .lookingAt( Scope.LOCAL_ELEMENT )
                .getConstraintDescriptors()
                .size()
);

//@Size on Car#getManufacturer() and @NotNull on Vehicle#getManufacturer()
assertEquals(
        2,
        manufacturerDescriptor.findConstraints()
                .lookingAt( Scope.HIERARCHY )
                .getConstraintDescriptors()
                .size()
);

//Combining several filter options
assertEquals(
        1,
        manufacturerDescriptor.findConstraints()
                .declaredOn( ElementType.METHOD )
                .lookingAt( Scope.HIERARCHY )
                .unorderedAndMatchingGroups( Vehicle.Basic.class )
                .getConstraintDescriptors()
                .size()
);

通孔declaredOn()你可以搜索ConstraintDescriptors在某些元素类型上声明。这对于查找在字段或getter方法上声明的属性约束非常有用。

unorderedAndMatchingGroups()将结果约束限制为与给定验证组匹配的约束。

lookingAt()允许区分直接在元素上指定的约束(Scope.LOCAL_ELEMENT)或属于元素但托管在类层次结构中任何位置的约束(Scope.HIERARCHY).

您还可以组合不同的选项,如上一个示例所示。

 

秩序不受尊重unorderedAndMatchingGroups(),但组继承和序列继承是。

10.5. ContainerDescriptorContainerElementTypeDescriptor

这个ContainerDescriptor接口是支持容器元素约束和级联验证的所有元素的公共接口(PropertyDescriptorParameterDescriptorReturnValueDescriptor).

它有一个单一的方法getConstrainedContainerElementTypes()返回一组ContainerElementTypeDescriptor.

ContainerElementTypeDescriptor延展ContainerDescriptor若要支持嵌套容器元素约束,请执行以下操作。

ContainerElementTypeDescriptor包含有关容器、约束和级联验证的信息。

示例10.7,“使用ContainerElementTypeDescriptor演示如何使用getConstrainedContainerElementTypes()若要检索ContainerElementTypeDescriptor.

示例10.7:使用ContainerElementTypeDescriptor
PropertyDescriptor booksDescriptor = libraryDescriptor.getConstraintsForProperty(
        "books"
);

Set<ContainerElementTypeDescriptor> booksContainerElementTypeDescriptors =
        booksDescriptor.getConstrainedContainerElementTypes();
ContainerElementTypeDescriptor booksContainerElementTypeDescriptor =
        booksContainerElementTypeDescriptors.iterator().next();

assertTrue( booksContainerElementTypeDescriptor.hasConstraints() );
assertTrue( booksContainerElementTypeDescriptor.isCascaded() );
assertEquals(
        0,
        booksContainerElementTypeDescriptor.getTypeArgumentIndex().intValue()
);
assertEquals(
        List.class,
        booksContainerElementTypeDescriptor.getContainerClass()
);

Set<ConstraintDescriptor<?>> constraintDescriptors =
        booksContainerElementTypeDescriptor.getConstraintDescriptors();
ConstraintDescriptor<?> constraintDescriptor =
        constraintDescriptors.iterator().next();

assertEquals(
        NotNull.class,
        constraintDescriptor.getAnnotation().annotationType()
);

10.6. GroupConversionDescriptor

表示可以作为级联验证主题的元素的所有描述符类型(即,PropertyDescriptorParameterDescriptorReturnValueDescriptor)提供对元素组转换的访问getGroupConversions()。返回的集合包含GroupConversionDescriptor对于每个配置的转换,允许检索转换的源组和目标组。示例10.8,“使用GroupConversionDescriptor举个例子。

示例10.8:使用GroupConversionDescriptor
PropertyDescriptor driverDescriptor = carDescriptor.getConstraintsForProperty( "driver" );

Set<GroupConversionDescriptor> groupConversions = driverDescriptor.getGroupConversions();
assertEquals( 1, groupConversions.size() );

GroupConversionDescriptor groupConversionDescriptor = groupConversions.iterator()
        .next();
assertEquals( Default.class, groupConversionDescriptor.getFrom() );
assertEquals( Person.Basic.class, groupConversionDescriptor.getTo() );

10.7. ConstraintDescriptor

最后但并非最不重要的是,ConstraintDescriptor接口描述单个约束及其组合约束。通过这个接口的一个实例,您可以访问约束注释及其参数。

示例10.9,“使用ConstraintDescriptor演示如何检索默认约束属性(例如消息模板、组等)。以及自定义约束属性(piecesOfLuggagePerPassenger)和其他元数据,例如约束的注释类型及其从ConstraintDescriptor.

示例10.9:使用ConstraintDescriptor
//descriptor for the @LuggageCountMatchesPassengerCount constraint on the
//load(List<Person>, List<PieceOfLuggage>) method
ConstraintDescriptor<?> constraintDescriptor = carDescriptor.getConstraintsForMethod(
        "load",
        List.class,
        List.class
).getCrossParameterDescriptor().getConstraintDescriptors().iterator().next();

//constraint type
assertEquals(
        LuggageCountMatchesPassengerCount.class,
        constraintDescriptor.getAnnotation().annotationType()
);

//standard constraint attributes
assertEquals( SeverityInfo.class, constraintDescriptor.getPayload().iterator().next() );
assertEquals(
        ConstraintTarget.PARAMETERS,
        constraintDescriptor.getValidationAppliesTo()
);
assertEquals( Default.class, constraintDescriptor.getGroups().iterator().next() );
assertEquals(
        "There must not be more than {piecesOfLuggagePerPassenger} pieces of luggage per " +
        "passenger.",
        constraintDescriptor.getMessageTemplate()
);

//custom constraint attribute
assertEquals(
        2,
        constraintDescriptor.getAttributes().get( "piecesOfLuggagePerPassenger" )
);

//no composing constraints
assertTrue( constraintDescriptor.getComposingConstraints().isEmpty() );

//validator class
assertEquals(
        Arrays.<Class<?>>asList( LuggageCountMatchesPassengerCount.Validator.class ),
        constraintDescriptor.getConstraintValidatorClasses()
);

11.与其他框架的整合

Hibernate Validator用于实现多层数据验证,其中约束在一个地方表示(带注释的域模型),并在应用程序的不同层中进行检查。因此,与其他技术有多个集成点。

11.1.ORM集成

Hibernate Validator集成了Hibernate ORM和所有纯Java持久性提供程序。

 

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

11.1.1.数据库模式级验证

Hibernate ORM会将您为实体定义的约束转换为映射元数据。例如,如果对实体的属性进行了注释@NotNull,则其列将声明为not null在Hibernate ORM生成的DDL模式中。

如果由于某种原因需要禁用该功能,则设置hibernate.validator.apply_to_ddlfalse。另见第2.3.1节,“JakartaBean验证约束”第2.3.2节,“附加限制”.

还可以通过设置属性将DDL约束生成限制为定义约束的子集。org.hibernate.validator.group.ddl。该属性指定约束必须包含的组的逗号分隔、完全指定的类名,以便在DDL模式生成中考虑。

11.1.2.基于Hibernate ORM事件的验证

Hibernate validator有一个内置的Hibernate事件监听器-org.hibernate.cfg.beanvalidation.BeanValidationEventListener-这是Hibernate ORM的一部分。每当PreInsertEventPreUpdateEventPreDeleteEvent发生时,侦听器将验证实体实例的所有约束,并在任何约束被违反时抛出异常。默认情况下,对象将在Hibernate ORM进行任何插入或更新之前进行检查。预删除事件在默认情况下不会触发验证。可以使用属性配置要根据事件类型验证的组。jakarta.persistence.validation.group.pre-persist,jakarta.persistence.validation.group.pre-updatejakarta.persistence.validation.group.pre-remove。这些属性值是要验证的组的逗号分隔、完全指定的类名。示例11.1,“手工配置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位于类路径中,则JPA 2规范要求启用JakartaBean验证。性质jakarta.persistence.validation.group.pre-persistjakarta.persistence.validation.group.pre-updatejakarta.persistence.validation.group.pre-remove如上文所述第11.1.2节,“基于Hibernate ORM事件的验证”在这种情况下,可以在Persistence.xmlPersistence.xml还定义了一个节点验证模式,该模式可以设置为AUTOCALLBACKNONE。默认情况是AUTO.

11.2.JSF&Seam

当在运行时环境中使用JSF 2或JBossSeam和Hibernate Validator(JakartaBean验证)时,将对应用程序中的每个字段触发验证。例11.2,“JSF 2中雅加达Bean验证的使用”显示了f:validateBean标记在JSF页面中。这个validationGroups属性是可选的,可用于指定逗号分隔的验证组列表。默认情况是jakarta.validation.groups.Default。有关更多信息,请参阅Seam文档或JSF 2规范。

例11.2:在JSF 2中使用JakartaBean验证
<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和JakartaBean验证之间的集成在JSR-314。很有趣的是,JSF 2实现了一个自定义MessageInterpolator以确保适当的本地化。为了鼓励使用JakartaBean验证消息工具,JSF 2在默认情况下只显示生成的Bean验证消息。但是,通过提供以下配置,可以通过应用程序资源包对其进行配置({0}被替换为JakartaBean验证消息和{1}替换为JSF组件标签):

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

默认情况是:

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

11.3.CDI

在1.1版中,Bean验证(因此是JakartaBean验证)与CDI(JakartaEE的上下文和依赖项注入)集成在一起。

此集成为ValidatorValidatorFactory并在约束验证器以及自定义消息内插器、可遍历解析器、约束验证器工厂、参数名称提供程序、时钟提供程序和值提取器中启用依赖注入。

此外,CDI托管bean的方法和构造函数的参数和返回值约束将在调用时自动验证。

当应用程序在JavaEE容器上运行时,默认情况下将启用此集成。在servlet容器或纯JavaSE环境中使用CDI时,可以使用Hibernate Validator提供的CDI可移植扩展。为此,将可移植扩展添加到类路径,如第1.1.2节,“CDI”.

11.3.1.依赖注入

CDI的依赖注入机制使得检索变得非常容易ValidatorFactoryValidator实例,并在托管bean中使用它们。只需用@jakarta.inject.Inject如图所示示例11.3,“通过以下方式检索验证器工厂和验证器@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是默认的验证器工厂和验证器实例。为了配置它们--例如使用自定义消息内插器--您可以使用JakartaBean验证XML描述符,如第八章,通过XML配置.

如果您正在使用多个JakartaBean验证提供程序,则可以通过使用@HibernateValidator中演示的限定符示例11.4,“使用@HibernateValidator限定符注释“.

示例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相反,哪一种是ValidationProvider用于在使用引导API时选择Hibernate Validator的实现(请参阅第9.1节,“检索ValidatorFactoryValidator).

通孔@Inject您还可以将依赖项注入到约束验证器和其他JakartaBean验证对象中,例如MessageInterpolator实现等。

示例11.5,“带有注入bean的约束验证程序”演示如何在ConstraintValidator实现来确定给定约束是否有效。如示例所示,您还可以使用@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.方法验证

CDI的方法拦截功能允许与JakartaBean验证的方法验证功能进行非常紧密的集成。只要将约束注释放在CDIbean的可执行文件的参数和返回值上,它们就会在(参数约束)之前和(返回值约束)之后自动验证,然后调用方法或构造函数。

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

 

拦截器org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor是由.注册的org.hibernate.validator.cdi.internal.ValidationExtension。这在JavaEE运行时环境中隐式发生,或者通过添加Hibernate-验证器-cdi神器-见第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 );
    }
}

在这里RentalStationBean托管了几个方法约束。调用RentalStation方法,如RentCarRequest,将自动验证被调用方法的约束。如果传递任何非法的参数值,如本例所示,则ConstraintViolationException将由方法拦截器抛出,提供有关违反约束的详细信息。如果方法的返回值违反了任何返回值约束,情况也是如此。

类似地,构造函数约束在调用时会自动验证。在示例中,RentalStation对象由构造函数返回的对象将被验证,因为构造函数返回值被标记为@Valid.

11.3.2.1.经验证的可执行文件类型

JakartaBean验证允许对自动验证的可执行类型进行细粒度控制。默认情况下,对构造函数和非getter方法的约束被验证.因此@NotNull对方法的约束RentalStation#getAvailableCars()在……里面示例11.6,“具有方法级约束的CDI托管bean”在调用该方法时不进行验证。

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

  • 通过xml描述符全局配置可执行类型Meta-INF/validation.xml;见第8.1节,“在Validation.xml举个例子

  • 使用@ValidateOnExecution可执行文件或类型级别上的注释

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

示例11.7,“使用@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是显式指定所有类型的更紧凑的表单。CONSTRUCTORSGETTER_METHODSNON_GETTER_METHODS.

 

通过指定<executable-validation enabled="false"/>在……里面Meta-INF/validation.xml。在这种情况下,所有的@ValidateOnExecution注释被忽略。

注意,当方法重写或实现超类型方法时,配置将从该重写或实现方法(如@ValidateOnExecution在方法本身或超级类型上)。这将保护超级类型方法的客户端免受意外的配置更改,例如禁用子类型中覆盖的可执行文件的验证。

如果CDI托管bean覆盖或实现了超级类型方法,而此超级类型方法承载任何约束,则可能会发生验证拦截器未在bean中正确注册的情况,从而导致bean的方法在调用时不被验证。在这种情况下,可以指定可执行类型。IMPLICIT中所示的子类示例11.8,“使用ExecutableType.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.JavaEE

当应用程序在JavaEE应用服务器上运行时,例如野弗利,您也可以获得ValidatorValidatorFactory实例通过@Resource在托管对象(如EJB等)中的注入,如示例11.9,“检索ValidatorValidatorFactory通孔@Resource注射“.

示例11.9:检索ValidatorValidatorFactory通孔@Resource注射
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“分别。

类似于基于cdi的注入@Inject,这些对象表示默认的验证器和验证器工厂,因此可以使用xml描述符进行配置。Meta-INF/validation.xml(见第八章,通过XML配置).

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

11.5.JavaFX

Hibernate Validator还为JavaFX属性的展开提供了支持。如果类路径上存在JavaFX,ValueExtractorS用于JavaFX属性自动注册。看见第7.4节,“JavaFX值提取器”例如和进一步的讨论。

12.Hibernate Validator细节

在本章中,您将学习如何使用Hibernate Validator提供的几个特性,以及JakartaBean验证规范定义的功能。这包括故障快速模式、编程约束配置API和约束的布尔组合。

新的api或spis被标记为org.hibernate.validator.Incubating注释,只要它们还在开发中。这意味着这些元素(例如包、类型、方法、常量等)可能是不兼容的改变-或删除-在随后的版本。鼓励使用孵化器API/SPI成员(因此开发团队可以获得关于这些新特性的反馈),但是您应该做好准备,以便在升级到Hibernate Validator的新版本时根据需要更新代码。

 

使用以下部分中描述的特性可能会导致应用程序代码在JakartaBean验证提供者之间不可移植。

12.1.公共API

不过,让我们从Hibernate Validator的公共API开始。下面可以找到属于这个API的所有包的列表以及它们的用途。注意,当包是公共API的一部分时,它的子包不一定是正确的。

org.hibernate.validator

JakartaBean验证引导机制使用的类(例如。验证提供程序,配置类);有关详细信息,请参阅第九章,自举.

org.hibernate.validator.cfgorg.hibernate.validator.cfg.contextorg.hibernate.validator.cfg.defsorg.hibernate.validator.spi.cfg

Hibernate Validator用于约束声明的FLUENT API;org.hibernate.validator.cfg你会发现ConstraintMapping接口org.hibernate.validator.cfg.defs所有约束定义和org.hibernate.validator.spi.cfg用于使用API配置默认验证器工厂的回调。请参阅第12.4节,“程序约束定义和声明”关于细节。

org.hibernate.validator.constraintsorg.hibernate.validator.constraints.brorg.hibernate.validator.constraints.pl

除了JakartaBean验证规范定义的内置约束之外,Hibernate Validator还提供了一些有用的自定义约束;这些约束将在第2.3.2节,“附加限制”.

org.hibernate.validator.constraintvalidation

扩展约束验证器上下文,允许为消息内插设置自定义属性。第12.13.1条,“HibernateConstraintValidatorContext描述如何使用该功能。

org.hibernate.validator.grouporg.hibernate.validator.spi.group

GroupSequenceProvider特性,允许您在已验证对象状态的函数中定义动态默认组序列;具体内容可在第5.4节,“重新定义默认组序列”.

org.hibernate.validator.messageinterpolationorg.hibernate.validator.resourceloadingorg.hibernate.validator.spi.resourceloading

类;第一个包包含Hibernate Validator的默认消息内插器,ResourceBundleMessageInterpolator。后两个包提供了ResourceBundleLocator用于加载资源包的SPI(请参见第4.2.1节,“ResourceBundleLocator)及其默认实现。

org.hibernate.validator.parameternameprovider

ParameterNameProvider基于Paranmer库,请参见第12.14节,“以Paranmer为基础的ParameterNameProvider.

org.hibernate.validator.propertypath

扩展到jakarta.validation.PathAPI,见第12.7节,“路径API的扩展”.

org.hibernate.validator.spi.constraintdefinition

用于以编程方式注册附加约束验证器的SPI,请参见第12.15节,“提供约束定义”.

org.hibernate.validator.spi.messageinterpolation

一个SPI,可用于在内插违反约束消息时调整区域设置的分辨率。看见第12.12节,“自定义地区解析”.

org.hibernate.validator.spi.nodenameprovider

一个SPI,可用于在构造属性路径时更改属性名称的解析方式。看见第12.18节,“为违反约束而自定义属性名称解析”.

 

Hibernate Validator的公共包分为两类:被调用使用由客户端(例如用于编程约束声明的API或自定义约束),SPI(服务提供者接口)包包含的接口落实由客户(如:ResourceBundleLocator).

该表中没有列出的任何包都是Hibernate Validator的内部包,不打算由客户端访问。这些内部包的内容可以在没有通知的情况下从发布更改到发布,从而可能破坏依赖它的任何客户端代码。

12.2.故障快速模式

使用快速失败模式,Hibernate Validator允许在第一次违反约束时立即从当前验证返回。这对于大型对象图的验证非常有用,因为您只对快速检查是否存在任何约束冲突感兴趣。

示例12.1,“使用快速失败验证模式”演示如何引导和使用故障快速启用验证器。

示例12.1:使用快速失败验证模式
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因为启用了快速失败模式。

 

无法保证按何种顺序计算约束,即返回的违规行为是否来自@NotNull或者@AssertTrue约束。如果需要,可以使用组序列强制执行确定性的评估顺序,如第5.3节,“定义组序列”.

请参阅第9.2.8节,“特定于提供者的设置”了解在引导验证器时启用快速失败模式的不同方法。

12.3.类层次结构中方法验证需求的放宽

JakartaBean验证规范定义了一组先决条件,这些先决条件在定义类层次结构中的方法的约束时应用。这些先决条件在第5.6.5节JavaBean验证2.0规范。另见第3.1.4节,“继承层次结构中的方法约束”在这个指南里。

根据规范,JakartaBean验证提供程序可以放宽这些先决条件。使用Hibernate Validator,您可以通过两种方式之一完成这一任务。

首先,您可以使用配置属性。Hibernate.validator.allow_parameter_constraint_override,Hibernate.validator.allow_multiple_cascaded_validation_on_resultHibernate.validator.allow_parallel_method_parameter_constraint在……里面Validation.xml。见示例示例12.2,“通过属性在类层次结构中配置方法验证行为”.

示例12.2:通过属性在类层次结构中配置方法验证行为
<?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>

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

示例12.3:在类层次结构中配置方法验证行为
HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ).configure();

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

默认情况下,所有这些属性都是假的,实现了JakartaBean验证规范中定义的默认行为。

 

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

12.4.程序约束定义和声明

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

此外,Hibernate Validator提供了一个FLUENT API,允许对约束进行编程配置。用例包括在运行时动态添加约束,这取决于某些应用程序状态或测试,在这些情况下,您需要在不同场景中具有不同约束的实体,但不希望为每个测试用例实现实际的Java类。

默认情况下,通过FLUENT API添加的约束与通过标准配置功能配置的约束是相加的。但是,在需要时也可以忽略注释和XML配置的约束。

API的中心是ConstraintMapping接口。您可以通过以下方式获得一个新的映射:HibernateValidatorConfiguration#createConstraintMapping()然后,您可以以流畅的方式进行配置,如实例12.4,“程序约束声明”.

示例12.4:编程约束声明
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配置的约束都会被忽略。

 

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

 

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

配置了映射之后,必须将其添加回Configuration对象,然后从该对象获得验证器工厂。

对于自定义约束,可以创建自己的定义类ConstraintDef或者你可以用GenericConstraintDef如图所示示例12.5,“自定义约束的程序声明”.

示例12.5:定制约束的编程声明
ConstraintMapping constraintMapping = configuration.createConstraintMapping();

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

容器元素约束由编程API支持,使用containerElementType().

示例12.6,“嵌套容器元素约束的程序声明”演示在嵌套容器元素上声明约束的示例。

示例12.6:嵌套容器元素约束的编程声明
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()用于获取所需的嵌套容器元素类型的类型参数索引的路径。

通过调用valid()您可以将一个成员标记为级联验证,这相当于用@Valid。配置在级联验证期间应用的任何组转换。convertGroup()方法(相当于@ConvertGroup)。一个例子可以在示例12.7,“为级联验证标记属性”.

示例12.7:将属性标记为级联验证
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 ) );

您不仅可以使用FLUENT API配置bean约束,还可以使用方法和构造函数约束。如图所示示例12.8,“方法和构造函数约束的程序声明”构造函数通过其参数类型和方法来标识它们的名称和参数类型。选择了方法或构造函数之后,您可以为级联验证标记其参数和/或返回值,并添加约束和跨参数约束。

如示例所示,valid()也可以在容器元素类型上调用。

示例12.8:方法和构造函数约束的编程声明
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();

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

示例12.9:默认组序列和默认组序列提供程序的配置
ConstraintMapping constraintMapping = configuration.createConstraintMapping();

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

12.5.将编程约束声明应用于默认验证器工厂

如果您不是手动引导验证器工厂,而是按照Meta-INF/validation.xml(见第八章,通过XML配置),您可以通过创建一个或多个约束映射贡献者来添加一个或多个约束映射。为此,请实现ConstraintMappingContributor合同:

例12.10:自定义ConstraintMappingContributor实施
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,使用属性键。hibernate.validator.constraint_mapping_contributors。您可以通过用逗号分隔几个贡献者来指定它们。

12.6.高级约束组合特征

12.6.1.验证纯组合约束的目标规范

如果在方法声明中指定纯组合约束(即没有验证器本身但完全由其他组合约束组成的约束),验证引擎无法确定该约束是作为返回值约束还是作为跨参数约束应用。

Hibernate Validator允许通过指定@SupportedValidationTarget对组合约束类型声明的注释,如示例12.11,“指定纯组合约束的验证目标”。这个@ValidInvoiceAmount不声明任何验证器,但它仅由@Min@NotNull制约因素。这个@SupportedValidationTarget确保将约束应用于方法声明中给定的方法返回值。

示例12.11:指定纯组合约束的验证目标
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.布尔约束合成

JakartaBean验证指定组合约束的约束(请参见第6.4节,“约束组合”)都是通过逻辑组合的。这意味着所有组合约束都需要返回true才能获得总体成功的验证。

Hibernate Validator提供了对此的扩展,并允许您通过逻辑组合约束。。要做到这一点,您必须使用ConstraintComplace注释和枚举组合类型及其值全假.

实例12.12,“约束的或组合”演示如何构建组合约束。@PatternOrSize其中,只有一个组合约束需要有效才能通过验证。验证的字符串要么是小写的,要么是2到3个字符长。

示例12.12:或约束的组合
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 { };
}
 

使用全假由于组合类型隐式强制要求在约束组合验证失败时只报告单个冲突。

12.7.路径API的扩展

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

示例12.13:从属性节点获取值
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,在本例中没有键或索引)。

12.8.动态有效载荷作为ConstraintViolation

在某些情况下,如果违反约束提供了额外的数据--所谓的动态有效载荷,则可以帮助自动处理违规行为。例如,此动态有效载荷可能包含向用户提示如何解决冲突。

动态有效载荷可以在自定义约束使用HibernateConstraintValidatorContext。这在示例中显示。例12.14,“ConstraintValidator实现设置动态有效载荷“在那里jakarta.validation.ConstraintValidatorContext被打开HibernateConstraintValidatorContext为了打电话withDynamicPayload.

实例12.14:ConstraintValidator实现设置动态有效载荷
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以便检索动态有效载荷以供进一步处理。

示例12.15:检索ConstraintViolation动态有效载荷
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();
@SuppressWarnings("unchecked")
HibernateConstraintViolation<Car> hibernateConstraintViolation = constraintViolation.unwrap(
        HibernateConstraintViolation.class
);
String suggestedCar = hibernateConstraintViolation.getDynamicPayload( String.class );
assertEquals( "Toyota Volta", suggestedCar );

12.9.启用表达式语言特性

Hibernate Validator在默认情况下限制表达式语言特性。

为此,我们在ExpressionLanguageFeatureLevel:

  • NONE表达式语言内插完全禁用。

  • VARIABLES*允许通过addExpressionVariable()的资源包和使用formatter对象。

  • BEAN_PROPERTIES*允许一切VARIABLES允许加上bean属性的插值。

  • BEAN_METHODS*还允许执行bean方法。这可能导致严重的安全问题,包括任意代码执行,如果不小心处理。

根据上下文的不同,我们公开的特性是不同的:

  • 对于约束,默认级别为BEAN_PROPERTIES。若要正确内插所有内置约束消息,至少需要VARIABLES水平。

  • 对于自定义违规行为,通过ConstraintValidatorContext默认情况下,表达式语言是禁用的。您可以为特定的自定义冲突启用它,当启用时,它将默认为VARIABLES.

Hibernate Validator提供了在引导ValidatorFactory.

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

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

若要更改自定义冲突的表达式语言功能级别,请使用以下内容:

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

这样做将自动为应用程序中的所有自定义冲突启用表达式语言。

它只应该用于兼容性和简化从旧的Hibernate Validator版本的迁移。

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

  • hibernate.validator.constraint_expression_language_feature_level

  • hibernate.validator.custom_violation_expression_language_feature_level

这些属性的接受值是:nonevariablesbean-propertiesbean-methods.

12.10. ParameterMessageInterpolator

Hibernate Validator在默认情况下需要统一EL的实现(参见第1.1.1节,“统一厄尔尼诺现象”)可用。需要这样做才能使用JakartaBean验证规范定义的EL表达式对约束错误消息进行内插。

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

请参阅第4.2节,“自定义消息插值”查看如何插入自定义消息内插器实现。

 

包含EL表达式的约束消息将被org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator。这也会影响使用EL表达式的内置默认约束消息。此刻,DecimalMinDecimalMax受到影响。

12.11. ResourceBundleLocator

带着ResourceBundleLocator,Hibernate Validator提供了一个额外的SPI,该SPI允许从其他资源包中检索错误消息,而不是验证信息同时仍然使用规范定义的实际插值算法。请参阅第4.2.1节,“ResourceBundleLocator学习如何利用SPI。

12.12.自定义地区解析

 

这些合同标记为@Incubating因此,它们可能会在未来发生变化。

Hibernate Validator提供了几个扩展点来构建自定义的地区解析策略。在内插约束冲突消息时使用解析的区域设置。

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

下面的示例演示如何将Hibernate Validator默认区域设置为fr-FR:

示例12.16:配置默认区域设置
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 Validator根据用户上下文选择区域设置。

Hibernate Validator提供org.hibernate.validator.spi.messageinterpolation.LocaleResolverSPI,它允许微调区域设置的分辨率。通常,在JAX-RS环境中,可以从Accept-LanguageHttp标头。

在下面的示例中,我们使用硬编码的值,但是,例如,在RESTEasy应用程序中,可以从ResteasyContext.

示例12.17:微调用于通过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()方法。

12.13.自定义上下文

JakartaBean验证规范在其API中的几个点提供了将给定接口解包装到实现者特定子类型的可能性。在违反约束的情况下,创建ConstraintValidator实现以及消息内插MessageInterpolator实例,存在unwrap()提供的上下文实例的方法-ConstraintValidatorContext分别MessageInterpolatorContext。Hibernate Validator为这两个接口提供了自定义扩展。

12.13.1. HibernateConstraintValidatorContext

HibernateConstraintValidatorContextConstraintValidatorContext这样你就可以:

  • 为特定的自定义冲突启用表达式语言内插-请参见下面

  • 使用表达式语言消息插值工具设置任意参数进行插值。HibernateConstraintValidatorContext#addExpressionVariable(String, Object)HibernateConstraintValidatorContext#addMessageParameter(String, Object).

    例152。习俗@Future验证器注入表达式变量
    package org.hibernate.validator.referenceguide.chapter12.context;
    
    import java.time.Instant;
    
    import jakarta.validation.ConstraintValidator;
    import jakarta.validation.ConstraintValidatorContext;
    import jakarta.validation.constraints.Future;
    
    import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
    
    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;
        }
    }
    例153.习俗@Future验证器注入消息参数
    package org.hibernate.validator.referenceguide.chapter12.context;
    
    import java.time.Instant;
    
    import jakarta.validation.ConstraintValidator;
    import jakarta.validation.ConstraintValidatorContext;
    import jakarta.validation.constraints.Future;
    
    import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorContext;
    
    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;
        }
    }
     

    除了语法之外,消息参数和表达式变量之间的主要区别是消息参数被简单地插值,而表达式变量是使用表达式语言引擎来解释的。实际上,如果不需要表达式语言的高级功能,请使用消息参数。

     

    注意,通过addExpressionVariable(String, Object)addMessageParameter(String, Object)是全局的,并应用于由isValid()召唤。这包括默认约束违反,但也包括由ConstraintViolationBuilder。但是,您可以在ConstraintViolationBuilder#addConstraintViolation().

  • 设置任意动态有效载荷-请参阅第12.8节,“动态有效载荷是ConstraintViolation

默认情况下,表达式语言内插是残废对于自定义冲突,如果消息模板是从不正确的转义用户输入生成的,这将避免任意代码执行或敏感数据泄漏。

可以为给定的自定义冲突启用表达式语言,方法是enableExpressionLanguage()如下例所示:

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;
    }
}

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

默认情况下,启用表达式语言时只启用变量内插。

您可以通过以下方式启用更多功能HibernateConstraintViolationBuilder#enableExpressionLanguage(ExpressionLanguageFeatureLevel level).

我们为表达式语言内插定义了几个层次的特征:

  • NONE表达式语言内插完全禁用--这是自定义冲突的默认设置。

  • VARIABLES*允许通过addExpressionVariable()的资源包和使用formatter对象。

  • BEAN_PROPERTIES*允许一切VARIABLES允许加上bean属性的插值。

  • BEAN_METHODS*还允许执行bean方法。这可能导致严重的安全问题,包括任意代码执行,如果不小心处理。

 

使用addExpressionVariable()是将变量注入表达式的唯一安全方法,如果使用BEAN_PROPERTIESBEAN_METHODS特征级别。

如果通过在消息中连接用户输入注入用户输入,则将允许潜在的任意代码执行和敏感数据泄漏:如果用户输入包含有效表达式,则它们将由表达式语言引擎执行。

下面是一个你应该做的事情的例子绝对不是做:

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;
    }
}

在上面的示例中,如果value,它可能是用户输入,包含一个有效的表达式,它将由表达式语言引擎内插,可能导致不安全的行为。

12.13.2. HibernateMessageInterpolatorContext

Hibernate Validator还提供了一个自定义扩展MessageInterpolatorContext,即HibernateMessageInterpolatorContext(见例12.18,“HibernateMessageInterpolatorContext)。引入此子类型是为了使Hibernate Validator更好地集成到玻璃鱼中。在本例中,需要根bean类型来确定消息资源包的正确类加载程序。如果您有其他用例,请通知我们。

实例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.帕拉纳默尔基ParameterNameProvider

Hibernate Validator附带一个ParameterNameProvider实现,它利用帕纳纳默尔图书馆。

此库提供了几种在运行时获取参数名称的方法,例如基于Java编译器创建的调试符号、在编译后步骤中将参数名编织到字节码中的常量或注释(如@NamedJSR 330的注释。

为了使用ParanamerParameterNameProvider,在引导验证器时传递一个实例,如示例9.10,“使用自定义ParameterNameProvider或指定org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider作为<parameter-name-provider>元素中的Meta-INF/validation.xml档案。

 

在使用此参数名称提供程序时,需要将Paranmer库添加到类路径中。它可以在带有组id的Maven中央存储库中使用。com.thoughtworks.paranamer和神器标识paranamer.

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

12.15.提供约束定义

JakartaBean验证允许(重新)通过XML在其约束映射文件中定义约束定义。看见第8.2节,“通过constraint-mappings获取更多信息和示例8.2“通过XML配置的Bean约束”举个例子。虽然这种方法对于许多用例来说是足够的,但它在其他用例中也有它的缺点。例如,设想一个约束库希望为自定义类型提供约束定义。这个库可以提供一个映射文件和它们的库,但是这个文件仍然需要由库的用户引用。幸运的是有更好的方法。

 

以下概念目前被认为是实验性的。让我们知道你是否觉得它们有用,它们是否能满足你的需要。

12.15.1.约束定义ServiceLoader

Hibernate Validator允许使用Java的ServiceLoader注册附加约束定义的机制。您所要做的就是添加文件Jakarta.validation.ConstraintValidatorMETA-INF/服务。在此服务文件中,列出约束验证器类的完全限定类名(每行一个)。Hibernate Validator将自动推断它们应用到的约束类型。看见通过服务文件定义约束举个例子。

实例12.19:META-INF/services/jakarta.validation.ConstraintValidator
# Assuming a custom constraint annotation @org.mycompany.CheckCase
org.mycompany.CheckCaseValidator

若要为自定义约束提供默认消息,请放置一个文件。ContributorValidationMessages.properties和/或JAR根部的特定于地区的专门化。Hibernate Validator将考虑在类路径上找到的所有具有此名称的包中的条目,以及ValidationMessages.properties.

这种机制在创建大型多模块应用程序时也很有帮助:与其将所有约束消息放在一个包中,不如每个模块只有一个资源包,只包含该模块的那些消息。

 

我们强烈建议阅读这篇博文由Marko Bekhta撰写,引导您逐步完成创建包含自定义约束的独立JAR的过程,并通过ServiceLoader.

12.15.2以编程方式添加约束定义

虽然服务加载器方法可以在许多场景中工作,但并不适用于所有场景(例如,在服务文件不可见的OSGi中),还有另一种提供约束定义的方法。可以使用编程约束声明api-请参阅示例12.20,“通过编程API添加约束定义”.

示例12.20:通过编程API添加约束定义
ConstraintMapping constraintMapping = configuration.createConstraintMapping();

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

如果您的验证器实现相当简单(即不需要从注释中初始化),并且ConstraintValidatorContext),还可以使用此替代API使用Lambda表达式或方法引用指定约束逻辑:

示例12.21:使用Lambda表达式添加约束定义
ConstraintMapping constraintMapping = configuration.createConstraintMapping();

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

不直接向配置对象添加约束映射,您可以使用ConstraintMappingContributor详见第12.5节,“将编程约束声明应用于默认验证器工厂”。当配置默认验证器工厂时,这将非常有用。Meta-INF/validation.xml(见第八章,通过XML配置).

 

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

使用编程约束声明API注册基于正则表达式的约束定义@URL
ConstraintMapping constraintMapping = configuration.createConstraintMapping();

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

12.16。自定义类加载

在几种情况下,Hibernate Validator需要加载资源或按名称指定的类:

  • XML描述符(Meta-INF/validation.xml以及XML约束映射)

  • 按名称在XML描述符中指定的类(例如,自定义消息内插器等)

  • 这个验证信息资源束

  • 这个ExpressionFactory基于表达式的消息插值实现

默认情况下,Hibernate Validator尝试通过当前线程上下文类加载器加载这些资源。如果这不成功,Hibernate Validator自己的类加载器将被作为后盾进行尝试。

对于这种策略不合适的情况(例如模块化环境,如OSGi),您可以提供一个特定的类加载器,用于在引导验证器工厂时加载这些资源:

示例12.22:提供用于加载外部资源和类的类加载器
Validator validator = Validation.byProvider( HibernateValidator.class )
        .configure()
        .externalClassLoader( classLoader )
        .buildValidatorFactory()
        .getValidator();

在OSGi的情况下,您可以将类的装入器从包引导Hibernate Validator或委托给Bundle#loadClass()等。

 

打电话ValidatorFactory#close()如果不再需要给定的验证器工厂实例。如果应用程序/包被重新部署,并且应用程序代码仍然引用一个非关闭的验证器工厂,那么如果不这样做,可能会导致类加载程序泄漏。

12.17.自定义getter属性选择策略

当一个bean被Hibernate Validator验证时,它的属性将被验证。属性可以是字段,也可以是getter。默认情况下,Hibernate Validator尊重JavaBeans规范,并在下列条件之一为真时将方法视为getter:

  • 方法名以get,它具有非空返回类型,没有参数;

  • 方法名以is,返回类型为boolean没有参数;

  • 方法名以has,返回类型为boolean并且没有参数(此规则特定于Hibernate Validator,JavaBeans规范没有强制要求)

虽然这些规则在遵循经典JavaBeans约定时通常是合适的,但可能会发生这样的情况,特别是在代码生成器中,JavaBeans的命名约定没有得到遵守,而getter的名称遵循的是不同的约定。

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

这个要求的一个典型例子是当类遵循流利的命名约定时,如示例12.23,“使用非标准getter的类”.

示例12.23:使用非标准getter的类
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执行任何验证,因为它们没有被标准策略检测到。

示例12.24:使用默认的getter属性选择策略使用非标准getter验证类
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 Validator将这些方法视为属性,则需要自定义GetterPropertySelectionStrategy应该配置。在这种情况下,战略的可能执行是:

例12.25:自定义GetterPropertySelectionStrategy实施
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 Set<String> getGetterMethodNameCandidates(String propertyName) {
        // As method name == property name, there always is just one possible name for a method
        return Collections.singleton( propertyName );
    }
}

有多种方法可以配置Hibernate Validator来使用此策略。可以通过编程方式完成(请参阅示例12.26,“配置自定义GetterPropertySelectionStrategy以编程方式“)或使用hibernate.validator.getter_property_selection_strategy属性在XML配置中(请参见示例12.27,“配置自定义GetterPropertySelectionStrategy使用XML属性“).

示例12.26:配置自定义GetterPropertySelectionStrategy以编程方式
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:配置自定义GetterPropertySelectionStrategy使用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.getter_property_selection_strategy">
        org.hibernate.validator.referenceguide.chapter12.getterselectionstrategy.NoPrefixGetterPropertySelectionStrategy
    </property>

</validation-config>
 

必须指出的是,在使用HibernateValidatorConfiguration#addMapping(ConstraintMapping),应该始终在配置所需的getter属性选择策略之后进行添加映射。否则,默认策略将用于在定义策略之前添加的映射。

12.18。自定义约束冲突的属性名称解析

假设我们有一个简单的数据类@NotNull对某些领域的限制:

例12.28:Person数据类
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;
    }
}

类可以使用杰克逊图书馆:

示例12.29:将Person对象序列化为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 );
    }
}

如我们所见,该对象被序列化为:

例12.30:Person作为json
{
  "first_name": "Clark",
  "last_name": "Kent"
}

注意属性的名称是如何不同的。在Java对象中,我们有firstNamelastName,而在JSON输出中,我们有first_namelast_name。我们通过@JsonProperty注释。

现在假设我们在REST环境中使用这个类,用户可以在其中发送Person实例作为JSON在请求体中。在指示验证失败的哪个字段时,最好指出他们在JSON请求中使用的名称,first_name,而不是我们内部在Java代码中使用的名称,firstName.

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

如何这样做的一个例子是利用Jackson API:

例12.31:JacksonPropertyNodeNameProvider实现
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();
    }
}

在进行验证时:

例12.32:JacksonPropertyNodeNameProvider用法
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.

请注意,当注释在getter上时,这也可以:

示例12.33: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;
    }
}

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

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

还有两个接口值得一提:

  • org.hibernate.validator.spi.nodenameprovider.Property保存有关属性的元数据的基本接口。它有一个单曲String getName()方法,可用于获取属性的“原始”名称。此接口应用作解析名称的默认方法(请参见例12.31,“JacksonPropertyNodeNameProvider实现”).

  • org.hibernate.validator.spi.nodenameprovider.JavaBeanProperty保存关于bean属性的元数据的接口。它扩展了org.hibernate.validator.spi.nodenameprovider.Property并提供一些其他方法,如Class<?> getDeclaringClass()返回属性所有者的类。

13.注释处理器

你有没有无意中做过这样的事?

  • 在不支持的数据类型中指定约束注释(例如,用@Past)

  • 注释JavaBeans属性的setter(而不是getter方法)

  • 用约束注释注释静态字段/方法(不支持)?

然后Hibernate Validator注释处理器对您来说是正确的。它通过插入构建过程并在不正确使用约束注释时引发编译错误来帮助防止此类错误。

 

您可以在SourceForge或者在通常的Maven存储库中,比如Gav下的Maven Centralorg.hibernate.validator:hibernate-validator-annotation-processor:7.0.1.Final.

13.1.先决条件

Hibernate Validator注释处理器基于“可插入注释处理API”,由JSR 269它是Java平台的一部分。

13.2.特征

从Hibernate Validator 7.0.1开始,Hibernate Validator注释处理器检查:

  • 对于带注释的元素的类型,允许使用约束注释。

  • 只有非静态字段或方法使用约束注释进行注释。

  • 只有非原始字段或方法被注释为@Valid

  • 只有这些方法使用约束注释进行注释,而约束注释是有效的JavaBeans getter方法(可选,参见下面)

  • 只有这样的注释类型使用约束注释进行注释,约束注释本身就是约束注释。

  • 动态缺省组序列的定义@GroupSequenceProvider是有效的

  • 注释参数值是有意义和有效的。

  • 继承层次结构中的方法参数约束遵守继承规则。

  • 继承层次结构中的方法返回值约束尊重继承规则。

13.3.备选方案

使用以下方法可以控制Hibernate Validator注释处理器的行为处理器选项:

diagnosticKind

控制如何报告约束问题。必须是枚举值之一的字符串表示形式。javax.tools.Diagnostic.Kind,G.WARNING。价值ERROR将导致编译在AP检测到约束问题时停止。默认为ERROR.

methodConstraintsSupported

控制在任何类型的方法中是否允许使用约束。必须设置为true当使用Hibernate Validator支持的方法级约束时。可以设置为false只允许在JavaBeans中使用由JakartaBean验证API定义的getter方法。默认为true.

verbose

控制是否应显示详细的处理信息,用于调试目的。一定是truefalse。默认为false.

13.4.使用注释处理器

本节详细说明如何将Hibernate Validator注释处理器集成到命令行构建(Maven、Ant、javac)以及基于IDE的构建(Eclipse、IntelliJ IDEA、NetBeans)中。

13.4.1.命令行构建

13.4.1.1.马文

若要在Maven中使用Hibernate Validator注释处理器,请通过annotationProcessorPaths这样的选择:

示例13.1:在Maven中使用HV注释处理器
<project>
    [...]
    <build>
        [...]
        <plugins>
            [...]
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.hibernate.validator</groupId>
                            <artifactId>hibernate-validator-annotation-processor</artifactId>
                            <version>7.0.1.Final</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            [...]
        </plugins>
        [...]
    </build>
    [...]
</project>
13.4.1.2.梯度

使用时梯度将注释处理器引用为annotationProcessor依赖。

示例13.2:使用带Gradle的注释处理器
dependencies {
    annotationProcessor group: 'org.hibernate.validator', name: 'hibernate-validator-annotation-processor', version: '7.0.1.Final'

    // any other dependencies ...
}
13.4.1.3.Apache Ant

类似于直接使用javac,在调用Javac任务Apache Ant:

示例13.3:将注释处理器与Ant一起使用
<javac srcdir="src/main"
       destdir="build/classes"
       classpath="/path/to/validation-api-3.0.0.jar">
       <compilerarg value="-processorpath" />
       <compilerarg value="/path/to/hibernate-validator-annotation-processor-7.0.1.Final.jar"/>
</javac>
13.4.1.4.javac

在命令行上编译时,使用javac,指定JARHibernate-validator-annotation-processor-7.0.1.Final.jar使用“processorpath”选项,如下面的清单所示。编译器将自动检测到处理器,并在编译期间调用处理器。

示例13.4:将注释处理器与javac一起使用
javac src/main/java/org/hibernate/validator/ap/demo/Car.java \
   -cp /path/to/validation-api-3.0.0.jar \
   -processorpath /path/to/hibernate-validator-annotation-processor-7.0.1.Final.jar

13.4.2.IDE构建

13.4.2.1.月食

注释处理器将自动为上述配置的Maven项目设置,只要您有M2E Eclipse插件安装好了。

对于普通Eclipse项目,请按照以下步骤设置注释处理器:

  • 右键单击项目,选择“属性”。

  • 转到“JavaCompiler”,并确保将“编译器遵从级别”设置为“1.8”。否则处理器将不会被激活

  • 转到“Java编译器-注释处理”并选择“启用注释处理”。

  • 转到“JavaCompiler-注释处理-工厂路径”并添加JAR hibernate-validator-annotation-processor-7.0.1.Final.jar

  • 确认工作区重建

现在,您应该在编辑器中和“Problem”视图中将任何注释问题视为常规错误标记:

annotation processor eclipse
13.4.2.2.IntelliJ理念

必须遵循以下步骤才能在IntelliJ理念(第9版及以上):

  • 转到“文件”,然后“设置”,

  • 展开节点“Compiler”,然后展开“注释处理器”

  • 选择“启用注释处理”并以“处理器路径”输入以下内容:/path/to/hibernate-validator-annotation-processor-7.0.1.Final.jar

  • 将处理器的完全限定名org.hibernate.validator.ap.ConstraintValidationProcessor添加到“注释处理器”列表

  • 如果适用的话,将您的模块添加到“处理模块”列表中

然后,重新构建项目应该显示任何错误的约束注释:

annotation processor intellij
13.4.2.3.NetBeans

这个NetBeansIDE支持在IDE构建中使用注释处理器。要做到这一点,请执行以下操作:

  • 右键单击项目,选择“属性”。

  • 转到“库”,标签“处理器”,并添加JAR hibernate-validator-annotation-processor-7.0.1.Final.jar

  • 转到“生成编译”,选择“启用注释处理”和“在编辑器中启用注释处理”。通过指定注释处理器的完全限定名org.hibernate.validator.ap.ConstraintValidationProcessor来添加注释处理器

然后,在编辑器中直接标记任何约束注释问题:

annotation processor netbeans

13.5.已知问题

截至2017年7月,存在下列已知问题:

  • 目前不支持容器元素约束。

  • 约束应用于容器,但实际上应用于容器元素(通过Unwrapping.Unwrap有效载荷或通过标记为@UnwrapByDefault)不正确地支持。

  • HV-308*为约束注册的其他验证器使用XML未由注释处理器计算。

  • 有时自定义约束不能正确评价在Eclipse中使用处理器时。在这些情况下,清理项目会有所帮助。这似乎是EclipseJSR 269 API实现中的一个问题,但这里还需要进一步研究。

  • 在Eclipse中使用处理器时,动态默认组序列定义的检查不起作用。经过进一步研究,EclipseJSR269API实现似乎是一个问题。

14.进一步阅读

最后但并非最不重要的是,一些指向进一步信息的提示。

示例的一个很好的来源是JakartaBean验证TCK,它可用于匿名访问GitHub。特别是TCK的试验可能很有趣。JakartaBean验证规范本身也是加深您对JakartaBean验证和Hibernate Validator的理解的好方法。

如果您对Hibernate Validator有任何进一步的疑问,或者希望共享您的一些用例,请查看Hibernate Validator WikiHibernate Validator论坛堆栈溢出上的Hibernate Validator标记.

如果您想报告错误使用Hibernate‘s Jira举个例子。欢迎反馈!

Driver 
posted @ 2021-03-29 10:02  一品堂.技术学习笔记  阅读(41)  评论(0编辑  收藏