【源码】Spring validation参数校验之跨参数校验Cross-Parameter实现原理分析

 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的值为参数数组;

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨,一起学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/576647.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ssm082基于java斗车交易系统设计与实现+vue

斗车交易系统 摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&…

12.Blender 界面介绍(上)及物体基础编辑操作

设置语言 首先在菜单栏打开编辑-Preferences-界面-翻译&#xff0c;可以修改语言 这里使用的是Steam上下载的4.1版本 工具栏 左边的工具栏&#xff0c;按T就会出现&#xff0c;再按T就会隐藏 右边的工具栏是按N&#xff0c;按N显示&#xff0c;再按N隐藏 旋转画面 长按鼠…

C语言面试题之相交链表

相交链表 实例要求 1、给定两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。2、如果两个链表不存在相交节点&#xff0c;返回 null 。示例&#xff1a; 实例分析 可以使用两种方法&#xff1a;哈希表方法和双指针方法。哈希表方法…

Golang基础7-并发编程

并发编程 https://www.cnblogs.com/Survivalist/p/11527949.html 进程和线程、协程的区别_线程协程进程的区别-CSDN博客 Golang中的并发编程是一个重点&#xff0c;我们要了解Golang中的并发Goroutine因此需要先理解进程、线程、之后再理解协程。 进程&#xff1a;操作系统进…

某翻译平台翻译接口逆向之webpack学习

逆向网址 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw 逆向链接 aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLyMv 逆向接口 aHR0cHM6Ly9kaWN0LnlvdWRhby5jb20vd2VidHJhbnNsYXRl 逆向过程 请求方式 POST 逆向参数 sign c168e4cb76169e90f82d28118dbd24d2 接口请求结果解密 过程分析 根据XHR…

免费获取!遗传算法+多目标规划算法+自适应神经模糊系统程序代码!

前言 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世纪70年代提出&#xff0c;该算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型&#xff0c;通过数学的方式&#xff0c;将问题的求解过程转…

全国省级金融发展水平数据集(2000-2022年)

01、数据简介 金融发展水平是一个国家或地区经济实力和国际竞争力的重要体现。它反映了金融体系的成熟程度和发展水平&#xff0c;是衡量一个国家或地区经济发展质量的重要指标。金融发展水平的提高&#xff0c;意味着金融体系能够更好地服务实体经济&#xff0c;推动经济增长…

3.7设计模式——Observer 观察者模式(行为型)

意图 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于他的对象都得到通知并被自动更新。 结构 Subject&#xff08;目标&#xff09;知道它的观察者&#xff0c;可以有任意多个观察者观察同一个目标&#xff0c;提供注册和删…

模块三:二分——153.寻找旋转排序数组中的最小值

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找疑问 代码实现解法一&#xff1a;暴力查找解法二&#xff1a;CJava 题目描述 题目链接&#xff1a;153.寻找旋转排序数组中的最小值 根据题目的要求时间复杂度为O(log N)可知需要使用二分查找…

电子负载仪的远端控制

前言 最近研究了电子负载仪的远端控制&#xff08;区别于前面板控制&#xff09;&#xff0c;主要是用于程序控制&#xff0c;避免繁琐复杂的人工控制&#xff0c;举了南京嘉拓和艾维泰科的例子。 有纰漏请指出&#xff0c;转载请说明。 学习交流请发邮件 1280253714qq.com …

基于JavaWEB的学生考勤管理系统(含论文)

本系统是用Java语言写的&#xff0c;基于JavaWEB的学生考勤管理系统 主要有三大模块&#xff0c;学生&#xff0c;教师和管理员模块&#xff0c;功能如下&#xff1a; 学生模块 教师模块&#xff1a; 管理员模块

深入了解Semaphore、CountDownLatch等实用工具的用法

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

4月26(信息差)

&#x1f30d; 1170万台 华为跃升重回首位&#xff01;苹果跌至第五位 &#x1f384;工业软件大事件 —— OGG 1.0 发布&#xff0c;华为贡献全部源代码 ✨ 苹果发布 OpenELM&#xff1a;专为在设备端运行而设计的小型开源 AI 模型 1.FisheyeDetNet&#xff1a;首个基于鱼眼相…

LED驱动模块RSC6218A 5W-18W迷你高效驱动电源应用-REASUNOS(瑞森半导体)

一、LED驱动模块RSC6218A REASUNOS(瑞森半导体)通过持续投入研发&#xff0c;提升LLC应用技术&#xff0c;集成控制芯片与功率转换&#xff0c;成功推出新一代产品RSC6218A WSOP-16&#xff0c;延续瑞森LLC拓扑方案&#xff0c;时机趋势完全迎合我国双碳政策&#xff0c;电气特…

【Web】DASCTF X GFCTF 2024|四月开启第一局 题解(全)

目录 EasySignin cool_index SuiteCRM web1234 法一、条件竞争(没成功) 法二、session反序列化 EasySignin 先随便注册个账号登录&#xff0c;然后拿bp抓包改密码(username改成admin) 然后admin / 1234567登录 康好康的图片功能可以打SSRF&#xff0c;不能直接读本地文…

Docker网络模式与cgroup资源控制

前言 在 Docker 中&#xff0c;网络模式和 cgroup 资源控制作为关键功能&#xff0c;对于容器的性能优化和资源管理起着至关重要的作用。本文将介绍 Docker 的网络模式和cgroup资源控制&#xff0c;探讨不同网络模式的特点以及如何利用 cgroup 资源控制机制来有效管理容器的资…

不使用加减运算符实现整数加和减

文章目录 进位 进位 加粗 最近想出了不适用运算符实现加与减 首先按位与找出的是需不需要进位 按位与是两边同时为1,则为1,那么如果两边同时为1的话,是不是就该进位?所以我们用按位与来判断是否需要进位 然后再按位异或找出不同的位数 按位异或是两边不相等,也就是1 和 0的时…

SpringBoot源码阅读2-自动配置

SpringBoot源码阅读2-自动配置 在传统的Spring应用中&#xff0c;开发者需要手动配置一系列Web应用的核心组件&#xff0c;例如DispatcherServlet用于处理请求分发、ViewResolver用于视图解析、CharacterEncodingFilter用于字符编码过滤等。 然而在SpringBoot中只要引入了spr…

力扣HOT100 - 994. 腐烂的橘子

解题思路&#xff1a; 因为要记录轮数&#xff08;分钟数&#xff09;&#xff0c;所以不能一口气遍历到底&#xff0c;所以不能用深搜&#xff08;bfs&#xff09;&#xff0c;而要用广搜&#xff08;bfs&#xff0c;层序遍历&#xff09;。 先记录下新鲜橘子数&#xff0c;…

github+PicGo+obsidian来作为你的免费高效可靠图床吧

前提 一直以来 博客的图床问题都是个大问题 ,如何找到一个 可靠并且 方便的搭建方式 非常重要 今天介绍一种 githubpicGoobsidian的搭建方式 准备github库 生成个人github token 找到个人 设置 生成一个新token 或者已经有的直接用 新生成的token 需要记录下来 这可能是你最后…
最新文章