【源码】Spring validation参数校验原理解析之Controller控制器参数校验中@RequestBody参数校验实现原理

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参数校验系列》前两篇文章分别分享了Spring validation的基本使用以及自定义校验规则、编程式校验等进阶功能。本文重新另起一篇,从源码分析一下Spring validation中@RequestBody参数校验实现原理,让大家知其然知其所以然。本文基于Spring-5.3.10源码分析。

温馨提醒:

Hibernate validation的设计比较复杂,要一次性全部分析清楚很困难,关联的细节很多。所以《Spring validation参数校验系列》文章通过从整体到细节,在每一篇中,不影响主题内容的情况下,穿插引入一些细节。在分享中,也会暂时忽略一些细节,留在下一篇讲解。建议如果本篇不太理解的,可以看看该系列的上一篇或者下一篇源码讲解文章。

@RequestBody注解参数校验入口

在SpringMVC中,使用RequestResponseBodyMethodProcessor解析带@RequestBody注解的参数以及处理@ResponseBody注解方法的返回值。参数校验的逻辑在resolveArgument()方法,核心代码如下:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
        // 将请求数据封装成参数对象
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
                // 执行数据校验
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}
}

validateIfApplicable()参数校验

resolveArgument()方法执行父类的validateIfApplicable()方法进行参数校验,代码如下:

    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        // 获取方法入参添加的注解
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
            // 遍历注解,判断是否为校验相关的注解。不是返回null
			Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
            // 如果是,则执行数据校验
			if (validationHints != null) {
				binder.validate(validationHints);
				break;
			}
		}
	}

2.1 ValidationAnnotationUtils类的代码如下:

public abstract class ValidationAnnotationUtils {

	private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

	@Nullable
	public static Object[] determineValidationHints(Annotation ann) {
        // 获取注解的类型
		Class<? extends Annotation> annotationType = ann.annotationType();
		String annotationName = annotationType.getName();
        // 如果是@Valid注解,则返回空对象数组
		if ("javax.validation.Valid".equals(annotationName)) {
			return EMPTY_OBJECT_ARRAY;
		}
        // 获取@Validated注解对象
		Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
		if (validatedAnn != null) {
			Object hints = validatedAnn.value();
            // 如果有添加@Validated注解,转换@Validated注解的信息为对象数组
			return convertValidationHints(hints);
		}
        // 如果注解的名称中是以Valid字符串开头的也会进行校验
		if (annotationType.getSimpleName().startsWith("Valid")) {
			Object hints = AnnotationUtils.getValue(ann);
			return convertValidationHints(hints);
		}
		return null;
	}

	private static Object[] convertValidationHints(@Nullable Object hints) {
		if (hints == null) {
			return EMPTY_OBJECT_ARRAY;
		}
		return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
	}

}

ValidationAnnotationUtils的determineValidationHints()方法对注解进行判断,如果是@Validated、@Valid或者Valid字符串开头的注解,返回一个Object[]数组;否则返回null。

注:从这个方法可以发现@Validated和@Valid可以混用。如果是@Validated,返回的数组为value值,即校验分组。不明白的可以看本人的另一篇博客

Spring validation参数校验基本使用-CSDN博客

如果是@Valid,返回空的Object数组。从此处可知只有@Validated注解支持分组校验。

2.2 如果2.1的ValidationAnnotationUtils的determineValidationHints()方法返回不为null,则执行binder.validate(validationHints)进行校验。DataBinder.java的validate()方法如下:

    public void validate(Object... validationHints) {
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidators()) {
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
                // 有分组的,执行分组的校验
				((SmartValidator) validator).validate(target, bindingResult, validationHints);
			}
			else if (validator != null) {
                // 没分组的,执行无分组的校验
				validator.validate(target, bindingResult);
			}
		}
	}

无论是否有分组,最终都是调用ValidatorAdapter的validate()方法。

而后执行SpringValidatorAdapter的validate()方法

    @Override
	public void validate(Object target, Errors errors, Object... validationHints) {
		if (this.targetValidator != null) {
            // 从上面的界面中可知,this.targetValidator为ValidatorImpl对象
			processConstraintViolations(
					this.targetValidator.validate(target, asValidationGroups(validationHints)), errors);
		}
	}

从上面的截图可知,this.targetValidator为ValidatorImpl对象,所以执行ValidatorImpl的validate()方法。

ValidatorImpl.validate()

    @Override
	public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
        // 数据有效性校验
		Contracts.assertNotNull( object, MESSAGES.validatedObjectMustNotBeNull() );
		sanityCheckGroups( groups );

		@SuppressWarnings("unchecked")
		Class<T> rootBeanClass = (Class<T>) object.getClass();
        // 获取添加@Validated或者@Valid注解的入参对象类型的BeanMetaData
		BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass );

		if ( !rootBeanMetaData.hasConstraints() ) {
			return Collections.emptySet();
		}
        // 将类以及类中添加的约束信息封装成BaseBeanValidationContext对象
		BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidate( rootBeanClass, rootBeanMetaData, object );

        // 校验组生成组和序列的顺序。如果groups为空,则添加Default.class组
		ValidationOrder validationOrder = determineGroupValidationOrder( groups );
        // 再次封装成BeanValueContext上下文对象
		BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForBean(
				validatorScopedContext.getParameterNameProvider(),
				object,
				validationContext.getRootBeanMetaData(),
				PathImpl.createRootPath()
		);
        // 校验,并返回校验结果
		return validateInContext( validationContext, valueContext, validationOrder );
	}

3.1 BeanMetaDataManagerImpl的getBeanMetaData(Class<T> beanClass)方法,对应的beanClass首次调用时,会先调用createBeanMetaData()方法,遍历beanClass及其父类,调用BeanMetaDataBuilder.build()方法,创建BeanMetaData对象,并以类名为key,保存到ConcurrentReferenceHashMap对象中缓存起来;再次调用getBeanMetaData()方法时,直接从缓存中获取。

BeanMetaData对象中,解析并报存了bean类中的属性、方法、构造器、方法参数、构造器参数中添加的约束注解的信息。以便后续校验时,可以直接使用。

3.2 determineGroupValidationOrder( groups )通过校验组生成组和序列的顺序时,如果groups为空,则添加Default.class组。即Spring validation校验都是分组校验,只是如果没有指定分组,则为默认分组。

ValidatorImpl.validateInContext()

执行ValidatorImpl的validateInContext()方法进行校验并返回校验结果。在该方法中,遍历校验组,执行validateConstraintsForCurrentGroup()方法。

    private void validateConstraintsForCurrentGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<?, Object> valueContext) {
		// 如果不是默认组,则调用validateConstraintsForNonDefaultGroup()直接进行校验
		if ( !valueContext.validatingDefault() ) {
			validateConstraintsForNonDefaultGroup( validationContext, valueContext );
		}
		else {
            // 如果是默认组,则要判断当前类的父类是否有添加分组
			validateConstraintsForDefaultGroup( validationContext, valueContext );
		}
	}

无论是否是默认组,都会调用validateMetaConstraints()。

    private void validateMetaConstraints(BaseBeanValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent,
			Iterable<MetaConstraint<?>> constraints) {
        // 循环遍历添加的约束,执行validateMetaConstraint()方法。
		for ( MetaConstraint<?> metaConstraint : constraints ) {
			validateMetaConstraint( validationContext, valueContext, parent, metaConstraint );
            // 调用shouldFailFast()判断是否添加了快速失败,且校验失败,则会结束校验过程
			if ( shouldFailFast( validationContext ) ) {
				break;
			}
		}
	}

4.1 validateMetaConstraint()会调用metaConstraint.validateConstraint( validationContext, valueContext ),最后会调用SimpleConstraintTree.validateConstraints()方法进行校验。

4.2 在shouldFailFast( validationContext )方法中,会调用isFailFastModeEnabled()来判断是否设置了快速失败,该属性为failFast。所以如果要快速失败,在配置文件中添加failFast配置。如下:

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 只要出现校验失败的情况就立即结束校验
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

 SimpleConstraintTree.validateConstraints()

在该方法中,获取校验对应的校验器,然后执行校验。

    @Override
	protected void validateConstraints(ValidationContext<?> validationContext,
			ValueContext<?, ?> valueContext,
			Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {

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

		// 获取约束对应的约束器
		ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );

		// create a constraint validator context
		ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(
				descriptor, valueContext.getPropertyPath()
		);

		// 进行校验
		if ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) {
			violatedConstraintValidatorContexts.add( constraintValidatorContext );
		}
	}

5.1 getInitializedConstraintValidator( validationContext, valueContext )为父类ConstraintTree的方法,该方法会判断当前的校验器是否存在,如果不存在,调用ConstraintValidatorManagerImpl的getInitializedValidator()方法。

    @Override
	public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(
			Type validatedValueType,
			ConstraintDescriptorImpl<A> descriptor,
			ConstraintValidatorFactory constraintValidatorFactory,
			HibernateConstraintValidatorInitializationContext initializationContext) {
        // 参数有效性检查
		Contracts.assertNotNull( validatedValueType );
		Contracts.assertNotNull( descriptor );
		Contracts.assertNotNull( constraintValidatorFactory );
		Contracts.assertNotNull( initializationContext );

		CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(), validatedValueType, constraintValidatorFactory, initializationContext );

		@SuppressWarnings("unchecked")
        // 先从缓存中获取对应约束的约束器
		ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key );

		if ( constraintValidator == null ) {
            // 如果没有对应的约束器,则创建并初始化约束器
			constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
            // 将约束器添加到缓存
			constraintValidator = cacheValidator( key, constraintValidator );
		}
		else {
			LOG.tracef( "Constraint validator %s found in cache.", constraintValidator );
		}

		return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
	}

先从缓存获取对应约束的约束器,如果没有,执行父类的AbstractConstraintValidatorManagerImpl.createAndInitializeValidator()方法,创建约束器,并进行初始化,执行初始化方法。

    protected <A extends Annotation> ConstraintValidator<A, ?> createAndInitializeValidator(
			Type validatedValueType,
			ConstraintDescriptorImpl<A> descriptor,
			ConstraintValidatorFactory constraintValidatorFactory,
			HibernateConstraintValidatorInitializationContext initializationContext) {

		ConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType );
		ConstraintValidator<A, ?> constraintValidator;

		if ( validatorDescriptor == null ) {
			return null;
		}
        // 通过校验器的描述符,newInstance()获得一个校验器对象
		constraintValidator = validatorDescriptor.newInstance( constraintValidatorFactory );
        // 执行校验器的initialize()进行初始化
		initializeValidator( descriptor, constraintValidator, initializationContext );

		return constraintValidator;
	}

该方法会调用AbstractConstraintValidatorManagerImpl的initializeValidator()方法。


	private <A extends Annotation> void initializeValidator(
			ConstraintDescriptor<A> descriptor,
			ConstraintValidator<A, ?> constraintValidator,
			HibernateConstraintValidatorInitializationContext initializationContext) {
		try {
			if ( constraintValidator instanceof HibernateConstraintValidator ) {
				( (HibernateConstraintValidator<A, ?>) constraintValidator ).initialize( descriptor, initializationContext );
			}
            // 执行约束器的初始化方法
			constraintValidator.initialize( descriptor.getAnnotation() );
		}
		catch (RuntimeException e) {
			if ( e instanceof ConstraintDeclarationException ) {
				throw e;
			}
			throw LOG.getUnableToInitializeConstraintValidatorException( constraintValidator.getClass(), e );
		}
	}

5.2 执行父类ConstraintTree的validateSingleConstraint( valueContext, constraintValidatorContext, validator )进行校验。

    protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConstraint(
			ValueContext<?, ?> valueContext,
			ConstraintValidatorContextImpl constraintValidatorContext,
			ConstraintValidator<A, V> validator) {
		boolean isValid;
		try {
			@SuppressWarnings("unchecked")
            // 获取当前的值
			V validatedValue = (V) valueContext.getCurrentValidatedValue();
            // 执行校验器的isValid()方法
			isValid = validator.isValid( validatedValue, constraintValidatorContext );
		}
		catch (RuntimeException e) {
			if ( e instanceof ConstraintDeclarationException ) {
				throw e;
			}
			throw LOG.getExceptionDuringIsValidCallException( e );
		}
		if ( !isValid ) {
			// 校验失败,返回constraintValidatorContext
			return Optional.of( constraintValidatorContext );
		}
		return Optional.empty();
	}

看到这里是不是很熟悉了,如果还不清楚,可以看看

Spring validation参数校验之自定义校验规则及编程式校验等进阶篇-CSDN博客

这篇博客中提到的自定义校验规则的部分。

package com.jingai.validation.custom;

import org.apache.logging.log4j.util.Strings;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 验证给定的String值是否满足InEnum注解中的约束
 */
public class InEnumValidator implements ConstraintValidator<InEnum, String> {

    private Class<? extends BaseEnum> enumType;
    private String enumValues = Strings.EMPTY;
    private Object obj = new Object();

    /**
     * 初始化方法可以获得该校验器对应的注解,从而获取注解中的信息。在校验方法调用前会调用
     */
    @Override
    public void initialize(InEnum constraintAnnotation) {
        enumType = constraintAnnotation.enumType();
    }

    /**
     * 校验。成功返回true,失败返回false
     */
    @Override
    public boolean isValid(String str, ConstraintValidatorContext constraintValidatorContext) {
        System.out.println("in enum str : " + str);
        if(!StringUtils.hasText(str)) {
            return true;
        }
        synchronized (enumValues) {
            if(enumType == null || !enumType.isEnum()) {
                return true;
            }
            for (BaseEnum e :enumType.getEnumConstants()){
                System.out.println("in enum str : " + str + ", e : " + e.getCode() + ", " + e.getDisplay());
                if(e.getCode().equals(str)) {
                    return true;
                }
            }
            if(!StringUtils.hasText(enumValues)) {
                for (BaseEnum e :enumType.getEnumConstants()){
                    enumValues += ";" + e.getCode() + ":" + e.getDisplay();
                }
                enumValues = enumValues.substring(1);
            }
            // 返回动态的校验失败信息。如果不添加如下两句代码,返回注解中的message信息
            constraintValidatorContext.disableDefaultConstraintViolation();
            constraintValidatorContext.buildConstraintViolationWithTemplate("值" + str + "没在枚举【" + enumValues + "】中")
                    .addConstraintViolation();
            return false;
        }
    }
}

通过上面的5.2中ConstraintTree的validateSingleConstraint()方法,当调用isValid()校验失败时,会返回constraintValidatorContext,所以在isValid()方法中,也可以通过constraintValidatorContext定义动态的校验失败信息。如上的代码,当性别不在枚举范围时,提示如下。

如果想要有更友好的提示,可以在使用@InEnum注解的message中添加占位符,在此时动态的替换占位符的信息。

自定义校验规则的调用栈信息

SpringValidatorAdapter.processConstraintViolations()

当执行完ValidatorImpl.validate()进行参数校验,校验结束后,会执行SpringValidatorAdapter的processConstraintViolations(),如果校验失败,会将错误信息添加到BindingResult对象中。然后返回到RequestResponseBodyMethodProcessor的resolveArgument()方法。

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
        // 将请求数据封装成参数对象
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
                // 执行数据校验,如果校验失败,会将失败信息添加到binder对象中
				validateIfApplicable(binder, parameter);
                // 如果有错误信息,则抛MethodArgumentNotValidException异常
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}
}

总结

以上为本次分享的从源码分析Spring validation中带@RequestBody注解的参数的校验原理,Spring validation使用的是Hibernate的validation,里面的细节很多,本人也没有全部研究明白,此处只分享了核心的部分,通过这些能够对@RequestBody注解的参数校验有更直观和深入的了解。

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

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值