目录

Hibernate Validator

Hibernate Validator 是 Jakarta Bean Validation 的参考实现。为实体和方法验证定义了元数据模型和 API。默认元数据源是注解,能够通过使用 XML 覆盖和扩展元数据。API 不依赖于特定的应用程序层或编程模型。

[ˈhaɪbərneɪt] [‘vɑlɪˌdeɪtə]

Hibernate validator

1
2
3
4
// https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api
implementation 'jakarta.validation:jakarta.validation-api:3.0.1'
// https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator
implementation 'org.hibernate.validator:hibernate-validator:7.0.4.Final'

org.hibernate.validator.constraintsjakarta.validation-api 的实现,以及一些校验注解

除了 Jakarta Bean Validation API 定义的约束之外,Hibernate Validator 还提供了几个有用的自定义约束:

  • @CreditCardNumber 此验证旨在检查用户错误,而不是信用卡有效性!
  • @Email 新版本标记删除 validation-api 已经包含
  • @Length 验证带注释的字符序列是否介于min和max包含之间
  • @Range 检查注释值是否介于(包括)指定的最小值和最大值之间
  • @URL 根据 RFC2396 检查带注释的字符序列是否是有效的 URL。如果指定了任何可选参数或protocol,则相应的 URL 片段必须与指定的值匹配。
  • Currency 检查货币单位 javax.money.MonetaryAmount 是否是指定货币单位的一部分。

Utilities

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
public class ValidationUtil {
  /** 开启快速结束模式 failFast (true) */
  private static final Validator VALIDATOR =
      Validation.byProvider(HibernateValidator.class)
          .configure()
          .failFast(false)
          .buildValidatorFactory()
          .getValidator();
  /**
   * 校验对象
   *
   * @param t bean
   * @param groups 校验组
   * @return ValidResult
   */
  public static <T> ValidResult validateBean(T t, Class<?>... groups) {
    ValidResult result = new ValidResult();
    Set<ConstraintViolation<T>> violationSet = VALIDATOR.validate(t, groups);
    boolean hasError = violationSet != null && !violationSet.isEmpty();
    result.setHasErrors(hasError);
    if (hasError) {
      for (ConstraintViolation<T> violation : violationSet) {
        result.addError(violation.getPropertyPath().toString(), violation.getMessage());
      }
    }
    return result;
  }
  /**
   * 校验 bean 的某一个属性
   *
   * @param obj bean
   * @param propertyName 属性名称
   * @return ValidResult
   */
  public static <T> ValidResult validateProperty(T obj, String propertyName) {
    ValidResult result = new ValidResult();
    Set<ConstraintViolation<T>> violationSet = VALIDATOR.validateProperty(obj, propertyName);
    boolean hasError = violationSet != null && !violationSet.isEmpty();
    result.setHasErrors(hasError);
    if (hasError) {
      for (ConstraintViolation<T> violation : violationSet) {
        result.addError(propertyName, violation.getMessage());
      }
    }
    return result;
  }

  public static void addMessageToContext(
      ConstraintValidatorContext context, String property, String message) {
    context.disableDefaultConstraintViolation();
    context
        .buildConstraintViolationWithTemplate(message)
        .addPropertyNode(property)
        .addConstraintViolation();
  }
}

@Data
public class ValidResult {

  /** 是否有错误 */
  private boolean hasErrors;

  /** 错误信息 */
  private List<ErrorMessage> errors;

  public ValidResult() {
    this.errors = new ArrayList<>();
  }

  public boolean hasErrors() {
    return hasErrors;
  }

  public void setHasErrors(boolean hasErrors) {
    this.hasErrors = hasErrors;
  }

  /**
   * 获取所有验证信息
   *
   * @return 集合形式
   */
  public List<ErrorMessage> getAllErrors() {
    return errors;
  }
  /**
   * 获取所有验证信息
   *
   * @return 字符串形式
   */
  public String getErrors() {
    return errors.stream()
        .map(i -> String.join(":", i.getPropertyPath(), i.getMessage()))
        .collect(Collectors.joining(";"));
  }

  public void addError(String propertyName, String message) {
    this.errors.add(new ErrorMessage(propertyName, message));
  }

  @Data
  public static class ErrorMessage {

    private String propertyPath;

    private String message;

    public ErrorMessage() {}

    public ErrorMessage(String propertyPath, String message) {
      this.propertyPath = propertyPath;
      this.message = message;
    }
  }
}

@Data
@VerifyPhoneNumber
public class PhoneNumber {
  @Min(value = 0)
  private Integer areaCode;

  @Min(value = 0)
  @NotNull
  private Long phoneNumber;
}

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface VerifyPhoneNumber {
  String message() default "{constraints.phone.number.rightful}";

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

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

public class PhoneNumberValidator implements ConstraintValidator<VerifyPhoneNumber, PhoneNumber> {

  @Override
  public boolean isValid(PhoneNumber value, ConstraintValidatorContext context) {
    boolean result = false;
    try {
      final Integer areaCode = value.getAreaCode();
      final Long phoneNumber = value.getPhoneNumber();

      if (areaCode == null) {
        // 自定义验证错误信息
        ValidationUtil.addMessageToContext(
            context, "areaCode", "{javax.validation.constraints.NotNull.message}");
        return false;
      }

      if (phoneNumber == null) {
        ValidationUtil.addMessageToContext(context, "phoneNumber", "请输入参数");
        return false;
      }

      if (PhoneUtil.checkPhoneNumber(phoneNumber, areaCode)) {
        result = true;
      }
    } catch (final Exception e) {
      throw new BaseException(ResultCode.VALID_ERROR, e);
    }
    return result;
  }
}

@Data
public class Account extends PhoneNumber {}
@Data
public class RegisterRequest {
  @Valid private PhoneNumber phoneNumber;

  @NotEmpty(message = "{user.nb.password}")
  private String password;
}


  @Test
  public void verifyPhoneNumber() {
    Account account = new Account();
    account.setAreaCode(-1);
    account.setPhoneNumber(null);

    ValidResult validResult = ValidationUtil.validateBean(account);
    if (validResult.hasErrors()) {
      String errors = validResult.getErrors();
      System.out.println(errors);
    }

    PhoneNumber phoneNumber = new PhoneNumber();
    phoneNumber.setAreaCode(null);
    phoneNumber.setPhoneNumber(123L);

    ValidResult validResult1 = ValidationUtil.validateBean(phoneNumber);
    if (validResult1.hasErrors()) {
      String errors = validResult1.getErrors();
      System.out.println(errors);
    }

    RegisterRequest registerRequest = new RegisterRequest();
    registerRequest.setPhoneNumber(phoneNumber);
    validResult1 = ValidationUtil.validateBean(registerRequest);
    if (validResult1.hasErrors()) {
      String errors = validResult1.getErrors();
      System.out.println(errors);
    }
  }

应用约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    //getters and setters ...
}

验证约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class CarTest {

    private static Validator validator;

    @BeforeClass
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    public void manufacturerIsNull() {
        Car car = new Car( null, "DD-AB-123", 4 );

        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate( car );

        assertEquals( 1, constraintViolations.size() );
        assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );
    }

    @Test
    public void licensePlateTooShort() {
        Car car = new Car( "Morris", "D", 4 );

        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate( car );

        assertEquals( 1, constraintViolations.size() );
        assertEquals(
                "size must be between 2 and 14",
                constraintViolations.iterator().next().getMessage()
        );
    }

    @Test
    public void seatCountTooLow() {
        Car car = new Car( "Morris", "DD-AB-123", 1 );

        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate( car );

        assertEquals( 1, constraintViolations.size() );
        assertEquals(
                "must be greater than or equal to 2",
                constraintViolations.iterator().next().getMessage()
        );
    }

    @Test
    public void carIsValid() {
        Car car = new Car( "Morris", "DD-AB-123", 2 );

        Set<ConstraintViolation<Car>> constraintViolations =
                validator.validate( car );

        assertEquals( 0, constraintViolations.size() );
    }
}

声明与验证 bean 约束

声明 bean 约束

字段级约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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...
}

属性级约束

如果您的模型类遵循 JavaBeans 标准,则还可以注释 bean 类的属性而不是其字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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;
    }
}

必须注释属性的 getter 方法,而不是它的 setter。这样也可以限制没有设置方法的只读属性。

容器元素约束

可以直接在参数化类型的类型参数上指定约束:这些约束称为容器元素约束。

这需要 在约束定义ElementType.TYPE_USE中指定。@Target从 Jakarta Bean Validation 2.0 开始,内置的 Jakarta Bean Validation 以及 Hibernate Validator 特定的约束指定ElementType.TYPE_USE并可以直接在此上下文中使用。

Hibernate Validator 验证在以下标准 Java 容器上指定的容器元素约束:

  • java.util.Iterable的实现(例如Lists, Sets)
  • java.util.Map 的实现,支持键和值
  • java.util.Optional, java.util.OptionalInt, java.util.OptionalDouble, java.util.OptionalLong
  • JavaFX 的各种实现 javafx.beans.observable.ObservableValue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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() );
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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()
);
1
2
3
4
5
6
7
8
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
public class Car {

    private Map<@NotNull Part, List<@NotNull Manufacturer>> partManufacturers =
            new HashMap<>();
}

类级约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@ValidPassengerCount
public class Car {

    private int seatCount;

    private List<Person> passengers;
}

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { ValidPassengerCountValidator.class })
@Documented
public @interface ValidPassengerCount {

    String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." +
            "ValidPassengerCount.message}";

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

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

public class ValidPassengerCountValidator
        implements ConstraintValidator<ValidPassengerCount, Car> {

    @Override
    public void initialize(ValidPassengerCount constraintAnnotation) {
    }

    @Override
    public boolean isValid(Car car, ConstraintValidatorContext context) {
        if ( car == null ) {
            return true;
        }

        return car.getPassengers().size() <= car.getSeatCount();
    }
}

约束继承

当一个类实现一个接口或扩展另一个类时,在超类型上声明的所有约束注释都以与在类本身上指定的约束相同的方式应用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Car {

    private String manufacturer;

    @NotNull
    public String getManufacturer() {
        return manufacturer;
    }
}

public class RentalCar extends Car {

    private String rentalStation;

    @NotNull
    public String getRentalStation() {
        return rentalStation;
    }
}

对象图 Object graphs

Jakarta Bean Validation API 不仅允许验证单个类实例,还允许验证完整的对象图(级联验证)。为此,只需 @Valid 注释一个表示对另一个对象的引用的字段或属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Car {

    @NotNull
    @Valid
    private Person driver;
}

public class Person {

    @NotNull
    private String name;
}

嵌套容器元素也支持级联验证。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Car {

    private List<@NotNull @Valid Person> passengers = new ArrayList<Person>();
    private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers = new HashMap<>();
}
public class Part {

    @NotNull
    private String name;
}

public class Manufacturer {

    @NotNull
    private String name;
}

验证 bean 约束

Validator接口是 Jakarta Bean Validation 中最重要的对象。该Validator接口包含三个方法,可用于验证整个实体或仅验证实体的单个属性。

所有三个方法都返回一个 Set<ConstraintViolation>. 如果验证成功,则该集合为空。否则ConstraintViolation为每个违反的约束添加一个实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();

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;
    }
}

Car car = new Car( null, true );

Set<ConstraintViolation<Car>> constraintViolations = validator.validate( car );

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

Set<ConstraintViolation<Car>> constraintViolations = validator.validateProperty(
        car,
        "manufacturer"
);

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

Set<ConstraintViolation<Car>> constraintViolations = validator.validateValue(
        Car.class,
        "manufacturer",
        null
);

assertEquals( 1, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage() );

声明与验证方法约束

声明方法约束

参数约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class RentalStation {

    public RentalStation(@NotNull String name) {
        //...
    }

    public void rentCar(
            @NotNull Customer customer,
            @NotNull @Future Date startDate,
            @Min(1) int durationInDays) {
        //...
    }
}

跨参数约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

@ConsistentDateParameters
public void period(Date start, Date end) {
  //...
}

@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {

    String message() default "{org.hibernate.validator.referenceguide.chapter04." +
            "crossparameter.ConsistentDateParameters.message}";

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

    Class<? extends Payload>[] payload() default { };
}
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParametersValidator implements
        ConstraintValidator<ConsistentDateParameters, Object[]> {

    @Override
    public void initialize(ConsistentDateParameters constraintAnnotation) {
    }

    @Override
    public boolean isValid(Object[] value, ConstraintValidatorContext context) {
        if ( value.length != 2 ) {
            throw new IllegalArgumentException( "Illegal method signature" );
        }

        //leave null-checking to @NotNull on individual parameters
        if ( value[0] == null || value[1] == null ) {
            return true;
        }

        if ( !( value[0] instanceof Date ) || !( value[1] instanceof Date ) ) {
            throw new IllegalArgumentException(
                    "Illegal method signature, expected two " +
                            "parameters of type Date."
            );
        }

        return ( (Date) value[0] ).before( (Date) value[1] );
    }
}

返回值约束

方法或构造函数的后置条件是通过向可执行文件添加约束注释来声明的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class RentalStation {

    @ValidRentalStation
    public RentalStation() {
        //...
    }

    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getCustomers() {
        //...
        return null;
    }
}

级联验证

@Valid 注释可用于标记可执行参数并返回值以进行级联验证。在验证带有 注释的参数或返回值时@Valid,参数或返回值对象上声明的约束也会被验证。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 ...
}

继承层次结构中的方法约束

在继承层次结构中声明方法约束时,了解以下规则很重要:

  • 方法调用者要满足的先决条件可能不会在子类型中得到加强
  • 保证给方法调用者的后置条件在子类型中可能不会被削弱
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface Vehicle {
    void drive(@Max(75) int speedInMph);
}

public class Car implements Vehicle {

    @Override
    public void drive(@Max(55) int speedInMph) {
        //...
    }
}

验证方法约束

方法约束的验证是使用ExecutableValidator接口完成的。

通常不是 ExecutableValidator 直接从应用程序代码中调用方法,而是通过方法拦截技术(如 AOP、代理对象等)调用。

1
2
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
ExecutableValidator executableValidator = factory.getValidator().forExecutables();
  • validateParameters() 以及 validateReturnValue() 用于方法验证
  • validateConstructorParameters() 并 validateConstructorReturnValue() 用于构造函数验证
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Car {

    public Car(@NotNull String manufacturer) {
        //...
    }

    @ValidRacingCar
    public Car(String manufacturer, String team) {
        //...
    }

    public void drive(@Max(75) int speedInMph) {
        //...
    }

    @Size(min = 1)
    public List<Passenger> getPassengers() {
        //...
        return Collections.emptyList();
    }
}

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()
        .next()
        .getConstraintDescriptor()
        .getAnnotation()
        .annotationType();
assertEquals( Max.class, constraintType );

内置方法约束

Hibernate Validator 当前提供了一个方法级别约束, @ParameterScriptAssert. 这是一个通用的跨参数约束,它允许使用任何与 JSR 223 兼容的(“Java TM平台脚本”)脚本语言来实现验证例程,前提是类路径上提供了该语言的引擎。

1
2
3
4
5
6
public class Car {
    @ParameterScriptAssert(lang = "groovy", script = "luggage.size() <= passengers.size() * 2")
    public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
        //...
    }
}

分组约束

Validator 与 验证方法的 ExecutableValidator 所有方法中都有可变参数 Class<?>... groups

默认组 jakarta.validation.groups.Default

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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 {
}

定义组序列

默认情况下,约束不按特定顺序进行评估,无论它们属于哪个组。但是,在某些情况下,控制评估约束的顺序很有用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@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() );

重新定义默认组序列

@GroupSequence

除了定义组序列外,@GroupSequence注释还允许重新定义给定类的默认组。为此,只需替换 @GroupSequence的组序列中的 Default。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@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;
    }
}

public interface RentalChecks {
}

@GroupSequenceProvider

除了通过 静态重新定义默认组序列外@GroupSequence,Hibernate Validator 还提供了一个 SPI,用于根据对象状态动态重新定义默认组序列。

为此,您需要实现接口DefaultGroupSequenceProvider并通过@GroupSequenceProvider注解向目标类注册此实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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;
    }
}
@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;
    }
}

组转换

@GroupSequence({ CarChecks.class, Car.class })用于在Default组下组合与汽车相关的约束。还有一个@ConvertGroup(from = Default.class, to = DriverChecks.class) 可确保在驱动程序关联的级联验证期间将Default组转换为DriverChecks组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@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 ...
}

自定义约束

创建一个简单的约束

约束注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public enum CaseMode {
    UPPER,
    LOWER;
}
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(List.class)
public @interface CheckCase {

    String message() default "{org.hibernate.validator.referenceguide.chapter06.CheckCase." +
            "message}";

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

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

    CaseMode value();

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        CheckCase[] value();
    }
}
  • message 在违反约束的情况下返回用于创建错误消息的默认键的属性
  • groups 允许指定此约束所属的验证组的属性(请参阅第 5 章,分组约束)。这必须默认为 Class 类型的空数组。
  • payload Jakarta Bean Validation API 的客户端可以使用该属性将自定义有效负载对象分配给约束。API 本身不使用此属性。

约束注释用几个元注释装饰:

  • @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}):定义约束支持的目标元素类型。可用于字段(元素类型FIELD)、JavaBeans 属性以及方法返回值(METHOD)、方法/构造函数参数(PARAMETER)和参数化类型的类型参数(TYPE_USE)。
  • @Retention(RUNTIME):指定,这种类型的注解将在运行时通过反射的方式可用
  • @Constraint(validatedBy = CheckCaseValidator.class): 将注解类型标记为约束注解,并指定用于验证用 注释的元素的验证器@CheckCase。如果一个约束可以用于多种数据类型,则可以指定多个验证器,每个数据类型一个。
  • @Documented:说,使用@CheckCase将包含在用它注释的元素的JavaDoc中
  • @Repeatable(List.class): 表示注解可以在同一个地方重复多次,通常使用不同的配置。List是包含注释类型。

约束验证器

实现 Jakarta Bean Validation 接口 ConstraintValidator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {

    private CaseMode caseMode;

    @Override
    public void initialize(CheckCase constraintAnnotation) {
        this.caseMode = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
        if ( object == null ) {
            return true;
        }

        if ( caseMode == CaseMode.UPPER ) {
            return object.equals( object.toUpperCase() );
        }
        else {
            return object.equals( object.toLowerCase() );
        }
    }
}

错误信息

最后一个缺失的构建块是一条错误消息,应该在@CheckCase 违反约束的情况下使用。要定义它,请创建一个包含以下内容的文件 ValidationMessages.properties

使用约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Car {

    @NotNull
    private String manufacturer;

    @NotNull
    @Size(min = 2, max = 14)
    @CheckCase(CaseMode.UPPER)
    private String licensePlate;

    @Min(2)
    private int seatCount;

    public Car(String manufacturer, String licencePlate, int seatCount) {
        this.manufacturer = manufacturer;
        this.licensePlate = licencePlate;
        this.seatCount = seatCount;
    }

    //getters and setters ...
}

//invalid license plate
Car car = new Car( "Morris", "dd-ab-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
        validator.validate( car );
assertEquals( 1, constraintViolations.size() );
assertEquals(
        "Case mode must be UPPER.",
        constraintViolations.iterator().next().getMessage()
);

//valid license plate
car = new Car( "Morris", "DD-AB-123", 4 );

constraintViolations = validator.validate( car );

assertEquals( 0, constraintViolations.size() );

自定义类级约束

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { ValidPassengerCountValidator.class })
@Documented
public @interface ValidPassengerCount {

    String message() default "{org.hibernate.validator.referenceguide.chapter06.classlevel." +
            "ValidPassengerCount.message}";

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

    Class<? extends Payload>[] payload() default { };
}
package org.hibernate.validator.referenceguide.chapter06.classlevel;

public class ValidPassengerCountValidator
        implements ConstraintValidator<ValidPassengerCount, Car> {

    @Override
    public void initialize(ValidPassengerCount constraintAnnotation) {
    }

    @Override
    public boolean isValid(Car car, ConstraintValidatorContext context) {
        if ( car == null ) {
            return true;
        }

        return car.getPassengers().size() <= car.getSeatCount();
    }
}

跨参数约束

通用约束适用于带注释的元素,例如类型、字段、容器元素、方法参数或返回值等。相比之下,跨参数约束适用于方法的参数数组或构造函数,可用于表达依赖于多个参数值的验证逻辑。

为了定义一个跨参数约束,它的验证器类必须用 注释 @SupportedValidationTarget(ValidationTarget.PARAMETERS)。T接口中的 类型参数ConstraintValidator必须解析为Object或Object[]为了接收方法中的方法/构造函数参数数组 isValid()。

约束组合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@NotNull
@Size(min = 2, max = 14)
@CheckCase(CaseMode.UPPER)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
@Documented
public @interface ValidLicensePlate {

    String message() default "{org.hibernate.validator.referenceguide.chapter06." +
            "constraintcomposition.ValidLicensePlate.message}";

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

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