0%

Hiberate Validator使用教程之自定义约束

Hiberate Validator使用教程之自定义约束

前面一篇文章已经讲解了Hibernate Validator的基本使用。虽然校验库本身已经提供了许多的约束,但是不一定能够满足不同功能的需要,这时就需要自定义一些约束,本篇文章主要就是来学习如何自定义约束。

简单例子

创建一个简单的自定义约束分为以下三步

  1. 创建一个约束注解
  2. 实现一个约束注解对应的Validator
  3. 定义一个默认的错误信息

下面的这个例子,是用来判断某个String字段是否是全部大小字段或者小写字段。

自定义注解

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

public enum CaseMode {
UPPER,
LOWER;
}

在我们自定义约束注解的时候,需要满足下面几个规定,这样Hibernate Validator才能够进行验证。

在注解中必须包含message,groups,payload这三个属性 ,他们的作用是

  1. message:定义违反约束时,展示的错误信息
  2. groups:用于指定约束对应的分组
  3. payload:用于个性化配置,这个很少用,这里先不讲解了。

其它的和自定义注解没什么区别,

  • value :用于表示注解对应值,这里定义的是CaseMode这个枚举
  • 另外定义了这个注解可以重复定义,所以在注解上用@Repeatable,另外需要注意的一点是,Hibernate Validator规定,@Repeatable中的值,最好是List.class,所以在内部定义了List注解。用来承载可重复注解

自定义Validator

上面介绍了自定义注解中重要的属性,但是对@Constraint(validatedBy = CheckCaseValidator.class)这一块没有介绍,这个用来告诉Hibernate Validator当检测到这个注解时,应该调用的Validator是哪一个,这里对应的是CheckCaseValidator,源码如下

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

自定义Validator要求实现ConstraintValidator接口,这个接口有俩个类型参数,第一个参数表示检测的注解,第二个表示这个注解检测对象类型。接口定义了俩个方法,initialize是一个默认方法,用来初始化,参数是当前检测对象对应的注解。isValid用来判断当前约束是否满足,其中ConstraintValidatorContext这个参数用来设置一些自定义的配置,平常用到的不多,等用到时候再补充。具体的可以看ConstraintValidatorContext

自定义错误信息

如果想要自定义错误信息模板,可以在项目目录下创建一个ValidationMessages.properties ,配置对应的键值对。下面是一个例子

1
org.hibernate.validator.referenceguide.chapter06.CheckCase.message=Case mode must be {value}.

在运行的时候,Hibernate Validator会自动的去查找这个文件, 并去除对应的键值对配置到注解上。

使用自定义约束

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

验证上面的自定义注解

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

类级别的约束基本上和这个差不多,这里就不具体介绍了,下面介绍下自定义Cross-parameter constraints。

自定义Cross-parameter约束

首先自定义Cross-parameter注解和上面的一样,下面这个例子比较参数的日期是否在正确的顺序。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {

String message() default "参数顺序错误";

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

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

但是对应的验证器需要加上这个注解,@SupportedValidationTarget(ValidationTarget.PARAMETERS),表示这个验证器应用在验证Cross-parameter参数上。

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
@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] );
}
}

注意上面的类型参数,传递的是Object[]数组,其它的和前面的自定义约束没什么区别。

组合约束

有时一个属性需要同时满足多个约束,这时就可能要写很多的注解。或者有一些注解需要联合使用,这时组合约束就发会作用了,极大的简化我们的代码。自定义组合约束其实就是讲需要的注解组合起来。具体的例子如下

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

上面这个注解表示,需要同时满足@Size,@CheckCase,@NotNull三个约束。注意这里的@Constraint(validatedBy = { })是空。

使用这个注解和之前使用注解一样,例子如下

1
2
3
4
5
6
7
public class Car {

@ValidLicensePlate
private String licensePlate;

//...
}

总结

这里介绍了如何简单的自定义约束,当然这里只能满足基本的需要,如果需要自定义错误信息,还有如果想在container上使用约束,可能还需要配置Value extraction相关的内容,但是这里已经足够使用。后续如果要用到会继续更新这篇文章。