Spring validation参数校验系列
1、Spring validation参数校验基本使用
2、Spring validation参数校验之自定义校验规则及编程式校验等进阶篇
3、【源码】Spring validation参数校验原理解析之Controller控制器参数校验中@RequestBody参数校验实现原理
4、【源码】Spring validation参数校验原理解析之Controller控制器参数校验中@ModelAttribute及实体类参数校验实现原理
5、【源码】Spring validation参数校验原理解析之基本类型参数及Service层方法参数校验实现原理
6、【源码】Spring validation校验的核心类ValidatorImpl、MetaDataProvider和AnnotationMetaDataProvider源码分析
7、Spring validation参数校验高级篇之跨参数校验Cross-Parameter及分组序列校验@GroupSequenceProvider、@GroupSequence
8、【源码】Spring validation参数校验之跨参数校验Cross-Parameter原理分析
9、【源码】Spring validation参数校验之分组序列校验@GroupSequenceProvider、@GroupSequence的实现原理
10、【源码】Spring validation参数校验实现原理总结
前言
上一篇分析了Spring validation参数校验的跨参数校验Cross-Parameter及分组序列校验@GroupSequenceProvider、@GroupSequence。沿用一贯的风格,这一篇从源码的角度来分析一下这些校验实现的原理。
一、解析跨参数校验的约束
通过前面几篇Spring-validation参数校验的实现原理可以了解到,在校验之前,会先解析类中添加的约束。跨参数校验是针对方法添加的校验,在AnnotaionMetaDataProvider类的findExecutableMetaData()方法中查找添加的约束。
private ConstrainedExecutable findExecutableMetaData(Executable executable) {
JavaBeanExecutable<?> javaBeanExecutable = javaBeanHelper.executable( executable );
// 获取方法的入参中添加的约束
List<ConstrainedParameter> parameterConstraints = getParameterMetaData( javaBeanExecutable );
// 获取方法添加的约束。并通过约束类型ConstraintType转为Map对象,key为ConstraintType
// 值为ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER
Map<ConstraintType, List<ConstraintDescriptorImpl<?>>> executableConstraints = findConstraints(
javaBeanExecutable,
ConstraintLocationKind.of( javaBeanExecutable.getConstrainedElementKind() )
).stream().collect( Collectors.groupingBy( ConstraintDescriptorImpl::getConstraintType ) );
Set<MetaConstraint<?>> crossParameterConstraints;
if ( annotationProcessingOptions.areCrossParameterConstraintsIgnoredFor( javaBeanExecutable ) ) {
crossParameterConstraints = Collections.emptySet();
}
else {
// 通过ConstraintType.CROSS_PARAMETER获取跨参数约束
crossParameterConstraints = convertToMetaConstraints(
executableConstraints.get( ConstraintType.CROSS_PARAMETER ),
javaBeanExecutable
);
}
Set<MetaConstraint<?>> returnValueConstraints;
Set<MetaConstraint<?>> typeArgumentsConstraints;
CascadingMetaDataBuilder cascadingMetaDataBuilder;
if ( annotationProcessingOptions.areReturnValueConstraintsIgnoredFor( javaBeanExecutable ) ) {
returnValueConstraints = Collections.emptySet();
typeArgumentsConstraints = Collections.emptySet();
cascadingMetaDataBuilder = CascadingMetaDataBuilder.nonCascading();
}
else {
// 获取方法返回值添加的约束信息
typeArgumentsConstraints = findTypeAnnotationConstraints( javaBeanExecutable );
returnValueConstraints = convertToMetaConstraints(
executableConstraints.get( ConstraintType.GENERIC ),
javaBeanExecutable
);
cascadingMetaDataBuilder = findCascadingMetaData( javaBeanExecutable );
}
// 封装成ConstrainedExecutable对象
return new ConstrainedExecutable(
ConfigurationSource.ANNOTATION,
javaBeanExecutable,
parameterConstraints,
crossParameterConstraints,
returnValueConstraints,
typeArgumentsConstraints,
cascadingMetaDataBuilder
);
}
1.1 调用findConstraints()方法,查找所有约束,最后遍历所有注解,调用findConstraintAnnotations(Constrainable constrainable, A annotation, ConstraintLocationKind type)方法,检查annotation约束注解的类型,获取约束注解的描述信息,封装成ConstraintDescriptorImpl集合。
protected <A extends Annotation> List<ConstraintDescriptorImpl<?>> findConstraintAnnotations(Constrainable constrainable,A annotation,ConstraintLocationKind type) {
/**
* HV-1049和HV-1311-忽略JDK(JDK.internal.和java.)中的注释;它们不能是约束注释,所以请跳过它们,因为为了进行正确的检查,
* 需要“jdk.internal”和“java”的包访问权限。
*/
if ( constraintCreationContext.getConstraintHelper().isJdkAnnotation( annotation.annotationType() ) ) {
return Collections.emptyList();
}
List<Annotation> constraints = newArrayList();
Class<? extends Annotation> annotationType = annotation.annotationType();
/**
* ConstraintHelper.isConstraintAnnotation()【执行constraintCreationContext.getConstraintHelper().
* isConstraintAnnotation( annotationType )】 -> isBuiltinConstraint() -> BuiltinConstraint.isBuiltin()
* 【BuiltinConstraint为枚举类,列举了validation的所有约束字段。从而判断当前的参数是否添加了约束】
*/
if ( constraintCreationContext.getConstraintHelper().isConstraintAnnotation( annotationType ) ) {
constraints.add( annotation );
}
// 暂时没有用到。用于判断多值约束
else if ( constraintCreationContext.getConstraintHelper().isMultiValueConstraint( annotationType ) ) {
constraints.addAll( constraintCreationContext.getConstraintHelper().getConstraintsFromMultiValueConstraint( annotation ) );
}
// 将约束转成ConstraintDescriptorImpl集合
return constraints.stream()
.map( c -> buildConstraintDescriptor( constrainable, c, type ) )
.collect( Collectors.toList() );
}
在将约束转成ConstraintDescriptorImpl集合时,使用了buildConstraintDescriptor()方法。
/**
* 将约束信息封装成ConstraintDescriptorImpl对象
*/
private <A extends Annotation> ConstraintDescriptorImpl<A> buildConstraintDescriptor(Constrainable constrainable,A annotation,ConstraintLocationKind type) {
/**
* 创建ConstraintDescriptorImpl对象时,会解析对应约束的类型是普通的约束还是跨参数约束。
*
*/
return new ConstraintDescriptorImpl<>(
constraintCreationContext.getConstraintHelper(),
constrainable,
new ConstraintAnnotationDescriptor<>( annotation ),
type
);
}
ConstraintDescriptorImpl类中的核心代码如下:
public class ConstraintDescriptorImpl<T extends Annotation> implements ConstraintDescriptor<T>, Serializable {
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
);
// 解析约束注解中的信息
this.groups = buildGroupSet( annotationDescriptor, implicitGroup );
this.payloads = buildPayloadSet( annotationDescriptor );
this.valueUnwrapping = determineValueUnwrapping( this.payloads, constrainable, annotationDescriptor.getType() );
this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );
// 获取所有的校验器
this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() )
.stream()
.map( ConstraintValidatorDescriptor::getValidatorClass )
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
// 获得添加了ValidationTarget.PARAMETERS的校验器
List<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
annotationDescriptor.getType(),
ValidationTarget.PARAMETERS
) );
// 获得添加了ValidationTarget.ANNOTATED_ELEMENT的校验器
List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
annotationDescriptor.getType(),
ValidationTarget.ANNOTATED_ELEMENT
) );
if ( crossParameterValidatorDescriptors.size() > 1 ) {
throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );
}
// 确定约束类型。类型为ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER
this.constraintType = determineConstraintType(
annotationDescriptor.getType(),
constrainable,
!genericValidatorDescriptors.isEmpty(),
!crossParameterValidatorDescriptors.isEmpty(),
externalConstraintType
);
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();
}
}
1.1.1 在ConstraintDescriptorImpl的构造方法中,调用constraintHelper.findValidatorDescriptors(Class<A> annotationType, ValidationTarget validationTarget),获取约束注解的校验器中添加了ValidationTarget.PARAMETERS和ValidationTarget.ANNOTATED_ELEMENT的校验器。
public class ConstraintHelper {
// 省略其他代码
/**
* 查找annotationType约束注解的校验器。通过约束注解中的@Constraint(validatedBy = xxx.class),找到校验器
*/
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValidatorDescriptors(Class<A> annotationType) {
Contracts.assertNotNull( annotationType, MESSAGES.classCannotBeNull() );
return validatorDescriptors.computeIfAbsent( annotationType, a -> getDefaultValidatorDescriptors( a ) );
}
/**
* 查找annotationType约束注解的校验器中添加了validationTarget的约束校验器描述符集合。
* 通过约束注解中的@Constraint(validatedBy = xxx.class),找到校验器,判断对应校验器是否添加了validationTarget
*/
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> findValidatorDescriptors(Class<A> annotationType, ValidationTarget validationTarget) {
return getAllValidatorDescriptors( annotationType ).stream()
.filter( d -> supportsValidationTarget( d, validationTarget ) )
.collect( Collectors.toList() );
}
/**
* 查找annotationType约束注解的校验器中添加了validationTarget的约束校验器描述符集合
*/
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValidatorDescriptors(Class<A> annotationType) {
Contracts.assertNotNull( annotationType, MESSAGES.classCannotBeNull() );
return validatorDescriptors.computeIfAbsent( annotationType, a -> getDefaultValidatorDescriptors( a ) );
}
/**
* 返回annotationType约束类型的验证器。
* ConstraintValidatorDescriptor的validationTargets会存储ValidationTarget.ANNOTATED_ELEMENT或ValidationTarget.ANNOTATED_PARAMETER【针对跨参数校验】
*/
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {
// 安全性校验
final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) enabledBuiltinConstraints
.get( annotationType );
if ( builtInValidators != null ) {
return builtInValidators;
}
// 获取约束注解中的约束校验器类。即实现了ConstraintValidator的类,可以添加多个
Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType
.getAnnotation( Constraint.class )
.validatedBy();
// 将ConstraintValidator的实现类调用ConstraintValidatorDescriptor.forClass()封装成ConstraintValidatorDescriptor对象。
// 在该方法中会解析ConstraintValidator的实现类添加的注解信息
return Stream.of( validatedBy )
.map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) )
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
}
/**
* 判断给定的validatorDescriptor中的validationTarget是否包含target
*/
private boolean supportsValidationTarget(ConstraintValidatorDescriptor<?> validatorDescriptor, ValidationTarget target) {
return validatorDescriptor.getValidationTargets().contains( target );
}
}
并将校验器的信息封装成ConstraintValidatorDescriptor对象。实际类型为ClassBasedValidatorDescriptor,其中validationTargets属性记录当前校验器的校验目标。值为ValidationTarget.ANNOTATED_ELEMENT或ValidationTarget.ANNOTATED_PARAMETER【针对跨参数校验】
public interface ConstraintValidatorDescriptor<A extends Annotation> {
/**
* 调用ClassBasedValidatorDescriptor.of(validatorClass, constraintAnnotationType )封装成ClassBasedValidatorDescriptor对象
* ClassBasedValidatorDescriptor实现了ConstraintValidatorDescriptor
*/
static <A extends Annotation> ConstraintValidatorDescriptor<A> forClass(Class<? extends ConstraintValidator<A, ?>> validatorClass,
Class<? extends Annotation> constraintAnnotationType) {
return ClassBasedValidatorDescriptor.of( validatorClass, constraintAnnotationType );
}
}
class ClassBasedValidatorDescriptor<A extends Annotation> implements ConstraintValidatorDescriptor<A> {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
// 约束校验器类
private final Class<? extends ConstraintValidator<A, ?>> validatorClass;
// 约束校验器的类型
private final Type validatedType;
// 校验目标。ValidationTarget.ANNOTATED_ELEMENT或ValidationTarget.ANNOTATED_PARAMETER【针对跨参数校验】
private final EnumSet<ValidationTarget> validationTargets;
private ClassBasedValidatorDescriptor(Class<? extends ConstraintValidator<A, ?>> validatorClass) {
this.validatorClass = validatorClass;
this.validatedType = TypeHelper.extractValidatedType( validatorClass );
this.validationTargets = determineValidationTargets( validatorClass );
}
public static <T extends Annotation> ClassBasedValidatorDescriptor<T> of(Class<? extends ConstraintValidator<T, ?>> validatorClass,
Class<? extends Annotation> registeredConstraintAnnotationType) {
// 安全性检查
Type definedConstraintAnnotationType = TypeHelper.extractConstraintType( validatorClass );
if ( !registeredConstraintAnnotationType.equals( definedConstraintAnnotationType ) ) {
throw LOG.getConstraintValidatorDefinitionConstraintMismatchException( validatorClass, registeredConstraintAnnotationType,
definedConstraintAnnotationType );
}
return new ClassBasedValidatorDescriptor<T>( validatorClass );
}
/**
* Constraint checking is relaxed for built-in constraints as they have been carefully crafted so we are sure types
* are right.
*/
public static <T extends Annotation> ClassBasedValidatorDescriptor<T> ofBuiltin(Class<? extends ConstraintValidator<T, ?>> validatorClass,
Class<? extends Annotation> registeredConstraintAnnotationType) {
return new ClassBasedValidatorDescriptor<T>( validatorClass );
}
/**
* 获取校验器类的校验目标
*/
private static EnumSet<ValidationTarget> determineValidationTargets(Class<? extends ConstraintValidator<?, ?>> validatorClass) {
// 获取@SupportedValidationTarget注解
SupportedValidationTarget supportedTargetAnnotation = validatorClass.getAnnotation(
SupportedValidationTarget.class );
// 默认是ValidationTarget.ANNOTATED_ELEMENT
if ( supportedTargetAnnotation == null ) {
return EnumSet.of( ValidationTarget.ANNOTATED_ELEMENT );
}
else {
// 返回@SupportedValidationTarget注解的value值。在跨参数校验中,此处返回ValidationTarget.PARAMETERS
return EnumSet.copyOf( Arrays.asList( supportedTargetAnnotation.value() ) );
}
}
}
1.1.2 在ConstraintDescriptorImpl的构造方法中,调用ConstraintDescriptorImpl.determineConstraintType()方法,确定约束类型,并赋值给constraintType,值为ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER。对于参数校验的校验器,constraintType为ConstraintType.CROSS_PARAMETER
/**
* 确定约束类型。类型为ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER
*/
private ConstraintType determineConstraintType(Class<? extends Annotation> constraintAnnotationType,
Constrainable constrainable,
boolean hasGenericValidators,
boolean hasCrossParameterValidator,
ConstraintType externalConstraintType) {
ConstraintTarget constraintTarget = validationAppliesTo;
ConstraintType constraintType = null;
boolean isExecutable = constraintLocationKind.isExecutable();
// 目标明确设置为RETURN_VALUE,则为ConstraintType.GENERIC
if ( constraintTarget == ConstraintTarget.RETURN_VALUE ) {
if ( !isExecutable ) {
throw LOG.getParametersOrReturnValueConstraintTargetGivenAtNonExecutableException(
annotationDescriptor.getType(),
ConstraintTarget.RETURN_VALUE
);
}
constraintType = ConstraintType.GENERIC;
}
// 目标明确设置为PARAMETERS,则返回ConstraintType.CROSS_PARAMETER。跨参数校验
else if ( constraintTarget == ConstraintTarget.PARAMETERS ) {
if ( !isExecutable ) {
throw LOG.getParametersOrReturnValueConstraintTargetGivenAtNonExecutableException(
annotationDescriptor.getType(),
ConstraintTarget.PARAMETERS
);
}
constraintType = ConstraintType.CROSS_PARAMETER;
}
//target set by external context (e.g. <return-value> element in XML or returnValue() method in prog. API)
else if ( externalConstraintType != null ) {
constraintType = externalConstraintType;
}
//target set to IMPLICIT or not set at all
else {
// 没有设置目标的。通过添加的校验器来判断。
// 如果校验器有添加了@SupportedValidationTarget,且value为ValidationTarget.PARAMETE,
// 则hasCrossParameterValidator为true,会返回ConstraintType.CROSS_PARAMETE
if ( hasGenericValidators && !hasCrossParameterValidator ) {
constraintType = ConstraintType.GENERIC;
}
else if ( !hasGenericValidators && hasCrossParameterValidator ) {
constraintType = ConstraintType.CROSS_PARAMETER;
}
else if ( !isExecutable ) {
constraintType = ConstraintType.GENERIC;
}
// 如果校验器有添加了@SupportedValidationTarget,且value不为ValidationTarget.ANNOTATED_ELEMENT,则返回ConstraintType.CROSS_PARAMETE
else if ( constraintAnnotationType.isAnnotationPresent( SupportedValidationTarget.class ) ) {
SupportedValidationTarget supportedValidationTarget = constraintAnnotationType.getAnnotation( SupportedValidationTarget.class );
if ( supportedValidationTarget.value().length == 1 ) {
constraintType = supportedValidationTarget.value()[0] == ValidationTarget.ANNOTATED_ELEMENT ? ConstraintType.GENERIC : ConstraintType.CROSS_PARAMETER;
}
}
//try to derive from existence of parameters/return value
//hence look only if it is a callable
else if ( constrainable instanceof Callable ) {
boolean hasParameters = constrainable.as( Callable.class ).hasParameters();
boolean hasReturnValue = constrainable.as( Callable.class ).hasReturnValue();
if ( !hasParameters && hasReturnValue ) {
constraintType = ConstraintType.GENERIC;
}
else if ( hasParameters && !hasReturnValue ) {
constraintType = ConstraintType.CROSS_PARAMETER;
}
}
}
// Now we are out of luck
if ( constraintType == null ) {
throw LOG.getImplicitConstraintTargetInAmbiguousConfigurationException( annotationDescriptor.getType() );
}
if ( constraintType == ConstraintType.CROSS_PARAMETER ) {
validateCrossParameterConstraintType( constrainable, hasCrossParameterValidator );
}
return constraintType;
}
1.2 通过1.1查找到方法添加的所有约束,并通过约束类型ConstraintType转为Map对象,key为ConstraintType。ConstraintType为ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER【跨参数约束类型】。通过ConstraintType.CROSS_PARAMETER,调用convertToMetaConstraints()方法,获取跨参数约束crossParameterConstraints。
private Set<MetaConstraint<?>> convertToMetaConstraints(List<ConstraintDescriptorImpl<?>> constraintDescriptors, Callable callable) {
if ( constraintDescriptors == null || constraintDescriptors.isEmpty() ) {
return Collections.emptySet();
}
Set<MetaConstraint<?>> constraints = newHashSet( constraintDescriptors.size() );
// 定义约束位置对象,分别为返回值和跨参数两种类型。不同类型的ConstraintLocation,传入校验器的isValid()的参数不同
// ConstraintLocation.forReturnValue()创建一个ReturnValueConstraintLocation返回
ConstraintLocation returnValueLocation = ConstraintLocation.forReturnValue( callable );
// ConstraintLocation.forCrossParameter()创建一个CrossParameterConstraintLocation返回
ConstraintLocation crossParameterLocation = ConstraintLocation.forCrossParameter( callable );
for ( ConstraintDescriptorImpl<?> constraintDescriptor : constraintDescriptors ) {
// 如果是ConstraintType.GENERIC类型,则使用returnValueLocation
// 针对跨参数校验,location为crossParameterLocation,为CrossParameterConstraintLocation类型对象
ConstraintLocation location = constraintDescriptor.getConstraintType() == ConstraintType.GENERIC
? returnValueLocation
: crossParameterLocation;
// 将location保存到MetaConstraints
constraints.add( MetaConstraints.create( constraintCreationContext.getTypeResolutionHelper(),
constraintCreationContext.getValueExtractorManager(),
constraintCreationContext.getConstraintValidatorManager(), constraintDescriptor, location ) );
}
return constraints;
}
针对跨参数约束,在MetaConstraint中的location为CrossParameterConstraintLocation对象。
1.3 封装成ConstrainedExecutable对象,传入crossParameterConstraints等信息。
类中添加的约束元数据信息首次解析是在BeanMetaDataManagerImpl的createBeanMetaData()方法中。首次解析调用流程详见
Spring validation参数校验原理解析之Controller控制器参数校验中@RequestBody参数校验实现原理-CSDN博客
的ValidatorImpl.validate()部分。
此处详细说一下BeanMetaDataManagerImpl的createBeanMetaData()方法。
private <T> BeanMetaDataImpl<T> createBeanMetaData(Class<T> clazz) {
BeanMetaDataBuilder<T> builder = BeanMetaDataBuilder.getInstance(
constraintCreationContext, executableHelper, parameterNameProvider,
validationOrderGenerator, clazz, methodValidationConfiguration );
for ( MetaDataProvider provider : metaDataProviders ) {
// getBeanConfigurationForHierarchy()方法遍历beanClass及其父类,调用AnnotaionMetaDataProvider.getBeanConfiguration()方法
// 获取对应类添加的约束注解,封装成BeanConfiguration对象
for ( BeanConfiguration<? super T> beanConfiguration : getBeanConfigurationForHierarchy( provider, clazz ) ) {
// 在BeanMetaDataBuilder中添加BeanConfiguration对象
// BeanMetaDataBuilder.add()【获取并遍历约束元素,执行addMetaDataToBuilder()方法】 -> addMetaDataToBuilder()
//【执行methodBuilder.add( constrainedElement )】 -> ExecutableMetaData.Builder.add(),
// 在该方法中,执行constrainedExecutable.getCrossParameterConstraints(),获取跨参数校验约束。加入到Builder中
builder.add( beanConfiguration );
}
}
// 将类中添加的约束信息封装成BeanMetaDataImpl对象。
// BeanMetaDataBuilder.build()【遍历builders,执行builder.build()】 ->
// BuilderDelegate.build()【执行methodBuilder.build()】 -> ExecutableMetaData.build()【new一个ExecutableMetaData对象。
// 调用adaptOriginsAndImplicitGroups( crossParameterConstraints )对约束进行适配修改,获得跨参数约束集合,
// 保存在crossParameterConstraints属性中】
return builder.build();
}
最终将解析的跨参数约束信息crossParameterConstraints添加到ExecutableMetaData对象中。
二、跨参数校验
跨参数校验的过程同基本数据类型的参数校验。在校验的时候,会执行ValidatorImpl.validateParametersForSingleGroup()方法。详见:
Spring validation参数校验原理解析之基本类型参数及Service层方法参数校验实现原理-CSDN博客
private <T> void validateParametersForSingleGroup(ExecutableValidationContext<T> validationContext, Object[] parameterValues, ExecutableMetaData executableMetaData, Class<?> currentValidatedGroup) {
// 判断是否有跨参数约束信息
if ( !executableMetaData.getCrossParameterConstraints().isEmpty() ) {
ValueContext<T, Object> valueContext = getExecutableValueContext(
validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
);
// 跨参数校验。方法入参中的多个参数联合校验,例如日期期间,结束日期要大于起始日期等
// 如果是跨参数校验,此处传入到约束校验器ConstraintValidator的isValid()方法的是所有的参数值parameterValues
validateMetaConstraints( validationContext, valueContext, parameterValues, executableMetaData.getCrossParameterConstraints() );
if ( shouldFailFast( validationContext ) ) {
return;
}
}
ValueContext<T, Object> valueContext = getExecutableValueContext(
validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
);
// 参数校验。遍历方法的每个参数,分别进行校验
for ( int i = 0; i < parameterValues.length; i++ ) {
ParameterMetaData parameterMetaData = executableMetaData.getParameterMetaData( i );
Object value = parameterValues[i];
if ( value != null ) {
Class<?> valueType = value.getClass();
if ( parameterMetaData.getType() instanceof Class && ( (Class<?>) parameterMetaData.getType() ).isPrimitive() ) {
valueType = ReflectionHelper.unBoxedType( valueType );
}
if ( !TypeHelper.isAssignable(
TypeHelper.getErasedType( parameterMetaData.getType() ),
valueType
) ) {
throw LOG.getParameterTypesDoNotMatchException(
valueType,
parameterMetaData.getType(),
i,
validationContext.getExecutable()
);
}
}
// 执行校验
validateMetaConstraints( validationContext, valueContext, parameterValues, parameterMetaData );
if ( shouldFailFast( validationContext ) ) {
return;
}
}
}
从上面BeanMetaDataManagerImpl的createBeanMetaData()方法可知,如果方法添加了跨参数校验,那么ExecutableMetaData.getCrossParameterConstraints()不为空,所以会执行validateMetaConstraints(BaseBeanValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent,Iterable<MetaConstraint<?>> constraints),其中constraints为crossParameterConstraints。
在validateMetaConstraints()方法中,会遍历constraints约束,然后执行validateMetaConstraint()
private boolean validateMetaConstraint(BaseBeanValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent, MetaConstraint<?> metaConstraint) {
BeanValueContext.ValueState<Object> originalValueState = valueContext.getCurrentValueState();
valueContext.appendNode( metaConstraint.getLocation() );
boolean success = true;
if ( isValidationRequired( validationContext, valueContext, metaConstraint ) ) {
// 在执行校验之前,如果方法的参数有值,则为valueContext赋值
if ( parent != null ) {
// metaConstraint.getLocation()中返回ConstraintLocation。valueContext.getValue()方法只有一行代码,即返回ConstraintLocation.getValue()
// 如果是跨参数校验,则为CrossParameterConstraintLocation,在getValue()方法中,返回传入的parent,即方法的参数值数组
valueContext.setCurrentValidatedValue( valueContext.getValue( parent, metaConstraint.getLocation() ) );
}
success = metaConstraint.validateConstraint( validationContext, valueContext );
validationContext.markConstraintProcessed( valueContext.getCurrentBean(), valueContext.getPropertyPath(), metaConstraint );
}
valueContext.resetValueState( originalValueState );
return success;
}
在执行校验之前,执行valueContext的setCurrentValidatedValue(),为属性currentValue赋值为方法的入参值数组,作为ConstraintValidator.isValid()的第一个入参。
在metaConstraint.validateConstraint()中真正执行校验。详见
Spring validation参数校验原理解析之Controller控制器参数校验中@RequestBody参数校验实现原理_@requestbody 校验字段-CSDN博客
总结
在第一篇Spring validation的源码分享中已经介绍过,Hibernate validation的设计比较复杂,要一次性全部分析清楚很困难,关联的细节很多。所以《Spring validation参数校验系列》文章通过从整体到细节,在每一篇中,不影响主题内容的情况下,穿插引入一些细节。在分享中,也会暂时忽略一些细节,留在下一篇讲解。建议如果本篇不太理解的,可以看看该系列的上一篇或者下一篇源码讲解文章。
本篇的源码比较多,细节也比较多。此处做一个总结。
1、通过AnnotaionMetaDataProvider.findExecutableMetaData()方法,查找方法添加的所有约束信息。
1.1 getParameterMetaData()解析入参约束;
1.2 findConstraints()解析方法的约束【返回值以及跨参数校验】,存放在Map中,key为对应的类型ConstraintType.GENERIC或ConstraintType.CROSS_PARAMETER。对应约束注解的校验器【实现ConstraintValidator接口的类】添加了@SupportedValidationTarget(ValidationTarget.PARAMETERS)的,为ConstraintType.CROSS_PARAMETER类型;
1.3、执行crossParameterConstraints = convertToMetaConstraints(executableConstraints.get( ConstraintType.CROSS_PARAMETER ),javaBeanExecutable),获取跨参数校验约束信息,封装成MetaConstraint集合对象。其中MetaConstraint的location为CrossParameterConstraintLocation对象;
1.4、将crossParameterConstraints等信息封装在ConstrainedExecutable对象中,最后类中添加的所有约束都封装在BeanConfiguration中;针对不同的MetaDataProvider,一个类会创建不同的BeanConfiguration,同时也会解析该类的父类。MetaDataProvider有XML、Annotation和程序API三种方式。在上一篇源码中有讲解;
2、在BeanMetaDataManagerImpl.createBeanMetaData()方法中,将1中获得的BeanConfiguration根据不同的约束类型,使用不同的Builder【MetaDataBuilder、ExecutableMetaData.Builder】归类创建ConstraintMetaData。最后封装成BeanMetaDataImpl对象;在BeanMetaDataImpl对象中,会根据约束的种类进行分类。同时存放分组、分组序列等。
3、在ValidatorImpl.validateParametersForSingleGroup()校验中,会先判断是否有crossParameterConstraints。如果有,多执行一次validateMetaConstraints()方法。流程和基本数据类型一样,唯一的区别在于ValidatorImpl.validateMetaConstraint()方法中,在执行metaConstraint.validateConstraint()时,会重新执行valueContext.setCurrentValidatedValue( valueContext.getValue( parent, metaConstraint.getLocation() ) ),获取要传入ConstraintValidator.isValid()的参数。针对跨参数校验,metaConstraint.getLocation()返回CrossParameterConstraintLocation,此时设置到valContext的值为参数数组;
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨,一起学习。