乐趣区

关于后端:hibernate-validator二声明和验证Bean约束

首发博客地址

https://blog.zysicyj.top/

一、申明 bean 束缚

1. 字段级别束缚

  1. 不反对动态类型字段
  2. 验证引擎间接拜访实例变量,不会调用属性的拜访器
  3. 在验证字节码加强的对象时,应实用属性级别束缚,因为字节码增库无奈通过反射确定字段拜访
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. 必须正文 getter 而不是 setter,这样能够限度没有设置办法的只读属性
  2. 该级别将应用属性拜访策略来拜访验证的值,即验证引擎通过属性拜访器来拜访数据
  3. 不要字段和 getter 都加校验,这样会导致校验两次
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;}
}
  1. 容器元素束缚

3.1 Iterable

在该类型上加束缚时,将会校验每个元素

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

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

3.3 Map

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

3.4 Optional

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

3.5 自定义容器

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

3.6 嵌套容器元素

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. 适宜依赖于对象的多个属性之间的相关性很高的场景
package org.hibernate.validator.referenceguide.chapter02.classlevel;
@ValidPassengerCount
public class Car {
    private int seatCount;
    private List<Person> passengers;
    //...
}

5. 束缚继承

在一个类实现接口或扩大另一个类时,在超类上申明的所有束缚正文都以与该类自身上指定的束缚雷同的形式束缚

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;}
    //...
}
  1. RentalCar 不仅会校验 getRentalStation,而且会校验父类的 getManufacturer
  2. 若继承换成接口,也是会校验超类的

6. 对象图

不仅反对单个对象校验,还反对级联验证

对象的级联校验
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,因而若 Car 援用的 Person 的 name 为空,则会校验失败

容器的级联校验
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;
    //...
}
  1. 校验 Person 的名字是否存在为 null 的
  2. 校验 Part 的名字是否存在为 null 的
  3. 校验所有的 Manufacturer 是否存在名字为 null 的

二、验证 Bean 束缚

1. 获取验证器

2. 验证的三种形式

先来个车

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. 束缚违规

内插的谬误音讯

09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 内插的谬误音讯:只能为 true

非插补的谬误音讯

09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 非插补的谬误音讯:{javax.validation.constraints.AssertTrue.message}

正在验证的根 Bean

09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 正在验证的根 Bean:com.bm.validate.Car@7c83dc97

如果是 bean 束缚,则将束缚利用到 bean 实例;如果是属性束缚,则是托管该束缚的属性的 bean 实例

09:35:00.446 [main] INFO com.bm.validate.TestValidatorBean - 如果是 bean 束缚,则将束缚利用到 bean 实例;如果是属性束缚,则是托管该束缚的属性的 bean 实例:com.bm.validate.Car@7c83dc97

bean 验证器值的属性门路

09:35:00.447 [main] INFO com.bm.validate.TestValidatorBean - 根 bean 验证器值的属性门路:isRegistered

** 报告束缚失败的原数据

09:35:00.447 [main] INFO com.bm.validate.TestValidatorBean - 报告束缚失败的原数据:false

告束缚失败的元数据

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

  • Boolean, boolean

@AssertTrue

查看带正文元素的属性为 True

  • Boolean,boolean

@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

  • 查看正文的值不为 null
  • 所有类型均反对

@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

  • 查看正文的值是 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

本文由 mdnice 多平台公布

退出移动版