有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步, 认准 https://blog.zysicyj.top
一、声明 Bean 约束
1. 字段级别约束
- 不支持静态类型字段
- 验证引擎直接访问实例变量,不会调用属性的访问器
- 在验证字节码增强的对象时,应适用属性级别约束,因为字节码增库无法通过反射确定字段访问
1 2 3 4 5 6 7 8 9 10 11 12
| 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; } }
|
2. 属性级别约束
- 必须注释 getter 而不是 setter,这样可以限制没有设置方法的只读属性
- 该级别将使用属性访问策略来访问验证的值,即验证引擎通过属性访问器来访问数据
- 不要字段和 getter 都加校验,这样会导致校验两次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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; } }
|
- 容器元素约束
3.1 Iterable
在该类型上加约束时,将会校验每个元素
1 2 3 4 5 6 7 8 9 10
| 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); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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() );
|
3.2 List
也会校验每个元素
1 2 3 4 5 6 7 8
| 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); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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() );
|
3.3 Map
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package org.hibernate.validator.referenceguide.chapter02.containerelement.map; import java.util.HashMap; import java.util.Map; import javax.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); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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() );
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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() );
|
3.4 Optional
1 2 3 4 5 6 7 8
| 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); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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() );
|
3.5 自定义容器
1 2 3 4 5 6 7 8
| 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; } }
|
1 2 3 4 5 6 7 8 9 10
| 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; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 ); } } }
|
1 2 3 4 5 6 7
| 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()); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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() );
|
3.6 嵌套容器元素
1 2 3 4 5 6 7 8 9 10
| package org.hibernate.validator.referenceguide.chapter02.containerelement.nested; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.validation.constraints.NotNull; public class Car { private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers = new HashMap<>(); }
|
4. 类级别约束
- 在这种情况下,验证的对象不是单个属性而是完整的对象
- 适合依赖于对象的多个属性之间的相关性很高的场景
1 2 3 4 5 6 7
| package org.hibernate.validator.referenceguide.chapter02.classlevel; @ValidPassengerCount public class Car { private int seatCount; private List<Person> passengers; }
|
5. 约束继承
在一个类实现接口或扩展另一个类时,在超类上声明的所有约束注释都以与该类本身上指定的约束相同的方式约束
1 2 3 4 5 6 7 8 9
| package org.hibernate.validator.referenceguide.chapter02.inheritance; public class Car { private String manufacturer; @NotNull public String getManufacturer() { return manufacturer; } }
|
1 2 3 4 5 6 7 8 9
| package org.hibernate.validator.referenceguide.chapter02.inheritance; public class RentalCar extends Car { private String rentalStation; @NotNull public String getRentalStation() { return rentalStation; } }
|
- RentalCar 不仅会校验 getRentalStation,而且会校验父类的 getManufacturer
- 若继承换成接口,也是会校验超类的
6. 对象图
不仅支持单个对象校验,还支持级联验证
对象的级联校验
1 2 3 4 5 6 7
| package org.hibernate.validator.referenceguide.chapter02.objectgraph; public class Car { @NotNull @Valid private Person driver; }
|
1 2 3 4 5 6
| package org.hibernate.validator.referenceguide.chapter02.objectgraph; public class Person { @NotNull private String name; }
|
在校验 Car 的时候,会校验 Person,因此若 Car 引用的 Person 的 name 为空,则会校验失败
容器的级联校验
1 2 3 4 5 6
| 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<>(); }
|
1 2 3 4 5 6
| package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement; public class Part { @NotNull private String name; }
|
1 2 3 4 5 6
| package org.hibernate.validator.referenceguide.chapter02.objectgraph.containerelement; public class Manufacturer { @NotNull private String name; }
|
- 校验 Person 的名字是否存在为 null 的
- 校验 Part 的名字是否存在为 null 的
- 校验所有的 Manufacturer 是否存在名字为 null 的
二、验证 Bean 约束
1. 获取验证器
2. 验证的三种方式
先来个车
1 2 3 4 5 6 7 8 9 10 11
| class Car { @NotNull @Size(min = 5,max = 20) private String manufacturer; @AssertTrue private boolean isRegistered; public Car(String manufacturer, boolean isRegistered) { this.manufacturer = manufacturer; this.isRegistered = isRegistered; } }
|
Bean 全部验证
验证单个属性
对属性的值进行验证
3. 约束违规
内插的错误消息
1
| 09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 内插的错误消息:只能为true
|
非插补的错误消息
1
| 09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 非插补的错误消息:{javax.validation.constraints.AssertTrue.message}
|
正在验证的根 Bean
1
| 09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 正在验证的根 Bean:com.bm.validate.Car@7c83dc97
|
如果是 Bean 约束,则将约束应用到 Bean 实例;如果是属性约束,则是托管该约束的属性的 Bean 实例
1
| 09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 如果是 bean 约束,则将约束应用到 bean 实例;如果是属性约束,则是托管该约束的属性的 bean 实例:com.bm.validate.Car@7c83dc97
|
bean 验证器值的属性路径
1
| 09:35:00.447 [main] INFO com.bm.validate.TestValidatorBean - 根 bean 验证器值的属性路径:isRegistered
|
**报告约束失败的原数据
1
| 09:35:00.447 [main] INFO com.bm.validate.TestValidatorBean - 报告约束失败的原数据:false
|
告约束失败的元数据
1
| 09:35:00.447 [main] INFO com.bm.validate.TestValidatorBean - 报告约束失败的元数据:ConstraintDescriptorImpl{annotation=j.v.c.AssertTrue, payloads=[], hasComposingConstraints=true, isReportAsSingleInvalidConstraint=false, elementType=FIELD, definedOn=DEFINED_LOCALLY, groups=[interface javax.validation.groups.Default], attributes={groups=[Ljava.lang.Class;@60015ef5, message={javax.validation.constraints.AssertTrue.message}, payload=[Ljava.lang.Class;@2f54a33d}, constraintType=GENERIC, valueUnwrapping=DEFAULT}
|
三、内置约束
@AssertFalse
检查带注释元素的属性为 false
@AssertTrue
检查带注释元素的属性为 True
@DecimalMax(value=, inclusive=)
- inclusive 为 false,检查带注释的值是否小于指定的最大值。否则,该值是否小于等于指定的最大值
- BigDecimal,BigInteger,CharSequence,byte,short,int,long,原始数据包装类,Number,javax.money.MonetaryAmount 任意子类
@DecimalMin(value=, inclusive=)
- inclusive 为 false,检查带注释的值是否大于指定的最小值。否则,该值是否大于等于指定的最小值
- BigDecimal,BigInteger,CharSequence,byte,short,int,long,原始数据包装类,Number,javax.money.MonetaryAmount 任意子类
@Digits(integer=, fraction=)
- integer 指定整数位数限制,fraction 指定小数位数限制
- BigDecimal,BigInteger,CharSequence,byte,short,int,long,原始数据包装类,Number,javax.money.MonetaryAmount 任意子类
@Email
- 是否为有效的电子邮箱地址
- regexp 和 flags 参数指定正则规则,必须匹配的其它表达式
- CharSequence
@Future
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate;
如果类路径上有 Joda Time 日期 / 时间 API,则由 HV 额外支持:ReadablePartial 和的任何实现 ReadableInstant
@FutureOnPresent
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate;
如果类路径上有 Joda Time 日期 / 时间 API,则由 HV 额外支持:ReadablePartial 和的任何实现 ReadableInstant
@Max(value=)
- 是否小于或等于该值
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@Min(value=)
- 是否大于或等于该值
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@NotBlank
- 指定字符不为 null 并且长度大于 0
- CharSequence
@NotEmpty
- 指定字符不为 null 或为空(去除尾随空格)
- CharSequence,Collection,Map 和数组
@NotNull
@Negative
- 检查元素是否严格为负,零被视为无效
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@NegativeOrZero
- 检查元素是负数或 0
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@Null
@Past
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate;
如果类路径上有 Joda Time 日期 / 时间 API,则由 HV 附加支持:ReadablePartial 和的任何实现 ReadableInstant
@PastOrPresent
java.util.Date,java.util.Calendar,java.time.Instant,java.time.LocalDate,java.time.LocalDateTime,java.time.LocalTime,java.time.MonthDay,java.time.OffsetDateTime,java.time.OffsetTime,java.time.Year,java.time.YearMonth,java.time.ZonedDateTime,java.time.chrono.HijrahDate,java.time.chrono.JapaneseDate,java.time.chrono.MinguoDate,java.time.chrono.ThaiBuddhistDate;
如果类路径上有 Joda Time 日期 / 时间 API,则由 HV 附加支持:ReadablePartial 和的任何实现 ReadableInstant
@Pattern(regex=, flags=)
- regex 考虑给定标志,检查带注释的字符串是否与正则表达式匹配 match
- CharSequence
@Positive
- 检查元素是否严格为正。零值被视为无效
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@PositiveOrZero
- 检查元素是否严格为正或零
- BigDecimal,BigInteger,byte,short,int,long 和原始类型的相应的包装;
HV 额外支持:的任何子类型 CharSequence(评估字符序列表示的数值),Number 和的任何子类型 javax.money.MonetaryAmount
@Size(min=, max=)
- 检查带注释的元素的大小是否介于 min 和之间 max(包括)
- CharSequence,Collection,Map 和数组
@CreditCardNumber(ignoreNonDigitCharacters=)
- 检查带注释的字符序列是否通过了 Luhn 校验和测试
- ignoreNonDigitCharacters 允许忽略非数字字符。默认值为 false。
- CharSequence
@Currency(value=)
- 检查带注释的货币单位 javax.money.MonetaryAmount 是否为指定货币单位的一部分。
- javax.money.MonetaryAmount
@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)
- 检查带注释的 java.time.Duration 元素不大于由注释参数构造的元素。如果将 inclusiveflag 设置为,则允许平等 true
- java.time.Duration
@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=)
- 检查带注释的 java.time.Duration 元素不少于由注释参数构造的元素。如果将 inclusiveflag 设置为,则允许平等 true。
- java.time.Duration
@EAN
- 检查带注释的字符序列是有效的 EAN 条形码。类型决定条形码的类型
- CharSequence
@ISBN
- 检查带注释的字符序列是有效的 ISBN
- CharSequence
@Length(min=, max=)
- 验证该注释字符序列是间 min 和 max 包含
- CharSequence
@Range(min=, max=)
- 检查带注释的值是否介于(包括)指定的最小值和最大值之间
- BigDecimal,BigInteger,CharSequence,byte,short,int,long 和原始类型的相应的包装
@UniqueElements
- 检查带注释的集合仅包含唯一元素。使用该 equals()
方法确定相等性。默认消息不包括重复元素的列表,但是您可以通过覆盖消息并使用{duplicates}message 参数来包括它。重复元素的列表也包含在约束违反的动态有效负载中。
- Collection��负载中。
- Collection