validator

1.入门

1.1.项目设置

要在Maven项目中使用Hibernate Validator,只需将以下依赖项添加到

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.17.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator-cdi</artifactId>
    <version>6.0.17.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.1-b09</version>
</dependency>

1.2应用约束

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

package com.sample.pass.demo.model.entity;

import lombok.Data;

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

@Data
public class Car {
    /**
     * 生产厂家
     */
    @NotNull
    private String manufacturer;
    /**
     * 牌照
     */
    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;
    /**
     * 座位数
     */
    @Min(2)
    private int seatCount;

    public Car() {
    }

    public Car(@NotNull String manufacturer, @NotNull @Size(min = 2, max = 14) String licensePlate, @Min(2) int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licensePlate;
        this.seatCount = seatCount;
    }
}

@NotNull,@Size和@Min注解用于声明汽车实例的字段中的限制:

manufacturer 绝对不能null

licensePlate 绝不能是null且必须在2到14个字符之间

seatCount 必须至少2

1.3验证

package com.sample.pass.demo.chapter01;

import com.sample.pass.demo.model.entity.Car;
import org.junit.BeforeClass;
import org.junit.Test;

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

import static org.junit.Assert.assertEquals;

public class Demo1CarTest {

    private static Validator validator;

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

    /**
     * 生产厂家为null
     */
    @Test
    public void manufacturerIsNull() {
        Car car = new Car(null, "苏A000000", 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()方法中,Validator从中检索对象ValidatorFactory。一个Validator 实例是线程安全的,可重复使用多次。因此,它可以安全地存储在静态字段中,并在测试方法中用于验证不同的Car实例。

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

在@NotNull对约束manufacturer的违反manufacturerIsNull()

在@Size对约束licensePlate的违反licensePlateTooShort()

在@Min对约束seatCount的违反seatCountTooLow()

如果对象成功验证,则validate()返回空集,如您所见carIsValid()。

请注意,仅javax.validation使用包中的类。这些是从Bean Validation API提供的。不直接引用Hibernate Validator中的类,从而产生可移植代码。

2.声明和验证bean约束

声明bean约束

bean约束有四种类型:

  • field constraints

  • property constraints

  • container element constraints

  • class constraints

2.1.1.字段级约束

例2.1:字段级约束

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.属性级约束

例2.2:属性级约束

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

使用属性级别约束时,属性访问策略用于访问要验证的值,即验证引擎通过属性访问器方法访问状态。
建议在一个模型类中,field级别的约束和property级别的约束不要同时使用,否则会导致一个属性被校验2次。

2.1.3容器元素约束

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

  • implementations of java.util.Iterable (e.g. Lists, Sets),

  • implementations of java.util.Map, with support for keys and values,

  • java.util.Optional, java.util.OptionalInt, java.util.OptionalDouble, java.util.OptionalLong,

  • the various implementations of JavaFX’s javafx.beans.observable.ObservableValue.

2.1.3.1. With Iterable
package com.sample.pass.demo.chapter02.containerelement.set;
import lombok.Data;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.HashSet;
import java.util.Set;
@Data
public class Car {
    private Set<@NotNull String> parts = new HashSet<>();

    public void addPart(String part) {
        parts.add( part );
    }
}
@Test
public void setTest() {
    Car car = new Car();
    Set<String> parts = new HashSet<>();
    parts.add("wheel");// 没问题
    parts.add("");// 没问题
    parts.add(null);// 有问题
    car.setParts(parts);

    Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
    log.info("违反的约束的个数:{}", constraintViolations.size());
    for (ConstraintViolation<Car> constraintViolation : constraintViolations) {
        log.info("违反的约束:{}, {}", constraintViolation.getExecutableReturnValue(),
                 constraintViolation.getMessage());
    }
}
2.1.3.2. With List

类似Iterable不赘述

2.1.3.3. With Map
import lombok.Data;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
@Data
public class Car {
    /**
     * 燃油消耗
     */
    public enum FuelConsumption {
        CITY,
        HIGHWAY
    }
    @Valid
    private Map<@NotNull FuelConsumption, @Max(10) Integer> fuelConsumption = new HashMap<>();

    public void setFuelConsumption(FuelConsumption consumption, int value) {
        fuelConsumption.put( consumption, value );
    }
}
@Slf4j
public class CarTest {
    private static Validator validator;

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

    @Test
    public void setTest() {
        Car car = new Car();
        car.setFuelConsumption( Car.FuelConsumption.HIGHWAY, 30 );

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

        assertEquals( 1, constraintViolations.size() );

        ConstraintViolation<Car> constraintViolation =
                constraintViolations.iterator().next();
        assertEquals(
                "最大不能超过10",
                constraintViolation.getMessage()
        );
        assertEquals(
                "fuelConsumption[HIGHWAY].<map value>",
                constraintViolation.getPropertyPath().toString()
        );
    }
    @Test
    public void setTest1() {
        Car car = new Car();
        car.setFuelConsumption( null, 0 );

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

        assertEquals( 1, constraintViolations.size() );

        ConstraintViolation<Car> constraintViolation =
                constraintViolations.iterator().next();
        assertEquals(
                "不能为null",
                constraintViolation.getMessage()
        );
        assertEquals(
                "fuelConsumption<K>[].<map key>",
                constraintViolation.getPropertyPath().toString()
        );
    }
}
2.1.3.4. With java.util.Optional

类似Iterable不赘述

2.1.3.5. 嵌套的容器元素
package com.sample.pass.demo.chapter02.containerelement.nested;

import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class Car {
    /**
     * 部件-生产厂家
     */
    private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers =
            new HashMap<>();
}
package com.sample.pass.demo.chapter02.containerelement.nested;

import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.*;

@Slf4j
public class CarTest {
    private static Validator validator;

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

    @Test
    public void setTest() {
        Car car = new Car();
        Map<Part, List<Manufacturer>> partManufacturers = new HashMap<>();
        partManufacturers.put(new Part(), Collections.singletonList(null));
        car.setPartManufacturers(partManufacturers);
        Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
        log.info("违反的约束的个数:{}", constraintViolations.size());
        for (ConstraintViolation<Car> constraintViolation : constraintViolations) {
            log.info("违反的约束:{}, {}", constraintViolation.getExecutableParameters(),
                    constraintViolation.getMessage());
        }
    }
}
2.1.4.类级约束

field级别的约束和property级别的约束都是针对单个属性的,而类级别的属性则是针对整个对象的,这在一个对象的多个属性之间具有相关性的情况下是非常有用的。

  • 对于下面这个类,我们应该确保乘客的数量不能多于座位的数量,也就是属性seatCount和passengers之间具有相关性,此时,class级别的约束就有了用武之地
@ValidPassengerCount
public class Car {
    private int seatCount;

    private List<Person> passengers;

    public int getSeatCount() {
        return seatCount;
    }

    public void setSeatCount(int seatCount) {
        this.seatCount = seatCount;
    }

    public List<Person> getPassengers() {
        return passengers;
    }

    public void setPassengers(List<Person> passengers) {
        this.passengers = passengers;
    }
}
  • 我们注意到了,在Car类上,我们使用了class级别的约束@ValidPassengerCount,这个约束是用来校验乘客数量不能超过座位数量的,具体实现如下:
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {ValidPassengerCountValidator.class})
@Documented
public @interface ValidPassengerCount {

    String message() default "乘客数量不能超过座位数量";

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

    Class<? extends Payload>[] payload() default {};
}
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();
    }
}

怎样实现自定义的校验不是本文要讲的,通过本文,我们只要了解到如果一个对象的多个属性之间有约束关系,那么我们可以使用class级别的来校验就行了。

2.1.5.约束继承

知识点有2个:

  1. 子类型可以继承父类型定义的约束
  2. 约束是累加生效的
2.1.6.级联约束(Object graphs)
public class Car {
    @NotNull
    @Valid
    private Person driver;
}

public class Person {

    @NotNull
    private String name;

}
public class Car {
    private List<@NotNull @Valid Person> passengers = new ArrayList<Person>();
    private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers = new HashMap<>();
}

public class Part {
    @NotNull
    private String name;
}

public class Manufacturer {
    @NotNull
    private String name;
}

2.2. 验证bean约束

2.2.1.获取Validator实例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
2.2.2.验证方法

Validator接口包含三种方法,可用于验证整个实体或仅实体的单个属性
这三种方法都返回了一个Set<ConstraintViolation>,如果验证成功,则该集为空。否则,ConstraintViolation为每个违反的约束添加实例
所有验证方法都有一个var-args参数,可用于指定执行验证时应考虑哪些验证组。如果未指定参数,则使用默认验证组

2.2.2.1 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()
/**
* 只校验一个参数
*/
@Test
public void validateProperty() {
    Car car = new Car( null, "D", 4);

    Set<ConstraintViolation<Car>> constraintViolations = validator.validateProperty(
        car,
        "licensePlate"
    );
    assertEquals( 1, constraintViolations.size() );
    assertEquals( "个数必须在2和14之间", constraintViolations.iterator().next().getMessage() );
}
2.2.2.3.Validator#validateValue()
/**
* 校验某个值是否符合校验规则
*/
@Test
public void validateValue() {
    Set<ConstraintViolation<Car>> constraintViolations = validator.validateValue(
        Car.class,
        "licensePlate","d"
    );
    assertEquals( 1, constraintViolations.size() );
    assertEquals( "个数必须在2和14之间", constraintViolations.iterator().next().getMessage() );
}

需要注意的是,validateProperty和validateValue方法不支持@Valid,也就是不支持级联校验。

2.2.3.ConstraintViolation
2.2.3.1.ConstraintViolation方法```java

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

getMessage()
The interpolated error message

Example
"must not be null"

getMessageTemplate()
The non-interpolated error message

Example
"{…​ NotNull.message}"

getRootBean()
The root bean being validated

Example
car

getRootBeanClass()
The class of the root bean being validated

Example
Car.class

getLeafBean()
If a bean constraint, the bean instance the constraint is applied on; if a property constraint, the bean instance hosting the property the constraint is applied on

Example
car

getPropertyPath()
The property path to the validated value from root bean

Example
contains one node with kind PROPERTY and name "manufacturer"

    getInvalidValue()
The value failing to pass the constraint

Example
null

getConstraintDescriptor()
Constraint metadata reported to fail

Example
descriptor for @NotNull

2.3.内置约束

2.3.1.Bean验证约束
@AssertFalse @AssertTrue  检验boolean类型的值

@DecimalMax @DecimalMin  限定被标注的属性的值的大小

@Digits(intege=,fraction=) 限定被标注的属性的整数位数和小数位数

@Email 邮箱

@Future检验给定的日期是否比现在晚

@Past    校验给定的日期是否比现在早

@Max检查被标注的属性的值是否小于等于给定的值

@Min检查被标注的属性的值是否大于等于给定的值

@NotNull 检验被标注的值不为空

@Null 检验被标注的值为空

@Pattern(regex=,flag=)  检查该字符串是否能够在match指定的情况下被regex定义的正则表达式匹配

@Size(min=,max=)  检查被标注元素的长度

@Valid递归的对关联的对象进行校验

@Negative 检查数字是否>0

@NegativeOrZero 检查数字是否>=0

@Range(min=, max=) 检查带注释的值是否在(包括)指定的最小值和最大值之间

@@Length(min=, max=)验证带注释的字符序列介于max min之间

3.声明和验证方法约束

这部分因为需要从架构上去做支持,不做改动。

4. 插入约束错误消息(Interpolating constraint error messages)

消息插值是为违反的Bean Validation约束创建错误消息的过程。在本章中,您将了解如何定义和解决此类消息,以及如何在默认算法不足以满足您的要求时插入自定义消息插补器。

4.1.默认消息插值

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

然后可以通过调用从结果约束违反中检索插值的错误消息ConstraintViolation#getMessage()

  • message可以使用引用约束的属性值:必须至少为${min}
4.1.1.特殊字符
  • \{被视为文字 {

  • \}被视为文字 }

  • \$被视为文字 $

  • \\被视为文字 \

4.1.2.使用消息表达式进行插值
  • 映射到属性名称的约束的属性值

  • 名为validatedValue的当前验证值(属性,bean,方法参数等)

  • 映射到名称格式化程序的bean,公开format(String format, Object…​ args)行为类似的var-arg方法java.util.Formatter.format(String format, Object…​ args)

4.1.3. Examples
package com.sample.pass.demo.chapter04.complete;

import lombok.Data;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal;

@Data
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;
    }
}

测试

package com.sample.pass.demo.chapter04.complete;

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

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.math.BigDecimal;
import java.util.Set;

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, "D", 1, 360, new BigDecimal(100001));
        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate(car);
        constraintViolations.forEach(next -> System.out.println(next.getMessage()));
    }
}

5.分组约束

5.1.请求小组

package com.sample.pass.demo.chapter05;

import lombok.Data;

import javax.validation.constraints.NotNull;
@Data
public class Person {

    @NotNull
    private String name;

    public Person(String name) {
        this.name = name;
    }
}
package com.sample.pass.demo.chapter05;

import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;

@Data
public class Driver  {
    public 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;
    }
}
package com.sample.pass.demo.chapter05;

public interface DriverChecks {
}
package com.sample.pass.demo.chapter05;

import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

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 licensePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licensePlate;
        this.seatCount = seatCount;
    }

    public String getManufacturer() {
        return manufacturer;
    }

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

    public String getLicensePlate() {
        return licensePlate;
    }

    public void setLicensePlate(String licensePlate) {
        this.licensePlate = licensePlate;
    }

    public int getSeatCount() {
        return seatCount;
    }

    public void setSeatCount(int seatCount) {
        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;
    }

}

package com.sample.pass.demo.chapter05;

public interface CarChecks {
}

例5.4:使用验证组

package com.sample.pass.demo.chapter05;

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

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import java.util.Set;

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();
    }

    /**
     * 生产厂家为null
     */
    @Test
    public void manufacturerIsNull() {
        // 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()
        );
    }
}

5.2.组继承

在某些情况下,您可能希望定义一组包含另一个组的约束。您可以使用组继承来实现。

在第一次调用时validate(),我们不指定组。有一个验证错误,因为汽车必须至少有一个座位。这是该Default组的约束。
在第二次调用时,我们仅指定组RaceCarChecks。有两个验证错误:一个是关于Default组中缺少的座位,另一个是关于组中没有安全带的事实RaceCarChecks。

package com.sample.pass.demo.chapter05.groupinheritance;

import com.sample.pass.demo.chapter05.Car;
import lombok.Data;

import javax.validation.constraints.AssertTrue;
@Data
public class SuperCar extends Car {

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

    public SuperCar(String manufacturer, String licensePlate, int seatCount) {
        super(manufacturer, licensePlate, seatCount);
    }
}
package com.sample.pass.demo.chapter05.groupinheritance;

import javax.validation.groups.Default;

public interface RaceCarChecks extends Default {
}
package com.sample.pass.demo.chapter05.groupinheritance;

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

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

import static org.assertj.core.api.Assertions.assertThat;
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 test() {
        // create a supercar and check that it's valid as a generic Car
        SuperCar superCar = new SuperCar( "Morris", "DD-AB-123", 1  );
        assertEquals( "最小不能小于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",
                "最小不能小于2"
        );
    }
}

5.3.定义组序列

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

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

例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
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 {
}
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() );
5.4.2. @GroupSequenceProvider

Hibernate Validator 提供了"根据对象状态动态重定义默认分组"的接口。

什么意思呢?考虑下面这个场景:假如一个bean有3个属性,分别是attrA, attrB和attrC。当attrA属性的值是value1,我们需要对attrB进行校验;当attrA属性的值是value2的时候,我们需要对attrC进行校验。

为了实现上面这个目的,我们需要实现DefaultGroupSequenceProvider接口,并且在被校验的bean上使用@GroupSequenceProvider来应用我们的实现。

  • 自定义分组序列提供器
public class SomeFormGroupSequenceProvider implements DefaultGroupSequenceProvider<SomeForm> {

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

        if (someForm != null && someForm.getType() == 1) {
            defaultGroupSequence.add(SomeForm.WhenTypeEqualsOne.class);
        }

        if (someForm != null && someForm.getType() == 2) {
            defaultGroupSequence.add(SomeForm.WhenTypeEqualsTwo.class);
        }

        return defaultGroupSequence;
    }
}

  • 待校验的bean
@AllArgsConstructor
@NoArgsConstructor
@Data
@GroupSequenceProvider(value = SomeFormGroupSequenceProvider.class)
public class SomeForm {

    private Integer type;

    @NotBlank
    @Size(min = 1, max = 3, groups = WhenTypeEqualsOne.class)
    private String whenTypeEqualsOne;

    @NotBlank
    @Size(min = 4, max = 6, groups = WhenTypeEqualsTwo.class)
    private String whenTypeEqualsTwo;

    interface WhenTypeEqualsOne {

    }

    interface WhenTypeEqualsTwo {

    }
}

    @Test
    public void groupValidateFifthTestVerTwo() {
        SomeForm formWithTypeEqualsOne = new SomeForm(1, "hello", "a");
        System.out.println("-------- formWithTypeEqualsOne -------- \n"
                + validator.validate(formWithTypeEqualsOne) + "\n");

        SomeForm formWithTypeEqualsTwo = new SomeForm(2, "hello", "a");
        System.out.println("-------- formWithTypeEqualsTwo -------- \n"
                + validator.validate(formWithTypeEqualsTwo) + "\n");
    }

-------- formWithTypeEqualsOne --------
[ConstraintViolationImpl{interpolatedMessage='个数必须在1和3之间', propertyPath=whenTypeEqualsOne, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.five.ver2.SomeForm, messageTemplate='{javax.validation.constraints.Size.message}'}]

-------- formWithTypeEqualsTwo --------
[ConstraintViolationImpl{interpolatedMessage='个数必须在4和6之间', propertyPath=whenTypeEqualsTwo, rootBeanClass=class com.qs.mmeng.hibernate.validator.group.five.ver2.SomeForm, messageTemplate='{javax.validation.constraints.Size.message}'}]

5.5. 组转换(Group conversion)

package com.sample.pass.demo.chapter05.groupconversion;

import com.sample.pass.demo.chapter05.CarChecks;
import com.sample.pass.demo.chapter05.DriverChecks;
import lombok.Data;

import javax.validation.GroupSequence;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.ConvertGroup;
@Data
@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(to = DriverChecks.class)
    private Driver driver;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }
}
package com.sample.pass.demo.chapter05.groupconversion;

import com.sample.pass.demo.chapter05.DriverChecks;
import lombok.Data;

import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Data
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(@NotNull String name) {
        this.name = name;
    }
}
package com.sample.pass.demo.chapter05.groupconversion;

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

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

import static org.junit.Assert.assertEquals;

public class CarTest {

    private static Validator validator;

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

    @Test
    public void test() {
        // 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"
        );
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值