一、声明和验证Bean约束
1. 声明Bean约束
1.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...
}
1.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(message = "制造商不能为空")
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;
}
}
1.3 类及约束
package org.hibernate.validator.referenceguide.chapter02.classlevel;
@ValidPassengerCount
public class Car {
private int seatCount;
private List<Person> passengers;
//...
}
1.4 约束继承
- 当一个类实现一个接口或扩展另一个类时,所有在超类型上声明的约束注释都
将应用到扩展类上
- 如果方法被覆盖,约束注释将被聚合(叠加)
1.5 对象图
- Bean验证API不仅允许验证单个类实例,还允许验证完整的对象图(级联验证)。为此,只需用@Valid注释表示对另一个对象引用的字段或属性,如级联验证中所示
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对象也将被验证,因为驱动程
序字段被标注为@Valid。因此,如果引用的Person实例的name字段为null,则Car
的验证将失败
对象图的验证是递归的,也就是说,如果为级联验证标记的引用指向本身具有
@Valid注释的属性的对象,那么验证引擎也将跟踪这些引用。验证引擎将确保级联
验证期间不会出现无限循环,例如,如果两个对象彼此持有引用
- 对象图验证也适用于集合类型的字段
arrays
implement java.lang.Iterable (especially Collection, List and Set)
implement java.util.Map
可使用@Valid进行注释,这将导致在验证父对象时对每个包含的元素进行验证
2. 验证Bean约束
2.1 获取验证器实例
验证实体实例的第一步是获取验证器实例。通过Validation类和ValidatorFactory。
最简单的方法是使用静态方法Validation#buildDefaultValidatorFactory(),eg:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
2.2 验证器方法(Validator方法)
Validator接口包含三个方法,可用于验证整个实体或实体的单个属性
这三个方法都返回一个Set< ConstraintViolation>。如果验证成功,则该集合为
空。否则,将为每个被违反的约束添加一个ConstraintViolation实例
所有验证方法都有一个var-args参数,可用于指定执行验证时应考虑哪些验证组。
如果没有指定该参数,则使用默认验证组(javax.validation.groups.Default)
- Validator#validate():检验Bean
//获取验证器实例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
final Validator validator = factory.getValidator();
Car car = new Car( null, true );//Car类参见1.2 属性级约束
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
System.out.println(constraintViolations.size());//1
//制造商不能为空
System.out.println(constraintViolations.iterator().next().getMessage());
- Validator#validateProperty()
在validateProperty()的帮助下,您可以验证给定对象的单个命名属性。属
性名是javabean属性名
final Validator validator = getValidator();
Car car = new Car( null, true );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validateProperty(
car,
"manufacturer"
);
System.out.println(constraintViolations.size());//1
//制造商不能为空
System.out.println(constraintViolations.iterator().next().getMessage());
- Validator#validateValue()
通过使用validateValue()方法,您可以检查给定类的单个属性是否可以成
功验证(如果该属性具有指定的值)
final Validator validator = getValidator();
Set<ConstraintViolation<Car>> constraintViolations =
validator.validateValue(
Car.class,
"manufacturer",
null
);
System.out.println(constraintViolations.size());//1
//制造商不能为空
System.out.println(constraintViolations.iterator().next().getMessage());
2.3 约束违反方法(ConstraintViolation methods)
- getMessage():获取内置的错误信息
- getMessageTemplate():获取非内置的错误信息
- getRootBean():获取正在接受验证的对象
- getRootBeanClass():获取正在接受验证的对象的类
- getLeafBean():如果一个类级约束,则返回该约束应用于bean实例;如果是属
性级约束,则返回承载该约束的属性的bean实例
- getPropertyPath():获取验证失败的属性名称
- getInvalidValue():获取未通过验证的属性值
- getConstraintDescriptor():获取约束元数据描述符
2.4 内置约束
Bean验证约束都适用于字段/属性级别,Bean验证规范中没有定义类级别约束
- @AssertFalse:验证被注解元素是false
- @AssertTrue:验证被注解元素是true
- @DecimalMax(value=, inclusive=):验证小于或等于(取决于inclusive)
value,支持BigDecimal, BigInteger, CharSequence, byte, short, int, long以
及它们的包装类
- @DecimalMin(value=, inclusive=)
- @Digits(integer=, fraction=):验证被注解元素具有最多的整数位数和小数位数
- @Future:Checks whether the annotated date is in the future
- @Past
- @Max(value=):验证被注解元素是否小于或等于value
- @Min(value=)
- @NotNull:Checks that the annotated value is not null,Supported data
types:Any type
- @Null
- @Pattern(regex=, flags=):检查带注释的字符串是否与正则表达式匹配(考虑
给定的标志匹配)
- @Size(min=, max=):检查带注释的元素的大小是否在min和max之间(包括),
支持类型:CharSequence, Collection, Map and arrays
- @Valid:递归地对关联对象执行验证。如果对象是集合或数组,则递归地验证
元素。如果对象是Map,则递归地验证值元素
2.5 附加约束
- @Length(min=, max=):验证字符序列在min和max之间(含)
- @NotBlank:验证字符串是否为“ ”或null或""
- @NotEmpty:验证CharSequence, Collection, Map and arrays是否为null或empty
- @Range(min=, max=):Checks whether the annotated value lies between (inclusive) the specified minimum and maximum;Supported data types:
BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective
wrappers of the primitive types
- @ScriptAssert(lang=, script=, alias=, reportOn=):检查给定脚本是否可以根据带注释的元素成功计算。为了使用这个约束,JSR 223定义的Java脚本API的实现(“针对JavaTM平台的脚本”)必须是类路径的一部分。要计算的表达式可以用任何脚本或表达式语言编写,在类路径中可以找到JSR 223兼容的引擎。即使这是一个类级约束,也可以使用reportOn属性报告对特定属性而不是整个对象的约束违反
@ScriptAssert.List({
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.tpl != null && _.tpl > 0 ? _.tplQuota != null : true",
message = "第三者责任险额度未选择"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.rat != null && _.rat > 0 ? _.vdi != null && _.vdi > 0 : true",
message = "购买无法找到第三者特约险,必须同时购买车辆损失险"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.uft != null && _.uft > 0 ? _.vdi != null && _.vdi > 0 : true",
message = "购买全车盗抢险,必须同时购买车辆损失险"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.gbr != null && _.gbr > 0 ? _.vdi != null && _.vdi > 0 : true",
message = "购买玻璃单独破碎险,必须同时购买车辆损失险"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.gbr != null && _.gbr > 0 ? _.gbrGlassType != null : true",
message = "玻璃单独破碎险,玻璃类型未选择"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.blb != null && _.blb > 0 ? _.tpl != null && _.tpl > 0 : true",
message = "购买无过责任险,必须同时购买第三者责任险"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.bsr != null && _.bsr > 0 ? _.vdi != null && _.vdi > 0 : true",
message = "购买车身划痕险,必须同时购买车辆损失险"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.bsr != null && _.bsr > 0 ? _.bsrQuota != null : true",
message = "车身划痕险额度未选择"),
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.wad != null && _.wad > 0 ? _.vdi != null && _.vdi > 0 : true",
message = "购买涉水险/发动机特别损失险,必须同时购买车辆损失险")
})
二、声明和验证方法约束
从Bean Validation 1.1开始,约束不仅可以应用于javabean及其属性,还可以应用于任何Java类型的方法和构造函数的参数和返回值。这样就可以使用Bean验证约束来指定
1.声明方法约束
1.1 参数约束
通过向其参数添加约束注释来指定方法或构造函数的前提条件
public class RentalStation {
public RentalStation(@NotNull String name) {
//...
}
public void rentCar(
@NotNull Customer customer,
@NotNull @Future Date startDate,
@Min(1) int durationInDays) {
//...
}
}
约束只能应用于实例方法,即不支持对静态方法声明约束
1.2 交叉参数约束
有时,验证不仅取决于单个参数,还取决于方法或构造函数的几个甚至所有参数。可以借助交叉参数约束来满足这种要求。可以将交叉参数约束视为等同于类级约束的方法验证。两者都可用于实现基于多个元素的验证要求。虽然类级约束适用于bean的多个属性,但交叉参数约束适用于可执行文件的多个参数。例如:
在load()方法上声明的交叉参数约束用于确保没有乘客有两件以上的行李
public class Car {
@LugCountMatchesPassengerCount(piecesOfLugPerPassenger = 2)
public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
//...
}
}
返回值约束也在方法级别上声明。为了区分交叉参数约束和返回值约束,ConstraintValidator使用@SupportedValidationTarget注释在实现中配置约束目标
约束可以应用于可执行文件的参数(即,它是交叉参数约束),但也可以应用于返回值。一个例子是自定义约束,它允许使用表达式或脚本语言指定验证规则。类约束必须定义validationAppliesTo()可在声明时使用以指定约束目标的成员。如指定约束的目标所示,通过指定将约束应用于可执行文件的参数 validationAppliesTo = ConstraintTarget.PARAMETERS,同时ConstraintTarget.RETURN_VALUE用于将约束应用于可执行返回值。
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;
}
}
1.3 返回值约束
通过向可执行文件添加约束注释来声明方法或构造函数的后置条件
public class RentalStation {
@ValidRentalStation
public RentalStation() {
//...
}
@NotNull
@Size(min = 1)
public List<Customer> getCustomers() {
//...
return null;
}
}
1.4 级联验证
@Valid注释可用于标记可执行参数并返回级联验证的值。验证带有注释@Valid的参数或返回值时,也会验证在参数或返回值对象上声明的约束
public class Garage {
@NotNull
private String name;
@Valid
public Garage(String name) {
this.name = name;
}
public boolean checkCar(@Valid @NotNull Car car) {
//...
return false;
}
}
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
级联验证不仅可以应用于简单对象引用,还可以应用于集合类型参数和返回值
is an array or implements java.lang.Iterable or implements java.util.Map
1.5 继承层次结构中的方法约束(todo 2019/05/12 23:44 )
2.验证方法约束
方法约束的验证是使用ExecutableValidator接口完成的
2.1 获取ExecutableValidator实例
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator =
factory.getValidator().forExecutables();
2.2 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()
.getConstraintDescriptor()
.getAnnotation()
.annotationType();
assertEquals( Max.class, constraintType );
- 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 );
- 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 );
- validateConstructorReturnValue():验证构造函数的返回值
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. 内置方法约束
- @ParameterScriptAssert:通用的跨参数限制
public class Car {
@ParameterScriptAssert(lang = "javascript", script = "arg1.size() <=
arg0.size() * 2")
public void load(List<Person> passengers, List<PieceOfLuggage>
luggage)
{
//...
}
}
三、分组验证
1. Requesting groups
//人类
public class Person {
@NotNull
private String name;
public Person(String name) {
this.name = name;
}
// getters and setters ...
}
//司机
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;
}
}
//司机校验组
public interface DriverChecks {
}
//车辆
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 ...
}
//车辆校验组
public interface CarChecks {
}
Person.name,Car.manufacturer,Car.licensePlate和Car.seatCount 都属于
Default组
Driver.age和Driver.hasDrivingLicense属于DriverChecks
Car.passedVehicleInspection属于该组CarChecks
2. Group inheritance(组继承)
在使用验证组时,我们需要调用validate()每个验证组,或逐个指定所有验证
组。在某些情况下,您可能希望定义一组包含另一个组的约束。您可以使用组继承
来实现。
public class SuperCar extends Car {
@AssertTrue(
message = "Race car must have a safety belt",
groups = RaceCarChecks.class
)
private boolean safetyBelt;
// getters and setters ...
}
import javax.validation.groups.Default;
public interface RaceCarChecks extends Default {
}
3. 定义组序列
为了实现这样的验证顺序,只需要定义一个接口并对其进行注解@GroupSequence
@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })
public interface OrderedChecks {
}