Fellow Travellers

hibernate 参数校验

张永朋
字数统计: 4k阅读时长: 21 min
2019/08/14 Share

Hibernate Validator 6.0.17.Final - JSR

380 Reference Implementation 2019-06-13

如何阅读官方文档

Bean Validate 是规范, Hibernate Validate 是其实现和拓展

章节十四阅读更多demo

章节二声明和校验Bean 约束

章节三使用Bean约束对方法执行前后传参和返回值进行校验

章节六自定义约束

Chapter 1. Getting started

1.1. 项目启动

maven 工程 使用 Hibernate Validator只需添加如下依赖:

1
2
3
4
5
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>

1.1.1. Unified EL

Hibernate Validator 需要实现 Unified Expression Language (JSR 341), 需要解析Default message 中的动态约束信息。

对于EL表达式实现的maven依赖

1
2
3
4
5
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b09</version>
</dependency>

1.1.2. CDI

Bean Validate 定义整合CDI(Contexts and Dependency Injection for Java TM EE, JSR 346),如果工程不支持依赖注入,可以通过引入以下依赖实现。

1
2
3
4
5
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<version>6.0.17.Final</version>
</dependency>

1.2. 快速使用约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.dist.model.biz.test.hibernate.validate;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
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.3. 校验约束

使用 Validator 实例进行校验

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
package hibernate.validate;

import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

import com.dist.model.biz.test.hibernate.validate.Car;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;



public class CarTest {

private static Validator validator;
@BeforeClass
public static void setUpValidator() {
ValidatorFactory factory = Validation
.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
/**
* org.junit.ComparisonFailure:
* Expected :must not be null
* Actual :不能为null
*/
@Test
public void manufacturerIsNull() {
Car car = new Car( null, "DD-AB-123", 4 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 0, constraintViolations.size() );
assertEquals( "must not be null", constraintViolations.iterator().next().getMessage());
}
/**
* org.junit.ComparisonFailure:
* Expected :size must be between 2 and 14
* Actual :个数必须在2和14之间
*/
@Test
public void licensePlateTooShort() {
Car car = new Car( "Morris", "D", 1 );
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate( car );
assertEquals( 2, constraintViolations.size() );
assertEquals(
"size must be between 2 and 14",
constraintViolations.iterator().next().getMessage()
);
}
/**
* org.junit.ComparisonFailure:
* Expected :must be greater than or equal to 2
* Actual :最小不能小于2
*/
@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() );
}
}

Chapter 2. 声明和校验Bean约束(Declaring and validating bean constraints)

2.1. 声明Bean约束(Declaring bean constraints)

主要有以下四种:

  • 字段约束( field constraints)
  • 属性约束( property constraints)
  • 容器元素约束(container element constraints)
  • 类级别约束(class constraints)

注意:不是所有的约束可以使用于所有约束级别。事实上,Bean Validate 定义的索引是不可以使用到类级别上的。java.lang.annotation.Target 注解是声明该注解是可以放到哪个层级上的。

2.1.1字段级别( Field-level constraints)

1
2
3
4
5
6
7
8
9
10
11
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.2 属性级别( Property-level constraints)

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

2.1.3 容器元素级别约束(Container element constraints)

可直接在泛型的类型参数上指定约束,它要求 ElementType.TYPE_USE 必须在约束定义中通过 @Target 指定。Hibernate Validate 只对以下标准java容器加以校验。

  • implementations of java.util.Iterable (e.g. Lists, Sets)
  • implementations of java.util.Map, with support for keys and values
  • java.util.Optional, java.util.OptionalInt, java.util.OptionalDouble, java.util.OptionalLong
  • the various implementations of JavaFX’s javafx.beans.observable.ObservableValue.
2.1.3.1. with set
1
2
3
4
5
6
7
8
9
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
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()
);
2.1.3.2. with List
1
2
3
4
5
6
7
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
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()
);
2.1.3.3. with Map
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
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()
);
2.1.3.4. with Optional
1
2
3
4
5
6
7
public class Car {
  private Optional<@MinTowingCapacity(1000) Integer> towingCapacity = Optional.empty();
  public void setTowingCapacity(Integer alias) {  
towingCapacity = Optional.of( alias );  
}
  //...
}
1
2
3
4
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() );

2.1.4. 类级别约束

2.2. 校验bean约束(Validating been constraints)

2.2.1. 创建一个 Validator 实例

1
2
ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 
validator = factory.getValidator();

2.3. 内置的约束(Built-in constraints)

Hibernate Validator 创建了一系列基础的常用约束。实现了Bean Validation specification 定义的约束,也添加了一些方便使用的自定义约束。

Basic Constraints:

Constraints function Supported data types Hibernate metadata impact
@AssertFalse 断言被注解元素false Boolean, boolean None
@AssertTrue 断言被注解元素true Boolean, boolean None
@DecimalMax(value=, inclusive=) BigDecimal, BigInteger, CharSequence, byte, short, int, long None
@DecimalMin(value=, inclusive=) BigDecimal, BigInteger, CharSequence, byte, short, int, long None
@Digits(integer=, fraction=) BigDecimal, BigInteger, CharSequence, byte, short, int, long Defines column precision and scale
@Email 检验是否是邮箱 CharSequence None
@Max(value=) BigDecimal, BigInteger, byte, short, int, long Adds a check constraint on the column
@Min(value=) BigDecimal, BigInteger, byte, short, int, long Adds a check constraint on the column
@NotBlank CharSequence None
@NotEmpty CharSequence, Collection, Map and arrays None
@NotNull Any type Column(s) are not nullable
@Negative 校验元素是否严格否定,零值是被考虑无效的。 BigDecimal, BigInteger, byte, short, int, long None
@NegativeOrZero Checks if the element is negative or zero. BigDecimal, BigInteger, byte, short, int, long None
@Null Any type None
@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 None
@PastOrPresent
@Pattern(regex=, flags=) 正则匹配 CharSequence None
@Positive
@PositiveOrZero
@Size(min=, max=) CharSequence, Collection, Map and arrays Column length will be set to max

Additional constraints:

Constraints function Supported data types Hibernate metadata impact
@Length(min=, max=) 校验字符长度是否在约束范围内 CharSequence Column length will be set to max
@Range(min=, max=) 校验数值大小是否在约束范围内 BigDecimal, BigInteger, CharSequence, byte, short, int, long None
@UniqueElements
@URL(protocol=, host=, port=, regexp=, flags=)
@ScriptAssert(lang=, script=, alias=, reportOn=)
@Mod11Check(threshold=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=, treatCheck10As=, treatCheck11As=)
@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)
@LuhnCheck(startIndex= , endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=)
@CodePointLength(min=, max=, normalizationStrategy=)

Chapter 3. 声明和校验方法约束(Declaring and validating method constraints)

3.1. 声明方法约束(Declaring method constraints)

3.1.1. 参数约束

Example 3.1: Declaring method and constructor parameter constraints

1
2
3
4
5
6
7
8
9
10
11
public class RentalStation {
  public RentalStation(@NotNull String name) {  
//...  
}
  public void rentCar(  
@NotNull Customer customer,  
@NotNull @Future Date startDate,  
@Min(1) int durationInDays) {  
//...  
}
}

Example 3.2: Declaring a cross-parameter constraint

1
2
3
4
5
6
public class Car {
@LuggageCountMatchesPassengerCount(piecesOfLuggagePerPassenger = 2)
public void load(List<Person> passengers, List<PieceOfLuggage> luggage) {
//...
}
}

3.1.2. 返回值约束

Example 3.4: Declaring method and constructor return value constraints

1
2
3
4
5
6
7
8
9
10
11
12
public class RentalStation {
@ValidRentalStation
public RentalStation() {
//...
}
@NotNull
@Size(min = 1)
public List<@NotNull Customer> getCustomers() {
//...
return null;
}
}

Chapter 4. 插入约束错误信息(Interpolating constraint error messages)

4.1. 默认的信息插入(Default message interpolation)

1
2
3
4
5
public class Car {
  @NotNull(message = "The manufacturer name must not be null")
private String manufacturer;
  //constructor, 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
public class Car {
  @NotNull  
private String manufacturer;
  @Size(  
min = 2,
max = 14,
message = "The license plate '${validatedValue}' must be between {min} and {max} characters long"
)
private String licensePlate;
  @Min(
value = 2,
message = "There must be at least {value} seat${value > 1 ? 's' : ''}"  
)
private int seatCount;
  @DecimalMax(
value = "350",
message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher than {value}
)
private double topSpeed;
  @DecimalMax(value = "100000", message = "Price must not be higher than ${value}")   private BigDecimal price;
  public Car(
String manufacturer,
String licensePlate,
int seatCount,
double topSpeed,
BigDecimal price) {
this.manufacturer = manufacturer;
this.licensePlate = licensePlate;
this.seatCount = seatCount;
this.topSpeed = topSpeed;
this.price = price;
}
  //getters and setters ...
}

Chapter 5. Grouping constraints

5.1. 请求分组(Requesting groups)

让我们看以下demo

1
2
3
4
5
6
7
8
public class Person {
@NotNull
private String name;
public Person(String name) {
this.name = name;
}
// getters and setters ...
}

Driver 类必须实现年龄@age 在18岁以上,并且拥有驾驶证

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
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;
}
}
1
2
public interface DriverChecks {
}

注意:使用接口来实现分组约束的安全,而且方便代码重构。它也说明了Group 可以通过类继承来实现约束组的继承。

最终上述类Car

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
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 ...
}
1
2
public interface CarChecks {
}

总结:

Person.name, Car.manufacturer Car.licensePlate and

Car.seatCount 属于默认分组

Driver.ageDriver.hasDrivingLicense 属于 DriverCheck 分组

Car.passedVehicleInspection 属于 CarCheck 分组

以下demo展示了通过不同的组绑定到Validator#validate() 这个方法会导致不同的校验结果。

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
Car car = new Car( "Morris", "DD-AB-123", 2 );
Set<ConstraintViolation<Car>> constraintViolations = validator.validate(
car );
assertEquals( 0, constraintViolations.size() );
// but has it passed the vehicle inspection?
constraintViolations = validator.validate( car, CarChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"The car has to pass the vehicle inspection first",
constraintViolations.iterator().next().getMessage()
);
// let's go to the vehicle inspection
car.setPassedVehicleInspection( true );
assertEquals( 0, validator.validate( car, CarChecks.class ).size() );
// now let's add a driver. He is 18, but has not passed the driving test yet
Driver john = new Driver( "John Doe" );
john.setAge( 18 );
car.setDriver( john );
constraintViolations = validator.validate( car, DriverChecks.class );
assertEquals( 1, constraintViolations.size() );
assertEquals(
"You first have to pass the driving test",
constraintViolations.iterator().next().getMessage()
);
// ok, John passes the test
john.passedDrivingTest( true );
assertEquals( 0, validator.validate( car, DriverChecks.class ).size() );
// just checking that everything is in order now
assertEquals(
0, validator.validate(
car,
Default.class,
CarChecks.class,
DriverChecks.class
).size()
);

组继承:

1
2
3
4
5
6
7
8
public class SuperCar extends Car {
@AssertTrue(
message = "Race car must have a safety belt",
groups = RaceCarChecks.class
)
private boolean safetyBelt;
// getters and setters ...
}
1
2
3
import javax.validation.groups.Default;
public interface RaceCarChecks extends Default {
}

在下面的例子,我们将会校验 SuperCar

1
2
3
4
5
6
7
8
9
10
// create a supercar and check that it's valid as a generic Car
assertEquals( "must be greater than or equal to 2", validator.validate(
superCar ).iterator().next().getMessage() );
// check that this supercar is valid as generic car and also as race car
Set<ConstraintViolation<SuperCar>> constraintViolations = validator
.validate( superCar, RaceCarChecks.class );
assertThat( constraintViolations ).extracting( "message" ).containsOnly(
"Race car must have a safety belt",
"must be greater than or equal to 2"
);

上述第一个校验,没有指定组,测试有一个校验错误-座位数超过两个,它的约束来自默认组;第二次校验指定了RaceCarChecks 组,测试校验将会出现两个错误,一个来自默认组 Default 座位缺失,第二个来自RaceCarChecks 组没有安全带。

Chapter 6. 自定义约束(Creating custom constraints)

6.1. 简单示例(Creating a simple constraint)

一、创建一个约束注解

二、实现一个 validator

三、定义一个默认的错误信息

该章节展示如何使用约束注解来实现校验字符串是完全的大写还是完全的小写。这个约束将被用到CarlicensePlate 字段,确保该字段总是大写。

Example 6.1 Enum CaseMode to express upper vs. lower case

1
2
3
4
public enum CaseMode {
UPPER,
LOWER;
}

一、创建一个约束注解

Example 6.2: Defining the @CheckCase constraint annotation

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
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@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();
}
}

Bean Validation API 所要求的,任何约束注解定义如下:

  • message 返回校验失败返回的错误信息
  • groups 指定了校验组
  • payload 该属性并不被 Bean Validate API 所使用,而是自定义属性。如下可自定义概念‘Severity’,也可以自定义所属国家,在加下来的payload 中有演示。
1
2
3
4
5
6
public class Severity {
public interface Info extends Payload {
}
public interface Error extends Payload {
}
}
1
2
3
4
5
6
7
8
public class ContactDetails {
@NotNull(message = "Name is mandatory", payload = Severity.Error.class)
private String name;
@NotNull(message = "Phone number not specified, but not mandatory", payload =
Severity.Info.class)
private String phoneNumber;
// ...
}
  • @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
1
2
Defines the supported target element types for the constraint. @CheckCase may be used on fields (element type FIELD), JavaBeans properties as well as method return values (METHOD), method/constructor parameters (PARAMETER) and type argument of parameterized types (TYPE_USE). The element type ANNOTATION_TYPE allows for the creation of composed constraints (see Section 6.4, “Constraint composition”) based on @CheckCase.
When creating a class-level constraint (see Section 2.1.4, “Class-level constraints”), the element type TYPE would have to be used. Constraints targeting the return value of a constructor need to support the element type CONSTRUCTOR. Cross-parameter constraints (see Section 6.3, “Cross-parameter constraints”) which are used to validate all the parameters of a method or constructor together, must support METHOD or CONSTRUCTOR, respectively.
  • @Retention(RUNTIME) 被注解的属性可在运行时反射使用有效
  • @Constraint(validatedBy = CheckCaseValidator.class) 标记约束注解类型,并指定校验器CheckCaseValidator
  • @Documented JavaDoc
  • @Repeatable(List.class) List 是容器声明类型,该注解在同一个地方可以使用多次。(不懂)

二、实现一个 validator

上文已经定义了一个注解,加下来需要创建一个约束校验器,它可以用CheckCase 注解来校验元素。此处必须要实现ConstraintValidator 接口:
Example 6.3: Implementing a constraint validator for the constraint @CheckCase

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

三、定义一个默认的错误信息

使用ConstraintValidatorContext 自定义一个错误信息,使用此方法可以覆盖掉默认的错误生成信息。

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

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;
}
boolean isValid;
if (caseMode == CaseMode.UPPER) {
isValid = object.equals(object.toUpperCase());
} else {
isValid = object.equals(object.toLowerCase());
}
if (!isValid) {
constraintContext.disableDefaultConstraintViolation();
constraintContext.buildConstraintViolationWithTemplate(
"{org.hibernate.validator.referenceguide.chapter06."
+
"constraintvalidatorcontext.CheckCase.message}"
)
.addConstraintViolation();
}
return isValid;
}
}

使用 HibernateConstraintValidator 拓展,ConstraintValidator 只是在initialize 方法中传参注解,而hibernate 的注解可以通过constraintDescriptor#getAnnotation()获取到,具体详见 P89。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyFutureValidator implements HibernateConstraintValidator<MyFuture, Instant> {
private Clock clock;
private boolean orPresent;
@Override
public void initialize(ConstraintDescriptor<MyFuture>
constraintDescriptor,
HibernateConstraintValidatorInitializationContext
initializationContext) {
this.orPresent = constraintDescriptor.getAnnotation().orPresent();
this.clock = initializationContext.getClockProvider().getClock();
}
@Override
public boolean isValid(Instant instant, ConstraintValidatorContext
constraintContext) {
//...
return false;
}
}

Payload使用demo(详见P90):

1
2
3
4
5
6
7
8
9
10
11
12
13
HibernateValidatorFactory hibernateValidatorFactory = Validation
.byDefaultProvider()
.configure()
.buildValidatorFactory()
.unwrap( HibernateValidatorFactory.class );
Validator validator = hibernateValidatorFactory.usingContext()
.constraintValidatorPayload( "US" )
.getValidator();
// [...] US specific validation checks
validator = hibernateValidatorFactory.usingContext()
.constraintValidatorPayload( "FR" )
.getValidator();
// [...] France specific validation checks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ZipCodeValidator implements ConstraintValidator<ZipCode, String> {
public String countryCode;
@Override
public boolean isValid(String object, ConstraintValidatorContext
constraintContext) {
if ( object == null ) {
return true;
}
boolean isValid = false;
String countryCode = constraintContext
.unwrap( HibernateConstraintValidatorContext.class )
.getConstraintValidatorPayload( String.class );
if ( "US".equals( countryCode ) ) {
// checks specific to the United States
}
else if ( "FR".equals( countryCode ) ) {
// checks specific to France
}
else {
// ...
}
return isValid;
}
}
CATALOG
  1. 1. Hibernate Validator 6.0.17.Final - JSR
  2. 2. 380 Reference Implementation 2019-06-13
    1. 2.1. 如何阅读官方文档
    2. 2.2. Chapter 1. Getting started
      1. 2.2.1. 1.1. 项目启动
        1. 2.2.1.1. 1.1.1. Unified EL
        2. 2.2.1.2. 1.1.2. CDI
      2. 2.2.2. 1.2. 快速使用约束
      3. 2.2.3. 1.3. 校验约束
    3. 2.3. Chapter 2. 声明和校验Bean约束(Declaring and validating bean constraints)
      1. 2.3.1. 2.1. 声明Bean约束(Declaring bean constraints)
        1. 2.3.1.1. 2.1.1字段级别( Field-level constraints)
        2. 2.3.1.2. 2.1.2 属性级别( Property-level constraints)
        3. 2.3.1.3. 2.1.3 容器元素级别约束(Container element constraints)
          1. 2.3.1.3.1. 2.1.3.1. with set
          2. 2.3.1.3.2. 2.1.3.2. with List
          3. 2.3.1.3.3. 2.1.3.3. with Map
          4. 2.3.1.3.4. 2.1.3.4. with Optional
        4. 2.3.1.4. 2.1.4. 类级别约束
      2. 2.3.2. 2.2. 校验bean约束(Validating been constraints)
        1. 2.3.2.1. 2.2.1. 创建一个 Validator 实例
      3. 2.3.3. 2.3. 内置的约束(Built-in constraints)
    4. 2.4. Chapter 3. 声明和校验方法约束(Declaring and validating method constraints)
      1. 2.4.1. 3.1. 声明方法约束(Declaring method constraints)
        1. 2.4.1.1. 3.1.1. 参数约束
        2. 2.4.1.2. 3.1.2. 返回值约束
    5. 2.5. Chapter 4. 插入约束错误信息(Interpolating constraint error messages)
      1. 2.5.1. 4.1. 默认的信息插入(Default message interpolation)
    6. 2.6. Chapter 5. Grouping constraints
      1. 2.6.1. 5.1. 请求分组(Requesting groups)
    7. 2.7. Chapter 6. 自定义约束(Creating custom constraints)
      1. 2.7.1. 6.1. 简单示例(Creating a simple constraint)