5.分组约束
5.1.请求组
分组可以在验证期间限制应用一组约束。目标组作为var-arg参数传递给适当的validate方法。
看一个例子,Person类的name属性有一个@NotNull约束,由于没有为此约束指定组,因此默认为javax.validation.groups.Default组 。
package org.hibernate.validator.referenceguide.chapter05;
public class Person {
@NotNull
private String name;
public Person(String name) {
this.name = name;
}
// getters and setters ...
}
Driver类继承Person类并增加了age和hasDrivingLicense属性。司机必须年满18岁(@Min(18))并有驾驶执照(@AssertTrue),这两个约束属于组DriverChecks,它只是一个简单的标记接口。
使用接口使组的类型安全,并允许轻松重构。这也意味着组可以通过类继承相互继承。请参阅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类有一些没有指定组的约束,这些是属于默认组的,还有passedVehicleInspection属性上的@AssertTrue约束是属于CarChecks组的,它指示一辆车是否通过了测试。
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.name,Car.manufacturer,Car.licensePlate和Car.seatCount的约束都属于Default组。
- Driver.age和Driver.hasDrivingLicense约束属于DriverChecks组。
- Car.passedVehicleInspection约束属于CarChecks组。
下面的例子展示如何传递给Validator#validate()方法不同的组组合,以得到不同的验证结果。
// 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()调用没有指定组,即使passedVehicleInspection的值是false,也没有出现验证错误,因为此约束不属于默认组。
下一个validate()调用指定组CarChecks,第一次验证错误,然后把passedVehicleInspection值设置成true,通过了验证。向car实例添加了一个Driver,该Driver实例的passedVehicleInspection为false,未能通过DriverChecks组的验证,只有在设置passedDrivingTest为true之后,才能通过DriverChecks组的验证。
最后一次validate()调用显示所有的约束都通过了所有组的验证。
5.2.组继承
使用验证组,我们需要为每个验证组调用validate()方法或者指定全部验证组。
在一些情况下,可能需要定义一个包含其他组的验证组,可以使用组继承。
下面的例子,在类SuperCar中,定义了一个RaceCarChecks组,它继承了Default组。它新增了一个safetyBelt(安全带)属性,表示比赛用车必须具有安全带。
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 javax.validation.groups.Default;
public interface RaceCarChecks extends Default {
}
在下面的例子中,将分别验证普通汽车跟比赛用车的约束。
// 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()方法,没有指定特定的组,使用默认组。这里有一个验证错误,因为汽车必须至少有一个座位,该约束属于默认组。
在第二次调用validate()方法,指定了RaceCarChecks组,有两个验证错误,一个是关于默认组的座位约束,一个是RaceCarChecks组的安全带约束。
5.3.定义组序列
默认的,不管约束属于哪些组,验证约束是没有特定的顺序的。但是,我们可以控制约束被验证的顺序。
下面的例子,要求在检查汽车的其它状况之前首先要通过所有默认组的约束。最后,在开车之前,应该检查驾驶员约束。
要实现这样的验证顺序,只需要通过定义接口,并使用@GroupSequence注解,以定义组验证顺序。如果在这写组中有一个约束没有通过验证,那么其它组的约束都不会被验证。
定义验证组顺序:
package org.hibernate.validator.referenceguide.chapter05;
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })
public interface OrderedChecks {
}
使用定义的组:
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 组。
下面的例子,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 {
}
通过这个定义,可以像验证默认组那样验证RentalChecks,CarChecks和RentalCar,
如下面的例子:
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
除了通过@GroupSequence静态地重定义默认组外,Hibernate Validator还提供了一个SPI,用于根据对象状态动态地重定义默认组序列。
为此,需要实现接口DefaultGroupSequenceProvider并通过@GroupSequenceProvider注解将这个实现注册到目标类。下面的例子就动态地添加CarChecks组。
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.转换组
如果想验证car的相关检查和driver的检查,可以将所需的组显式传递给validate调用,但是如果想使这些验证作为Default组验证的一部分进行,可以使用@ConvertGroup,它允许在级联验证时去使用跟原来不同的组。
下面的例子中,@GroupSequence({ CarChecks.class, Car.class })重新定义Default组;@ConvertGroup(from = Default.class, to = DriverChecks.class)确保driver在级联验证期间将该Default组转换为DriverChecks组。
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 ...
}
看下面的验证程序:
// 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异常。
转换规则不会被递归执行。这第一个匹配的规则将会被使用,后续的规则将会被忽略。