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. 为每个校验注解也生成一个ConstraintDescriptorImplConstraintDescriptorImpl<?> descriptor = createComposingConstraintDescriptor(constraintHelper,constrainable,overrideParameters,OVERRIDES_PARAMETER_DEFAULT_INDEX,declaredAnnotation,constraintType);// 6. 生成的ConstraintDescriptorImpl,放到composingConstraintsSet里面,该Set座位返回值赋值给了this.composingConstraintscomposingConstraintsSet.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,另外一种是对应多个校验注解的ComposingConstraintTreethis.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,否则创建的是ComposingConstraintTreeif ( 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,那么默认是ANDCompositionType 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包提供的组合机制自定义一个新校验注解把它们组合起来,方便使用。