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包提供的組合機制自定義一個新校驗注解把它們組合起來,方便使用。