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 );
validateComposingConstraintTypes();
if ( constraintType == ConstraintType.GENERIC ) {
this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( genericValidatorDescriptors );
}
else {
this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( crossParameterValidatorDescriptors );
}
this.hashCode = annotationDescriptor.hashCode();
}
// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
private Set<ConstraintDescriptorImpl<?>> parseComposingConstraints(ConstraintHelper constraintHelper, Constrainable constrainable, ConstraintType constraintType) {
Set<ConstraintDescriptorImpl<?>> composingConstraintsSet = newLinkedHashSet();
Map<ClassIndexWrapper, Map<String, Object>> overrideParameters = parseOverrideParameters();
Map<Class<? extends Annotation>, ComposingConstraintAnnotationLocation> composingConstraintLocations = new HashMap<>();
// 2. annotationDescriptor为校验注解(如@NotNull),从注解上获取标注的其它注解
for ( Annotation declaredAnnotation : this.annotationDescriptor.getType().getDeclaredAnnotations() ) {
Class<? extends Annotation> declaredAnnotationType = declaredAnnotation.annotationType();
// 3. NON_COMPOSING_CONSTRAINT_ANNOTATIONS为@Target、@Retention等常用注解,它们与校验无关,过滤掉
if ( NON_COMPOSING_CONSTRAINT_ANNOTATIONS.contains( declaredAnnotationType.getName() ) ) {
continue;
}
// 4. 判断注解是否为校验注解,内置的注解和加了@Constraint的注解才是校验注解
if ( constraintHelper.isConstraintAnnotation( declaredAnnotationType ) ) {
if ( composingConstraintLocations.containsKey( declaredAnnotationType )
&& !ComposingConstraintAnnotationLocation.DIRECT.equals( composingConstraintLocations.get( declaredAnnotationType ) ) ) {
throw LOG.getCannotMixDirectAnnotationAndListContainerOnComposedConstraintException( annotationDescriptor.getType(), declaredAnnotationType );
}
// 5. 为每个校验注解也生成一个ConstraintDescriptorImpl
ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
constraintHelper,
constrainable,
overrideParameters,
OVERRIDES_PARAMETER_DEFAULT_INDEX,
declaredAnnotation,
constraintType
);
// 6. 生成的ConstraintDescriptorImpl,放到composingConstraintsSet里面,该Set座位返回值赋值给了this.composingConstraints
composingConstraintsSet.add( descriptor );
composingConstraintLocations.put( declaredAnnotationType, ComposingConstraintAnnotationLocation.DIRECT );
LOG.debugf( "Adding composing constraint: %s.", descriptor );
}
else if ( constraintHelper.isMultiValueConstraint( declaredAnnotationType ) ) {
List<Annotation> multiValueConstraints = constraintHelper.getConstraintsFromMultiValueConstraint( declaredAnnotation );
int index = 0;
for ( Annotation constraintAnnotation : multiValueConstraints ) {
if ( composingConstraintLocations.containsKey( constraintAnnotation.annotationType() )
&& !ComposingConstraintAnnotationLocation.IN_CONTAINER.equals( composingConstraintLocations.get( constraintAnnotation.annotationType() ) ) ) {
throw LOG.getCannotMixDirectAnnotationAndListContainerOnComposedConstraintException( annotationDescriptor.getType(),
constraintAnnotation.annotationType() );
}
ConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(
constraintHelper,
constrainable,
overrideParameters,
index,
constraintAnnotation,
constraintType
);
composingConstraintsSet.add( descriptor );
composingConstraintLocations.put( constraintAnnotation.annotationType(), ComposingConstraintAnnotationLocation.IN_CONTAINER );
LOG.debugf( "Adding composing constraint: %s.", descriptor );
index++;
}
}
}
return CollectionHelper.toImmutableSet( composingConstraintsSet );
}
private static final List<String> NON_COMPOSING_CONSTRAINT_ANNOTATIONS = Arrays.asList(
Documented.class.getName(),
Retention.class.getName(),
Target.class.getName(),
Constraint.class.getName(),
ReportAsSingleViolation.class.getName(),
Repeatable.class.getName(),
Deprecated.class.getName()
);
// 源码位置:org.hibernate.validator.internal.metadata.core.MetaConstraint
MetaConstraint(ConstraintValidatorManager constraintValidatorManager, ConstraintDescriptorImpl<A> constraintDescriptor,
ConstraintLocation location, List<ContainerClassTypeParameterAndExtractor> valueExtractionPath,
Type validatedValueType) {
// 7. 在把找到的校验注解(如@NotNull)和属性字段封装为MetaConstraint时,要确定ConstraintTree的实际实现类,
// ConstraintTree的实现有两种,一种是对应单个校验注解的SimpleConstraintTree,另外一种是对应多个校验注解的ComposingConstraintTree
this.constraintTree = ConstraintTree.of( constraintValidatorManager, constraintDescriptor, validatedValueType );
this.location = location;
this.valueExtractionPath = getValueExtractionPath( valueExtractionPath );
this.hashCode = buildHashCode( constraintDescriptor, location );
this.isDefinedForOneGroupOnly = constraintDescriptor.getGroups().size() <= 1;
}
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree
public static <U extends Annotation> ConstraintTree<U> of(ConstraintValidatorManager constraintValidatorManager,
ConstraintDescriptorImpl<U> composingDescriptor, Type validatedValueType) {
// 8. composingDescriptor.getComposingConstraintImpls()获取到的就是上面的composingConstraints
// 当校验注解上还加了其它校验注解,则composingConstraints不为空,
// composingConstraints为空则创建的是SimpleConstraintTree,否则创建的是ComposingConstraintTree
if ( composingDescriptor.getComposingConstraintImpls().isEmpty() ) {
return new SimpleConstraintTree<>( constraintValidatorManager, composingDescriptor, validatedValueType );
}
else {
return new ComposingConstraintTree<>( constraintValidatorManager, composingDescriptor, validatedValueType );
}
}
// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
public Set<ConstraintDescriptorImpl<?>> getComposingConstraintImpls() {
return composingConstraints;
}
// 实际校验的时候会调ConstraintTree的validateConstraints()方法,ComposingConstraintTree重载了父类ConstraintTree的validateConstraints()方法
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ComposingConstraintTree
protected void validateConstraints(ValidationContext<?> validationContext,
ValueContext<?, ?> valueContext,
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
// 9. 校验
CompositionResult compositionResult = validateComposingConstraints(validationContext, valueContext, violatedConstraintValidatorContexts);
Optional<ConstraintValidatorContextImpl> violatedLocalConstraintValidatorContext;
if ( mainConstraintNeedsEvaluation( validationContext, violatedConstraintValidatorContexts ) ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Validating value %s against constraint defined by %s.",
valueContext.getCurrentValidatedValue(),
descriptor
);
}
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(
descriptor, valueContext.getPropertyPath()
);
violatedLocalConstraintValidatorContext = validateSingleConstraint(
valueContext,
constraintValidatorContext,
validator
);
if ( !violatedLocalConstraintValidatorContext.isPresent() ) {
compositionResult.setAtLeastOneTrue( true );
}
else {
compositionResult.setAllTrue( false );
}
}
else {
violatedLocalConstraintValidatorContext = Optional.empty();
}
if ( !passesCompositionTypeRequirement( violatedConstraintValidatorContexts, compositionResult ) ) {
prepareFinalConstraintViolations(
validationContext, valueContext, violatedConstraintValidatorContexts, violatedLocalConstraintValidatorContext
);
}
}
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ComposingConstraintTree
private CompositionResult validateComposingConstraints(ValidationContext<?> validationContext,
ValueContext<?, ?> valueContext,
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
CompositionResult compositionResult = new CompositionResult( true, false );
// 10. 当校验注解上还加了其它校验注解,其它校验注解每个也对应生成了一个处理单个校验注解的SimpleConstraintTree
// children就是SimpleConstraintTree列表
for ( ConstraintTree<?> tree : children ) {
List<ConstraintValidatorContextImpl> tmpConstraintValidatorContexts = new ArrayList<>( 5 );
// 11. 把注解当一个普通的校验注解完成校验逻辑
tree.validateConstraints( validationContext, valueContext, tmpConstraintValidatorContexts );
violatedConstraintValidatorContexts.addAll( tmpConstraintValidatorContexts );
if ( tmpConstraintValidatorContexts.isEmpty() ) {
compositionResult.setAtLeastOneTrue( true );
if ( descriptor.getCompositionType() == OR ) {
break;
}
}
else {
compositionResult.setAllTrue( false );
if ( descriptor.getCompositionType() == AND
&& ( validationContext.isFailFastModeEnabled() || descriptor.isReportAsSingleViolation() ) ) {
break;
}
}
}
return compositionResult;
}
// 回到ComposingConstraintTree的validateConstraints继续处理
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ComposingConstraintTree
protected void validateConstraints(ValidationContext<?> validationContext,
ValueContext<?, ?> valueContext,
Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
// 9. 校验
CompositionResult compositionResult = validateComposingConstraints(validationContext, valueContext, violatedConstraintValidatorContexts);
Optional<ConstraintValidatorContextImpl> violatedLocalConstraintValidatorContext;
// 12. 如果校验注解本身也指定了校验器,那么它本身也需要当一个普通校验注解进行校验
if ( mainConstraintNeedsEvaluation( validationContext, violatedConstraintValidatorContexts ) ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Validating value %s against constraint defined by %s.",
valueContext.getCurrentValidatedValue(),
descriptor
);
}
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(
descriptor, valueContext.getPropertyPath()
);
violatedLocalConstraintValidatorContext = validateSingleConstraint(
valueContext,
constraintValidatorContext,
validator
);
if ( !violatedLocalConstraintValidatorContext.isPresent() ) {
compositionResult.setAtLeastOneTrue( true );
}
else {
compositionResult.setAllTrue( false );
}
}
else {
violatedLocalConstraintValidatorContext = Optional.empty();
}
// 13. 有多个校验结果的时候,需要确定一下总结果是什么
if ( !passesCompositionTypeRequirement( violatedConstraintValidatorContexts, compositionResult ) ) {
prepareFinalConstraintViolations(
validationContext, valueContext, violatedConstraintValidatorContexts, violatedLocalConstraintValidatorContext
);
}
}
// 源码位置:org.hibernate.validator.internal.engine.constraintvalidation.ComposingConstraintTree
private boolean passesCompositionTypeRequirement(Collection<?> constraintViolations, CompositionResult compositionResult) {
// 14. 有三种关系:一是OR,只要有一个校验通过即可;二是AND,需要所有都校验通过;三是ALL_FALSE,所有校验都不通过。
// 如果不指定CompositionType,那么默认是AND
CompositionType compositionType = getDescriptor().getCompositionType();
boolean passedValidation = false;
switch ( compositionType ) {
case OR:
passedValidation = compositionResult.isAtLeastOneTrue();
break;
case AND:
passedValidation = compositionResult.isAllTrue();
break;
case ALL_FALSE:
passedValidation = !compositionResult.isAtLeastOneTrue();
break;
}
assert ( !passedValidation || !( compositionType == AND ) || constraintViolations.isEmpty() );
if ( passedValidation ) {
constraintViolations.clear();
}
return passedValidation;
}
在找校验注解的时候,把关联的注解也当普通的校验注解处理(封装到ConstraintDescriptorImpl对象里),处理结果放到一个Set中。在校验的时候,先处理这个Set的内容,处理流程和普通的校验注解一样;处理完之后再处理当前校验注解本身,处理流程和普通的校验注解一样。
由此可以看出,在设计的时候,不管是内置的校验注解,还是自定义的校验注解,原理都是一致的,只有初始化的地方不一样;同样,组合校验注解也会分解成当前注解和关联的校验注解,分解之后也是普通的注解,也是在初始化的时候和校验开始时分解的地方不一样,其它的都是一样的。这个组合的设计思路值得学习。
3 架构一小步
针对需要一起组合使用的校验注解,利用hibernate-validator包提供的组合机制自定义一个新校验注解把它们组合起来,方便使用。