虽然说Bean Validation API和Hibernate Validator给我们提供了很多内置的约束(如:@NotNull, @Size等),但是,这些内置的约束总有不能满足业务需求的时候。此时,我们很自然就想到了自己定义约束。
在Hibernate Validator中,要实现自定义约束,需要经过下面三个步骤:
- 创建一个约束注解
- 实现一个校验器
- 定义默认的错误消息
还是通过一个简单的例子来讲解:假如我们需要对一个String类型的字符串进行约束,要求该字符串是大写或者小写。
- 首先我们可以使用一个枚举将大写和小写两种情况区分开来(当然,你也可以使用字符串)
public enum CaseMode {
// @formatter:off
UPPER, // 大写
LOWER; // 小写
// @formatter:on
}
- 下一步就是创建我们的约束注解了。
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
@Repeatable(CheckCase.List.class)
public @interface CheckCase {
/**
* 默认的错误消息
*
* @return 消息
*/
String message() default "出错了";
/**
* 区分大小写
*
* @return 大写/小写
*/
CaseMode value();
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Documented
@interface List {
CheckCase[] value();
}
}
接下来,我们详细分析这个自定义的约束:
- Bean Validation API要求任何一个约束注解都要有 message groups 和 payload 的定义。
- message 指定了当该约束被违反的时候给出的默认错误提示。在这里,为了简单起见,我们直接使用字符串常量,其实这里是可以做国际化的。
- groups 是为分组校验服务的,关于分组校验,博主的其他文章有介绍,这里不讲解。
- payload 是给Bean Validation API的客户端使用的,不用管。
- 除了上面三个是必须的之外。我们还定义了一个 value 属性,它是用来指定大写还是小写的。了解注解的同学都知道,注解的value属性是特殊的,可以被省略,所以我们可以像这样应用这个注解:@CheckCase(CaseMode.UPPER),这和@CheckCase(value = CaseMode.UPPER) 是一样的。
- 至于这个自定义的注解上面使用的@Target、@Retention、@Documented这些注解,它们是Java中注解相关的东西,这里不讲。
- @Repeatable(CheckCase.List.class) 是用来表明我们自定义的这个注解可以在同一个地方重复使用,主要是针对List类型的。
- 最后一点,也是最重要的 ------ @Constraint(validatedBy = CheckCaseValidator.class) 指定了校验器,我们在这个校验器里边实现具体的校验逻辑。就是最上面说的第二个步骤。
- 接着,我们需要实现校验器了,自定义的校验器需要实现ConstraintValidator接口
/**
* 自定义的校验器需要实现ConstraintValidator接口
*
* ConstraintValidator 接口定义了2个类型参数
*
* 第一个是我们自定义的约束注解 -- CheckCase
*
* 第二个是要被校验的数据的类型 -- String
*/
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
/**
* 初始化,取得约束的value,
*
* @param constraintAnnotation 约束注解
*/
@Override
public void initialize(CheckCase constraintAnnotation) {
this.caseMode = constraintAnnotation.value();
}
/**
* 校验是否合法
*
* @param value 被校验的对象
* @param context 约束上下文
* @return 合法/非法
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 不做空校验,职责单一
if (value == null) {
return true;
}
// 校验大写
if (caseMode == CaseMode.UPPER) {
return value.equals(value.toUpperCase());
}
// 校验小写
if (caseMode == CaseMode.LOWER) {
return value.equals(value.toLowerCase());
}
return false;
}
}
校验器中的isValid实现了核心校验逻辑,它的返回值决定了校验对象是否合法,true--合法,false--不合法。
到这里,一个最简单的自定义约束就完成了。