最近新开了一个项目,虽然hibernate-validator很好用,但是有时不能满足稍微复杂一些的业务校验。为了不在业务代码中写校验逻辑,以及让代码更优雅,故而采用了自定义校验注解的方式。
一. 场景说明
本例注解应用场景: 填写表单时,某一项数据存在时,对应的一类数据都应存在,一同提交。
二.源码
1.类注解
主注解用于标记要在校验的实体类
@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = RelateOtherValidator.class)
@Documented
public @interface RelateOther {
String message() default "";
/**
* 校验数量
*/
int num() default 2;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.辅助注解
辅助注解用于标注于要校验的字段,isMaster区分为主注解和从注解。
主注解是关键字段,存在才进行校验从注解对应字段的有效性;主注解的value()属性可以设置默认值,当字段对应值对应value()时才开启校验。
从注解为等待校验的值,默认为从注解。
@Target( { FIELD })
@Retention(RUNTIME)
@Documented
public @interface RelateOtherItem {
/**
* 是否为主字段,主字段存在才进行校验
*/
boolean isMaster() default false;
/**
* 用于开启对指定值校验判断,master字段有效
* 当前为master且value与标注字段值相等才进行校验,
*/
String value() default "";
}
3.校验类
校验类为实际执行校验逻辑的类,在类注解的@Constraint的validatedBy属性上设置。
要设置为校验类,首先要实现ConstraintValidator类的isValid方法。
@Slf4j // @Slf4j是lombok的注解
public class RelateOtherValidator implements ConstraintValidator<RelateOther, Object> {
// 要校验的个数
private int validateNum;
@Override
public void initialize(RelateOther constraintAnnotation) {
validateNum = constraintAnnotation.num();
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
if (o == null) {
return true;
}
Field[] declaredFields = o.getClass().getDeclaredFields();
boolean mater = false;
int emptyNum = 0;
try {
// 总共需要校验的字段数
int totalValidateNum = validateNum;
for (Field field : declaredFields) {
// 校验是否进行过标注
if (!field.isAnnotationPresent(RelateOtherItem.class)) {
continue;
}
if (validateNum > 0 && totalValidateNum-- < 0) {
return false;
}
field.setAccessible(true);
Object property = field.get(o);
RelateOtherItem relateOtherItem = field.getAnnotation(RelateOtherItem.class);
// 主字段不存在,则校验通过
if (relateOtherItem.isMaster()) {
if (property==null) {
return true;
}
// 与指定值不一致,校验通过
if (!StringUtils.isEmpty(relateOtherItem.value()) && !relateOtherItem.value().equals(property)) {
return true;
}
mater = true;
continue;
}
if (null == property) {
emptyNum++;
}
}
// 主字段不存在,则校验通过
if (!mater) {
log.info("RelateOther注解主字段不存在");
return true;
}
return emptyNum==0;
} catch (Exception e) {
log.info("RelateOther注解,解析异常 {}", e.getMessage());
return false;
}
}
}
4.校验失败
注解校验不同时会抛出一个MethodArgumentNotValidException异常。这里可以采用全局异常处理的方法,进行捕获处理。捕获之后的异常可以获取BindingResult 对象,后面就跟hibernate-validator处理方式一致了。
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
5.使用demo
注解的使用类似下面,首先在请求实体类上标注类注解,再在对应的字段上标注辅助注解。
@RelateOther(message = "xx必须存在!",num=2)
public class MarkReq {
@RelateOtherItem (isMaster= true,value="1")
private Integer girl;
@RelateOtherItem
private Integer sunscreen;
private String remarks;
}
三.总结
自定义注解在开发中还是很好用的,本文主要起到抛砖引玉的作用。对于首次使用的朋友应该还是有些用处的。