编程it互联网

Bean Validation

2019-01-21  本文已影响23人  BIN_1
1、简介

Bean Validation,是JCP(Java Community Process)定义的标准化的JavaBean校验API,基于注解,并且具有良好的易用性和扩展性;需要注意的是,Bean Validation只是一个规范和标准,并没有提供实现。

2、Maven依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
3、简单的使用
// 字段约束
@NotNull(message = "生产厂家不能为空")
private String manufacturer;
// 属性约束(如果字段也有约束的话,会重复)
@NotEmpty(message = "出租车站不能为空")
public String getRentalStation() {
    return rentalStation;
}
//类约束
@PassengerCount(value = 2, message = "你确定要超载?")
public class Car {
    ……
}
public class RentalCar extends Car {
    ……
}
@Test
public void testRentalCar() {
    // 会校验子类,同时还会校验父类,这就是约束继承,这同样适用于接口。如果子类覆盖了父类的方法,那么子类和父类的约束都会被校验。
    RentalCar rentalCar = new RentalCar(null);
    Set<ConstraintViolation<RentalCar>> constraintViolations =
            validator.validate(rentalCar);
    assertEquals(4, constraintViolations.size());
}
@PassengerCount(value = 2, message = "你确定要超载?")
public class Car {
    ……

    // valid注解会级联验证关联对象,如果对象是集合、Map、数组,也会验证其中的元素
    @Valid
    private Driver driver;

    @Valid
    private List<User> passengers;
}
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

// 校验给定对象的所有约束
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
// 校验对象的给定属性的所有约束
<T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups);
// 使用给定的属性值来校验对象的给定属性上的所有约束
<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups);

// 校验所有约束
@Test
public void testRentalCar() {
    // 会校验子类,同时还会校验父类,这就是约束继承,这同样适用于接口。如果子类覆盖了父类的方法,那么子类和父类的约束都会被校验。
    RentalCar rentalCar = new RentalCar(null);
    Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validate(rentalCar);
    assertEquals(4, constraintViolations.size());
}
// 校验属性约束
@Test
public void testPropertyValidate() {
    RentalCar rentalCar = new RentalCar(null);
    Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validateProperty(rentalCar, "rentalStation");
    assertEquals(1, constraintViolations.size());
}
// 给定属性值,校验所有属性约束
@Test
public void testPropertyValueValidate() {
    Set<ConstraintViolation<RentalCar>> constraintViolations = validator.validateValue(RentalCar.class, "rentalStation", null);
    assertEquals(1, constraintViolations.size());
}
// 获取违反约束的消息
String getMessage();
// 获取违反约束的消息模板,如{javax.validation.constraints.NotNull.message}
String getMessageTemplate();
// 获取被校验的根实体
T getRootBean();
// 获取被校验的根实体class
Class<T> getRootBeanClass();
// 如果约束是添加在一个实体对象上的,那么则返回这个对象的实例, 如果是约束是定义在一个属性上的, 则返回这个属性所属的实体对象.
Object getLeafBean();
// 获取从被验证的根对象到被验证的属性的路径.
Path getPropertyPath();
// 获取导致校验失败的值
Object getInvalidValue();
// 获取导致校验失败的约束定义
ConstraintDescriptor<?> getConstraintDescriptor();
// 汽车检测组:
public interface CarChecks {}
// 驾驶员检测组:
public interface DriverChecks {}

@Data
@PassengerCount(value = 2, message = "你确定要超载?")
public class Car {
    // 在Car类上添加一个通过上路检测的属性,定义他所属的组为CarChecks:
    @AssertTrue(message = "必须先通过上路检测", groups = CarChecks.class)
    private boolean passedVehicleInspection;
}

@Data
public class Driver {
    // 将属性加入DriverChecks组中
    @AssertTrue(message = "你想无证驾驶?", groups = DriverChecks.class)
    private boolean hasDrivingLicense;
}

// 现在可以使用组进行校验了,想要校验哪个组,就多传递一个组的参数。
// 先创建一个Car,校验其默认组属性的约束,由于passedVehicleInspection属于CarChecks组,默认组并不校验;然后再校验CarChecks组,此时会校验组内的passedVehicleInspection属性;然后创建一个Driver,他并没有通过驾照考试,所以校验DriverChecks组时不能通过;最后,当司机也通过了驾照考试后,所有条件具备,当校验所有的组时,校验成功。
@Test
public void driveAway() {
    // 通过校验,默认组,由于passedVehicleInspection属于另外的组,所以这里并不校验该属性
    Car car = new Car("ford", "川A12345", 2);
    Set<ConstraintViolation<Car>> constraintViolations = validator.validate(car);
    assertEquals(0, constraintViolations.size());
    // 未通过上路检测
    constraintViolations = validator.validate(car, CarChecks.class);
    assertEquals(1, constraintViolations.size());
    assertEquals("必须先通过上路检测", constraintViolations.iterator().next().getMessage());
    // 设置通过上路检测
    car.setPassedVehicleInspection(true);
    assertEquals(0, validator.validate(car).size());
    // 设置一个司机
    Driver john = new Driver("john", "john@123.com", 20);
    car.setDriver(john);
    constraintViolations = validator.validate(car, DriverChecks.class);
    assertEquals(1, constraintViolations.size());
    assertEquals("你想无证驾驶?", constraintViolations.iterator().next().getMessage());
    // 司机通过了驾照考试
    john.setHasDrivingLicense(true);
    assertEquals(0, validator.validate(car, DriverChecks.class).size());
    // 全部通过
    assertEquals(0, validator.validate(car, Default.class, CarChecks.class, DriverChecks.class).size());
}
4、自定义校验
@Documented
@Constraint(validatedBy = PassengerCountValidator.class)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassengerCount {
    String message() default "{com.belonk.car.passengerCount}";

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

    Class<? extends Payload>[] payload() default {};

    int value();
}
// 需要实现ConstraintValidator接口来自定义校验器:
public interface ConstraintValidator<A extends Annotation, T> {
    // 初始化一些必要的信息
    default void initialize(A constraintAnnotation) {}

    // 校验逻辑,value为被校验目标(类、属性、方法)的值
    boolean isValid(T value, ConstraintValidatorContext context);
}
com.belonk.car.passengerCount = 核载人数为{value},请勿超载
5、约束组合

有时候,在一个字段上加多个约束显得非常臃肿,不易阅读。我们可以通过组合这些约束,来自定义一个约束,从而简化编码,提高可读性

// 约束上添加需要组合的多个约束
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidNumber {
    String message() default "{com.belonk.car.no}";

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

    Class<? extends Payload>[] payload() default {};
}

// @ReportAsSingleViolation添加了这个注解后,表示不会验证所有约束,当有一个约束验证失败,则会立即返回组合约束所定义的错误信息(message属性定义),而不是单个约束本身的错误信息。
//例如:如果@NotEmpty、@Pattern都校验失败,不添加@ReportAsSingleViolation注解,则会生成两个校验失败的结果,而提示信息为@NotEmpty、@Pattern对应的错误信息;相反,如果添加了@ReportAsSingleViolation注解,当@NotEmpty校验失败时直接返回校验错误,信息为@ValidNumber定义的错误信息。
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@NotEmpty
@Size(min = 5, max = 16)
@Pattern(regexp = "^GL.*$")
// 不需要单独的验证器
@Constraint(validatedBy = {})
// 有约束校验失败立即返回,信息为组合约束定义的信息
@ReportAsSingleViolation
public @interface ValidNumber {
    String message() default "{com.belonk.car.no}";

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

    Class<? extends Payload>[] payload() default {};
}
上一篇 下一篇

猜你喜欢

热点阅读