Springboot2.x中的hibernate-validator(1)GET请求

1、自动配置

springboot中早已默认集成hibernate-validator,首先看下自动配置是如何配置validator的。
ValidationAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({ExecutableValidator.class})
@ConditionalOnResource(
    resources = {"classpath:META-INF/services/javax.validation.spi.ValidationProvider"}
)
@Import({PrimaryDefaultValidatorPostProcessor.class})
public class ValidationAutoConfiguration {
    public ValidationAutoConfiguration() {
    }

    @Bean
    @Role(2)
    @ConditionalOnMissingBean({Validator.class})
    public static LocalValidatorFactoryBean defaultValidator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
        factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
        return factoryBean;
    }

    @Bean
    @ConditionalOnMissingBean
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        boolean proxyTargetClass = ((Boolean)environment.getProperty("spring.aop.proxy-target-class", Boolean.class, Boolean.valueOf(true))).booleanValue();
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }
}

配置都是带ConditionalOnMissingBean,也就是说我们可以自定义。例如为validator增加快速失败的配置,校验过程只要有一个错误就立即返回,而不是默认的校验完所有再返回。
接下来看看MethodValidationPostProcessor
在这里插入图片描述
实现了接口InitializingBean,在bean初始化的时候手动创建了一个aop的切点(Pointcut)和通知器(advisor),定义了一个Advice——MethodValidationInterceptor。(aop部分参见spring-core#aop

	@Override
	public void afterPropertiesSet() {
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}

	/**
	 * Create AOP advice for method validation purposes, to be applied
	 * with a pointcut for the specified 'validated' annotation.
	 * @param validator the JSR-303 Validator to delegate to
	 * @return the interceptor to use (typically, but not necessarily,
	 * a {@link MethodValidationInterceptor} or subclass thereof)
	 * @since 4.2
	 */
	protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
		return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
	}

查看切点Pointcut的默认值:

private Class<? extends Annotation> validatedAnnotationType = Validated.class;

也就是说该aop仅对注解@Validated生效。MethodValidationInterceptor才是真正实现参数校验功能的。

2、跟踪一个接口

从MethodValidationInterceptor开始断点,跟踪一个接口的参数校验错误是如何返回的。

2.1获取校验分组

获取方法上是否有@Validated,若没有则找类上是否有@Validated注解,然后返回其中的value值,@Validated的value就表示校验分组。

	protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
		Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
		if (validatedAnn == null) {
			validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
		}
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}

2.2 validateParameters

2.2.1 检查参数是否有限制条件
	boolean hasConstraints = false;

		for ( ConstraintMetaData constraintMetaData : constraintMetaDataSet ) {
			boolean elementHasConstraints = constraintMetaData.isCascading() || constraintMetaData.isConstrained();
			hasConstraints |= elementHasConstraints;

			if ( constraintMetaData.getKind() == ElementKind.PROPERTY ) {
				propertyMetaDataSet.add( (PropertyMetaData) constraintMetaData );
			}
			else {
				ExecutableMetaData executableMetaData = (ExecutableMetaData) constraintMetaData;
				if ( elementHasConstraints ) {
					executableMetaDataSet.add( executableMetaData );
				}
				else {
					tmpUnconstrainedExecutables.addAll( executableMetaData.getSignatures() );
				}
			}
		}

没有则说明不要校验。

2.2.2 根据groups计算校验的顺序

groups为空时,默认设为DEFAULT_GROUPS

	private static final Collection<Class<?>> DEFAULT_GROUPS = Collections.<Class<?>>singletonList( Default.class );
public ValidationOrder getValidationOrder(Collection<Class<?>> groups) {
		if ( groups == null || groups.size() == 0 ) {
			throw LOG.getAtLeastOneGroupHasToBeSpecifiedException();
		}

		// HV-621 - if we deal with the Default group we return the default ValidationOrder. No need to
		// process Default as other groups which saves several reflection calls (HF)
		if ( groups.size() == 1 && groups.contains( Default.class ) ) {
			return ValidationOrder.DEFAULT_GROUP;
		}

		for ( Class<?> clazz : groups ) {
			//分组的定义必须要是Interface
			if ( !clazz.isInterface() ) {
				throw LOG.getGroupHasToBeAnInterfaceException( clazz );
			}
		}

		DefaultValidationOrder validationOrder = new DefaultValidationOrder();
		for ( Class<?> clazz : groups ) {
			if ( Default.class.equals( clazz ) ) { // HV-621
				validationOrder.insertGroup( Group.DEFAULT_GROUP );
			}
			else if ( isGroupSequence( clazz ) ) {
				insertSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), true, validationOrder );
			}
			else {
				Group group = new Group( clazz );
				validationOrder.insertGroup( group );
				insertInheritedGroups( clazz, validationOrder );
			}
		}

		return validationOrder;
	}

可以使用@GroupSequence指定分组的顺序


/**
 * 新增校验组
 */
public interface AddGroup {
}
 
/**
 * 更新校验组
 */
public interface UpdateGroup {
}
 
/**
 * 定义校验顺序
 */
@GroupSequence({AddGroup.class, UpdateGroup.class})
public interface Group {
}

DefaultValidationOrder数据结构

public final class DefaultValidationOrder implements ValidationOrder {

	private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

	/**
	 * The list of single groups to be used this validation.
	 */
	private List<Group> groupList;

	/**
	 * The different sequences for this validation. The map contains the sequences mapped to their sequence
	 * name.
	 */
	private Map<Class<?>, Sequence> sequenceMap;
	......
}
2.2.3 根据分组顺序依次执行validateParametersForGroup

若是快速失败模式,只要有一个分组失败了就立即返回。
执行单个分组校验validateParametersForSingleGroup

	private <T> void validateParametersForSingleGroup(ValidationContext<T> validationContext, Object[] parameterValues, ExecutableMetaData executableMetaData, Class<?> currentValidatedGroup) {
		if ( !executableMetaData.getCrossParameterConstraints().isEmpty() ) {
			ValueContext<T, Object> valueContext = getExecutableValueContext(
					validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
			);

			// 1. validate cross-parameter constraints
			validateMetaConstraints( validationContext, valueContext, parameterValues, executableMetaData.getCrossParameterConstraints() );
			if ( shouldFailFast( validationContext ) ) {
				return;
			}
		}

		ValueContext<T, Object> valueContext = getExecutableValueContext(
				validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
		);

		// 2. validate parameter constraints
		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;
			}
		}
	}

对每个参数调用validateMetaConstraints进行校验,并判断是否为快速失败模式。
根据参数上的校验注解和参数类型,找到对应的validator,调用其isValid方法。例如我对一个String类型的参数添加校验@NotEmpty,则这里的validator为NotEmptyValidatorForCharSequence。
ComposingConstraintTree的validateConstraints

	@Override
	protected <T> void validateConstraints(ValidationContext<T> validationContext,
			ValueContext<?, ?> valueContext,
			Set<ConstraintViolation<T>> constraintViolations) {
		CompositionResult compositionResult = validateComposingConstraints(
				validationContext, valueContext, constraintViolations
		);

		Set<ConstraintViolation<T>> localViolations;

		// After all children are validated the actual ConstraintValidator of the constraint itself is executed
		if ( mainConstraintNeedsEvaluation( validationContext, constraintViolations ) ) {

			if ( LOG.isTraceEnabled() ) {
				LOG.tracef(
						"Validating value %s against constraint defined by %s.",
						valueContext.getCurrentValidatedValue(),
						descriptor
				);
			}

			// find the right constraint validator
			ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );

			// create a constraint validator context
			ConstraintValidatorContextImpl constraintValidatorContext = new ConstraintValidatorContextImpl(
					validationContext.getParameterNames(),
					validationContext.getClockProvider(),
					valueContext.getPropertyPath(),
					descriptor,
					validationContext.getConstraintValidatorPayload()
			);

			// validate
			localViolations = validateSingleConstraint(
					validationContext,
					valueContext,
					constraintValidatorContext,
					validator
			);

			// We re-evaluate the boolean composition by taking into consideration also the violations
			// from the local constraintValidator
			if ( localViolations.isEmpty() ) {
				compositionResult.setAtLeastOneTrue( true );
			}
			else {
				compositionResult.setAllTrue( false );
			}
		}
		else {
			localViolations = Collections.emptySet();
		}

		if ( !passesCompositionTypeRequirement( constraintViolations, compositionResult ) ) {
			prepareFinalConstraintViolations(
					validationContext, valueContext, constraintViolations, localViolations
			);
		}
	}

getInitializedConstraintValidator正是在寻找当前校验对应的validator

	protected final <T> ConstraintValidator<A, ?> getInitializedConstraintValidator(ValidationContext<T> validationContext, ValueContext<?, ?> valueContext) {
		ConstraintValidator<A, ?> validator;

		if ( validationContext.getConstraintValidatorFactory() == validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory()
				&& validationContext.getConstraintValidatorInitializationContext() == validationContext.getConstraintValidatorManager()
						.getDefaultConstraintValidatorInitializationContext() ) {
			validator = constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext;

			if ( validator == null ) {
				synchronized ( this ) {
					validator = constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext;
					if ( validator == null ) {
						validator = getInitializedConstraintValidator( validationContext );

						constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext = validator;
					}
				}
			}
		}
		else {
			// For now, we don't cache the result in the ConstraintTree if we don't use the default constraint validator
			// factory. Creating a lot of CHM for that cache might not be a good idea and we prefer being conservative
			// for now. Note that we have the ConstraintValidatorManager cache that mitigates the situation.
			// If you come up with a use case where it makes sense, please reach out to us.
			validator = getInitializedConstraintValidator( validationContext );
		}

		if ( validator == DUMMY_CONSTRAINT_VALIDATOR ) {
			throw getExceptionForNullValidator( validatedValueType, valueContext.getPropertyPath().asString() );
		}

		return validator;
	}

真正调用validator.isValid

	protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
			ValueContext<?, ?> valueContext,
			ConstraintValidatorContextImpl constraintValidatorContext,
			ConstraintValidator<A, V> validator) {
		boolean isValid;
		try {
			@SuppressWarnings("unchecked")
			V validatedValue = (V) valueContext.getCurrentValidatedValue();
			isValid = validator.isValid( validatedValue, constraintValidatorContext );
		}
		catch (RuntimeException e) {
			if ( e instanceof ConstraintDeclarationException ) {
				throw e;
			}
			throw LOG.getExceptionDuringIsValidCallException( e );
		}
		if ( !isValid ) {
			//We do not add these violations yet, since we don't know how they are
			//going to influence the final boolean evaluation
			return executionContext.createConstraintViolations(
					valueContext, constraintValidatorContext
			);
		}
		return Collections.emptySet();
	}

3、如何根据参数上的校验注解最终准确找到validator

刚才的getInitializedConstraintValidator一步一步深入后会发现一个叫findMatchingValidatorDescriptor的方法。以@NotEmpty为例

	private <A extends Annotation> ConstraintValidatorDescriptor<A> findMatchingValidatorDescriptor(ConstraintDescriptorImpl<A> descriptor, Type validatedValueType) {
		//descriptor.getMatchingConstraintValidatorDescriptors()表示@NotEmpty对应的所有validator,availableValidatorDescriptors以被校验参数类型为key,validator为value
		Map<Type, ConstraintValidatorDescriptor<A>> availableValidatorDescriptors = TypeHelper.getValidatorTypes(
				descriptor.getAnnotationType(),
				descriptor.getMatchingConstraintValidatorDescriptors()
		);
		//判断当前校验的类型是否存在类型匹配的validator
		List<Type> discoveredSuitableTypes = findSuitableValidatorTypes( validatedValueType, availableValidatorDescriptors.keySet() );
		//匹配结果为多个时,移除所有的派生类型,只保留一个
		resolveAssignableTypes( discoveredSuitableTypes );

		if ( discoveredSuitableTypes.size() == 0 ) {
			return null;
		}

		if ( discoveredSuitableTypes.size() > 1 ) {
			throw LOG.getMoreThanOneValidatorFoundForTypeException( validatedValueType, discoveredSuitableTypes );
		}

		Type suitableType = discoveredSuitableTypes.get( 0 );
		//返回匹配的validator
		return availableValidatorDescriptors.get( suitableType );
	}

查看NotEmptyValidatorForCharSequence的源码

	public class NotEmptyValidatorForCharSequence implements ConstraintValidator<NotEmpty, CharSequence> {

	/**
	 * Checks the character sequence is not {@code null} and not empty.
	 *
	 * @param charSequence the character sequence to validate
	 * @param constraintValidatorContext context in which the constraint is evaluated
	 * @return returns {@code true} if the character sequence is not {@code null} and not empty.
	 */
	@Override
	public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
		if ( charSequence == null ) {
			return false;
		}
		return charSequence.length() > 0;
	}
}

实现接口ConstraintValidator的时候声明了注解的type和被校验参数的类型。
在这里插入图片描述
可以发现NotEmpty的validator有十几个实现类,根据type(NotEmpty)和被校验参数类型(String)可以唯一的定位到某一个validator(NotEmptyValidatorForCharSequence)。

发布了26 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_36142042/article/details/104949871