自定义 Validation 注解

自定义 Validation 注解

在 Java 后端的数据校验常用 @NotNull@NotEmpty@Size 等注解进行校验,但标应准的验证注解是很局限的,在一些特殊要求的验证中并不能满足需求。

自定义校验注解

要想实现业务用特定需求的验证只用定义一个注解和对应的校验类既可,下面实现一个非空字符串验证注解。

1. 定义注解

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotBlankCharValidator.class})
public @interface NotBlankChar {
String message() default "{tech.imoe.java.validate.NotBlankChar.message}";
}

注解中要注意的是 @Constraint 注解用于指定验证的实现类。

2. 定义注解校验类

注解校验类需实现 ConstraintValidator<A extends Annotation, T> 接口,该接口有两个方法。

1
void initialize(A constraintAnnotation);

initialize 方法用于在校验的时候初始化一些参数,比如注解的信息。对于同一个注解(一个验证实例)只会调用一次,而不是每次校验都会调用。

1
boolean isValid(T value, ConstraintValidatorContext context);

isValid 方法里实现真正的校验逻辑,验证通过返回 true。value 是注解的对象,如果注解到类上,则对应类的对象。 context 是验证的上下文,可以将验证的结果存放在这里,如果验证不通过,错误信息也应该放在里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NotBlankCharValidator implements ConstraintValidator<NotBlankChar, String> {
private NotBlankChar annotation;

@Override
public void initialize(NotBlankChar constraintAnnotation) {
this.annotation = constraintAnnotation;
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return StringUtils.containsWhitespace(value);
}
}

错误信息中的 Constraint Violation

Constraint Violation 指的是验证违反信息,每调用一次 addConstraintViolation 会增加一条新的记录。 假设有如下对象:

1
2
3
4
5
6
7
8
9
10
public class User {
public Map<String,Address> getAddresses() { ... }
}
public class Address {
public String getStreet() { ... }
public Country getCountry() { ... }
}
public class Country {
public String getName() { ... }
}
  1. User.addresses 属性上注解,User.addresses 验证报错

直接使用默认的 path,因为注解直接打在 User.addresses 上,默认就是 addresses 属性

1
2
context.buildConstraintViolationWithTemplate( "this detail is wrong" )
.addConstraintViolation();
  1. Address 的类上注解,street 报错

注解打在 Address 类上,错误的属性就是 默认+street

1
2
3
context.buildConstraintViolationWithTemplate( "this detail is wrong" )
.addPropertyNode( "street" )
.addConstraintViolation();
  1. User.addresses 属性上注解,取 addresses map 里 key 为 home 的验证报错
1
2
3
4
5
6
// addresses["home"]

context.buildConstraintViolationWithTemplate( "Incorrect home address" )
.addBeanNode()
.inIterable().atKey( "home" )
.addConstraintViolation();
  1. User 类上注解,addresses["home"].country.name 验证报错
1
2
3
4
5
6
7
8
\\ addresses["home"].country.name

context.buildConstraintViolationWithTemplate( "this detail is wrong" )
.addPropertyNode( "addresses" )
.addPropertyNode( "country" )
.inIterable().atKey( "home" )
.addPropertyNode( "name" )
.addConstraintViolation();

上下文变量

在 message 中可以用 ${} 取得上下文的变量,通过以下方法可以设置这些变量

1
2
HibernateConstraintValidatorContext validatorContext = context.unwrap(HibernateConstraintValidatorContext.class);
validatorContext.addExpressionVariable(attr, value);
作者

Jakes Lee

发布于

2018-06-15

更新于

2021-11-18

许可协议

评论