1 概述
如果遇到某些属性需要多种校验,比如需要非空、符合某正则表达式、长度不能超过某值等,如果这种属性只有有限几个,那么手工把对应的校验注解都加上即可。但如果这种属性比较多,那么重复加这些校验注解,也是一种代码重复。
hibernate-validator包提供了一种组合注解的方式,来解决上面场景的问题。
2 原理
2.1 例子
先来看一个组合校验注解的例子:
1) 定义一个新注解@FormatedString
import org.hibernate.validator.constraints.CompositionType;
import org.hibernate.validator.constraints.ConstraintComposition;
import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@ConstraintComposition(CompositionType.AND)
@NotEmpty
@Pattern(regexp = "")
@Size
@Constraint(validatedBy = {})
public @interface FormatedString {
String message() default "{string.formated.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
@OverridesAttribute(constraint = Size.class, name = "min")
int min() default 0;
@OverridesAttribute(constraint = Size.class, name = "max")
int max() default Integer.MAX_VALUE;
@OverridesAttribute(constraint = Pattern.class, name = "regexp")
String regexp() default "";
}
从上面看,@FormatedString注解的上方指定了非空(@NotEmpty)、正则表达式(@Pattern)、长度限制(@Size)这三个常用的的校验注解。@Constraint里面的validatedBy值为空,代表着自身没有提供ContraintValidator。@ConstraintComposition指定了多个注解校验结果的关系是AND,也就是都需要校验通过则总结果才算通过。
注意:使用@OverridesAttribute把参数传给原注解,这不属于Spring环境,不能使用Spring的@AliasFor来传参数。@Pattern的regexp属性没有给默认值,需要给一个默认值,否则无法通过编译。
2) 使用注解
// 以O开头且后面都是数字,长度在16-20之间
@FormatedString(regexp = "O[0-9]+", min = 16, max = 20)
private String orderNumber;
2.2 处理流程
详细的流程需要参考前面的文章,这里只给出涉及处理组合注解的部分。
// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,
Constrainable constrainable,
ConstraintAnnotationDescriptor<T> annotationDescriptor,
ConstraintLocationKind constraintLocationKind,
Class<?> implicitGroup,
ConstraintOrigin definedOn,
ConstraintType externalConstraintType) {
this.annotationDescriptor = annotationDescriptor;
this.constraintLocationKind = constraintLocationKind;
this.definedOn = definedOn;
this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(
ReportAsSingleViolation.class
);
// 省略部分代码
// 1. 当把校验注解的信息和属性封装为ConstraintDescriptorImpl时,还会解析一下校验注解里是否还带其它校验注解。
// annotationDescriptor为校验注解(如@NotNull),constrainable为属性字段信息(如标了@NotNull注解的mobilePhone属性字段),constraintType为GENERIC。
// 返回结果赋值给了composingConstraints。
this.composingConstraints = parseComposingConstraints( constraintHelper, constrainable, constraintType );
this.compositionType = parseCompositionType( constraintHelper );
validateCo

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



