【源码】Spring validation参数校验原理解析之基本类型参数及Service层方法参数校验实现原理

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参数校验系列》的前四篇分享了基础使用、高级进阶使用以及@RequestBody和实体类参数校验实现原理,这一篇和大家分享一下第三种使用场景“基本类型参数校验”的实现原理。

温馨提醒:

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

MethodValidationPostProcessor

前面知识回顾

在上一篇

Spring validation参数校验原理解析之Controller控制器参数校验中@ModelAttribute及实体类参数校验实现原理-CSDN博客

讲解的Spring MVC方法参数处理解析器HandlerMethodArgumentResolver了解到,对于@RequestBody、@ModelAttribute、实体类、@RequestParam、@SessionAttribute、@MatrixVariable等都有对应的参数解析器,在解析器的resolveArgument()方法会对参数进行校验。而对于String、int等基础类型对应的HandlerMethodArgumentResolver解析器为RequestParamMethodArgumentResolver。RequestParamMethodArgumentResolver的核心代码如下:

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {

	private final boolean useDefaultResolution;

	/**
	 * 如果参数添加了@RequestParam注解且name有值,或者useDefaultResolution为true且是基本数据类型,则返回true
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestParam.class)) {
			if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
				// 添加了@RequestParam注解且name有值
				RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
				return (requestParam != null && StringUtils.hasText(requestParam.name()));
			}
			else {
				return true;
			}
		}
		else {
			if (parameter.hasParameterAnnotation(RequestPart.class)) {
				return false;
			}
			parameter = parameter.nestedIfOptional();
			if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
				return true;
			}
			// useDefaultResolution为true且是基本数据类型,则返回true
			else if (this.useDefaultResolution) {
				return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
			}
			else {
				return false;
			}
		}
	}

}

RequestParamMethodArgumentResolver也类似上一篇的ModelAttributeMethodProcessor,初始化的时候也创建了两个对象,一个用于处理@RequestParam注解,一个用于处理基本数据类型。而resolveArgument()是在父类AbstractNamedValueMethodArgumentResolver中实现。该resolveArgument()方法并没有参数校验的入口,所以针对基本数据类型校验并不是在Spring MVC中实现的。那它是如何实现的呢?这里先卖个关子。请继续往下看。

MethodValidationPostProcessor

 在SpringBoot的WebMvcAutoConfiguration自动配置类中自动装载ValidationAutoConfiguration类。

public class ValidationAutoConfiguration {

	/**
	 * 创建一个MethodValidationPostProcessor对象,类型为FilteredMethodValidationPostProcessor
	 */
	@Bean
	@ConditionalOnMissingBean
	public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
			@Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
		FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(
				excludeFilters.orderedStream());
		boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
		processor.setProxyTargetClass(proxyTargetClass);
		processor.setValidator(validator);
		return processor;
	}

}

添加了@ConditionalOnMissingBean,即当系统没有创建MethodValidationPostProcessor bean对象时,才会自动创建。在基本使用

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

的快速失败配置中,就是通过自定义返回一个MethodValidationPostProcessor bean对象

package com.jingai.validation.config;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

@Configuration
public class AppConfig {

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

    @Bean
    public MethodValidationPostProcessor validationPostProcessor() {
        MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
        methodValidationPostProcessor.setValidator(validator());
        return methodValidationPostProcessor;
    }

}

在这一篇

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

的自定义通用的校验章节中,跟大家分享了使用AOP封装List集合校验的示例,没印象的可以再看一下。Spring Validation对于基本数据类型的校验也是采用的AOP思想实现的。

在MethodValidationPostProcessor中,添加了@Validated注解的切点以及针对该切点的Advisor增强器。

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
		implements InitializingBean {

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

	@Nullable
	private Validator validator;


	/**
	 * 可以修改校验标识的注解,默认是@Validated
	 */
	public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
		Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
		this.validatedAnnotationType = validatedAnnotationType;
	}

	/**
	 * 设置校验器
	 */
	public void setValidator(Validator validator) {
		// Unwrap to the native Validator with forExecutables support
		if (validator instanceof LocalValidatorFactoryBean) {
			this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
		}
		else if (validator instanceof SpringValidatorAdapter) {
			this.validator = validator.unwrap(Validator.class);
		}
		else {
			this.validator = validator;
		}
	}

	/**
	 * 设置校验器工厂,用于生成一个校验器
	 */
	public void setValidatorFactory(ValidatorFactory validatorFactory) {
		this.validator = validatorFactory.getValidator();
	}


    /**
     * bean创建后初始化时调用
     */
	@Override
	public void afterPropertiesSet() {
		// 以validatedAnnotationType生成一个切点,其MethodMatcher为MethodMatcher.TRUE。即只要类中添加了validatedAnnotationType类型的注解即可
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		// 创建一个advisor,监听器为MethodValidationInterceptor。即只要类中添加了validatedAnnotationType类型的注解,
		// 在类中方法执行之前,会先执行MethodValidationInterceptor.invoke()方法
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}

	/**
	 * 返回MethodValidationInterceptor
	 */
	protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
		return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
	}

}

可以简单理解,只要在类中添加了@Validated注解,对应类就会被增强,对应方法在执行之前,都会先执行MethodValidationInterceptor的invoke()方法。

public class MethodValidationInterceptor implements MethodInterceptor {

	private final Validator validator;

	public MethodValidationInterceptor() {
		this(Validation.buildDefaultValidatorFactory());
	}

	public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
		this(validatorFactory.getValidator());
	}

	public MethodValidationInterceptor(Validator validator) {
		this.validator = validator;
	}


	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// 针对特定方法,不进行参数校验
		if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
			return invocation.proceed();
		}

		// 获取校验分组
		Class<?>[] groups = determineValidationGroups(invocation);

		// Standard Bean Validation 1.1 API
		ExecutableValidator execVal = this.validator.forExecutables();
		Method methodToValidate = invocation.getMethod();
		Set<ConstraintViolation<Object>> result;

		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");

		try {
			// 参数校验
			result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
		}
		catch (IllegalArgumentException ex) {
			// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
			// Let's try to find the bridged method on the implementation class...
			methodToValidate = BridgeMethodResolver.findBridgedMethod(
					ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
			result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
		}
		// 如果校验失败,则抛ConstraintViolationException异常
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

		// 执行业务方法
		Object returnValue = invocation.proceed();

		// 方法返回参数校验
		result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
		// 如果校验失败,则抛ConstraintViolationException异常
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

		return returnValue;
	}

	/**
	 * 判断对应的方法是否为框架中的FactoryBean相关类的方法,针对特定方法,不进行参数校验
	 */
	private boolean isFactoryBeanMetadataMethod(Method method) {
		Class<?> clazz = method.getDeclaringClass();

		// Call from interface-based proxy handle, allowing for an efficient check?
		if (clazz.isInterface()) {
			return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
					!method.getName().equals("getObject"));
		}

		// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
		Class<?> factoryBeanType = null;
		if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = SmartFactoryBean.class;
		}
		else if (FactoryBean.class.isAssignableFrom(clazz)) {
			factoryBeanType = FactoryBean.class;
		}
		return (factoryBeanType != null && !method.getName().equals("getObject") &&
				ClassUtils.hasMethod(factoryBeanType, method));
	}

	/**
	 * 获取校验分组
	 */
	protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
		// 获取方法添加的@Validated注解
		Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
		if (validatedAnn == null) {
			// 如果方法没有添加@Validated注解,查找方法所在类或父类、父接口类的是否有添加@Validated注解
			Object target = invocation.getThis();
			Assert.state(target != null, "Target must not be null");
			validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
		}
		// 如果添加了@Validated注解,返回注解的value值,即校验分组
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}

}

ValidatorImpl.validateParameters()

在MethodValidationInterceptor的invoke()方法中,执行ValidatorImpl.validateParameters()进行参数校验。

public class ValidatorImpl implements Validator, ExecutableValidator {

	private <T> Set<ConstraintViolation<T>> validateParameters(T object, Executable executable, Object[] parameterValues, Class<?>... groups) {
		// 有效性检查
		sanityCheckGroups( groups );

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

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

        // 校验组生成组和序列的顺序。如果groups为空,则添加Default.class组
		ValidationOrder validationOrder = determineGroupValidationOrder( groups );

		// 执行validateParametersInContext进行参数校验
		validateParametersInContext( validationContext, parameterValues, validationOrder );
		// 返回校验结果
		return validationContext.getFailingConstraints();
	}
	
	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;
			}
		}
	}
	
	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;
			}
		}
	}
	
}

2.1 在validateParameters()方法中调用validateParametersInContext()方法,该方法会遍历分组,调用validateParametersForGroup(),判断分组信息。不管是否是默认分组,最后都会调用validateParametersForSingleGroup()。

2.2 在validateParametersForSingleGroup()方法中,会遍历每个参数,调用validateMetaConstraints()方法。因为一个参数可能会添加多个约束,所以此处会遍历每个约束,调用validateMetaConstraint()进行参数校验。

接下去的逻辑同

Spring validation参数校验原理解析之Controller控制器参数校验中@RequestBody参数校验实现原理-CSDN博客

中ValidatorImpl.validateInContext()的4.1分析。

大体思路是获取对应约束的约束器,如果没有,先创建并执行ConstraintValidator的initialize()完成初始化,然后执行ConstraintValidator的isValid()方法进行校验,如果成功返回true,失败返回false。

如果校验失败,在MethodValidationPostProcessor.invoke()方法中,会抛ConstraintViolationException异常。

总结

以上为本次分享的Spring validation参数校验原理解析之基本类型参数校验实现原理,无论是Controller中的基本数据类型校验,还是Service层中的校验,都是采用同一套AOP代码实现的。

这里有一个疑问:

为何不在Spring MVC中对基本数据类型参数进行校验,而采用AOP思想拦截方法呢?本人觉得有以下两点可能:

1、基本数据类型校验使用的注解有一二十个,如果是在Spring MVC中直接判断,意味着不论代码中是否有添加校验注解,每个方法的入参都需要遍历一遍,影响性能;

2、在Servcie层中也需要用到方法参数校验,其只能通过AOP思想进行方法拦截处理。该种处理也适用于Controller层中的方法,所以采用相同的处理逻辑,提升代码的复用性和可维护性;

以上为本人的见解,如果你知道答案、有的想法或独到见解,欢迎在评论区一起交流探讨下吧。

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Validation 是一种基于注解的数据校验框架,可以对 Java 对象进行校验,常用于 Web 表单数据的校验。其核心原理是基于 Java 标准库中的 JSR-303 规范,通过注解标记 Java 类的字段,然后使用 Validator 接口对标记的字段进行校验Spring Validation校验流程大致如下: 1. 在 Controller 接收表单数据,将数据封装成 Java 对象。 2. 对 Java 对象进行校验,检测是否符合预定规则。 3. 如果校验失败,返回错误信息。 Spring Validation 的核心接口是 Validator,其定义如下: ```java public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); } ``` 其中,supports 方法用于判断该 Validator 是否支持校验指定的类型validate 方法用于对指定的对象进行校验Spring Validation 内部使用了一个 Errors 对象来记录校验错误信息,如下所示: ```java public interface Errors { void reject(String errorCode, Object[] errorArgs, String defaultMessage); void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage); boolean hasErrors(); } ``` 其中,reject 方法用于记录全局错误信息,rejectValue 方法用于记录字段错误信息,hasErrors 方法用于判断是否存在校验错误信息。 Spring Validation 是通过反射机制读取 Java 对象的注解信息来完成校验的。在执行校验时,Spring Validation 会将 Java 对象的每个字段和对应的约束注解信息一一对应,然后根据注解信息进行校验Spring Validation源码分析较为复杂,主要涉及到反射、注解、AOP 等技术,需要深入了解 Spring 框架的原理和机制。如果需要深入学习 Spring Validation源码,可以阅读 Spring 源码中的 Validation 相关模块。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值