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

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参数校验系列》来到第四篇。由于对数据的变更更多都是采用post的方式同后台交互,所以Controller中的接口入参带@RequestBody的场景很多,我们也专门用了一篇来讲解对应参数校验的规则,不明白的可以看一下以下博文。

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

本文继续从源码的角度分析Spring validation针对其他场景参数的校验原理,让大家知其然知其所以然。本文基于Spring-5.3.10源码分析。

温馨提醒:

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

Spring MVC方法参数处理解析器HandlerMethodArgumentResolver

在开始分析Spring validation的源码之前,先来一起了解一下Spring MVC的方法参数处理解析器HandlerMethodArgumentResolver。该解析器用于自动向Controller类的接口方法参数注入值(自动为方法参数赋值)。HandlerMethodArgumentResolver是一个接口类,提供了两个接口方法。

package org.springframework.web.method.support;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public interface HandlerMethodArgumentResolver {

	/**
     * 判断当前的解析器能否解析parameter参数
     * @param parameter 要解析的方法参数。MethodParameter为Spring MVC中对方法参数的包装类
     * @return 如果支持该参数的解析则返回true,否则返回false
     */
	boolean supportsParameter(MethodParameter parameter);
    
	/**
     * 从request中解析出方法参数值,并返回
     * @param parameter 要解析的方法参数。
     * @param mavContainer 当前请求的ModelAndViewContainer,其中ModelAndViewContainer 提供了request中的model
     * @param webRequest 当前请求对象request
     * @param binderFactory 创建WebDataBinder实例的工厂,其中WebDataBinderFactory 提供了创建WebDataBinder实例的方法(当需要绑定数据和类型转换时使用)
     * @return 返回解析后的参数值,如果不能解析返回null
     * @throws Exception 如果解析参数值出错抛出异常
     */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

Spring MVC采用模板方法模式和策略模式,通过实现HandlerMethodArgumentResolver接口,针对@BodyRequest、@RequestParam、@SessionAttribute、@MatrixVariable、@ModelAttribute等分别定义了各自的解析器,在supportsParameter()方法中判断是否有对应的注解,如果有,则执行对应解析器的resolveArgument()方法。

上一篇中讲解的@RequestBody的解析器RequestResponseBodyMethodProcessor中的supportParameter()方法代码如下:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

    /**
     * 判断参数中是否有@RequestBody注解,有返回true
     */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}
}

开发者也可以通过自己实现HandlerMethodArgumentResolver接口来扩展Controller接口方法的入参类型。

这些HandlerMethodArgumentResolver是在RequestMappingHandlerAdapter bean初始化的时候加载到系统中的HandlerMethodArgumentResolverComposite对象。

/**
 * 实现了InitializingBean接口
 */
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

    /**
     * 当RequestMappingHandlerAdapter bean初始化时,调用该方法
     */
	@Override
	public void afterPropertiesSet() {
		// Do this first, it may add ResponseBody advice beans
		initControllerAdviceCache();

		if (this.argumentResolvers == null) {
            // 调用getDefaultArgumentResolvers(),添加系统默认的HandlerMethodArgumentResolver接口入参处理解析器
			List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            // 解析器全部放在在HandlerMethodArgumentResolverComposite对象的集合属性中,当有接口请求时,执行HandlerMethodArgumentResolverComposite对象的
			this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.initBinderArgumentResolvers == null) {
			List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
			this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
		}
		if (this.returnValueHandlers == null) {
			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
		}
	}

	/**
	 * 返回Spring MVC创建的方法参数处理解析器集合
	 */
	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());
		resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
		resolvers.add(new SessionAttributeMethodArgumentResolver());
		resolvers.add(new RequestAttributeMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
		if (KotlinDetector.isKotlinPresent()) {
			resolvers.add(new ContinuationHandlerMethodArgumentResolver());
		}

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver());
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}

}

在RequestMappingHandlerAdapter中保存HandlerMethodArgumentResolverComposite对象,其中HandlerMethodArgumentResolverComposite对象也是实现了HandlerMethodArgumentResolver接口,后续参数解析时,直接使用HandlerMethodArgumentResolverComposite的suppoertParameter()和resolveArgument()。由HandlerMethodArgumentResolverComposite统一管理HandlerMethodArgumentResolver。在Spring源码中很多地方采用了类似的设计思路。

package org.springframework.web.method.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

	private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
			new ConcurrentHashMap<>(256);


	/**
	 * Add the given {@link HandlerMethodArgumentResolver}.
	 */
	public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
		this.argumentResolvers.add(resolver);
		return this;
	}

	/**
	 * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
	 * @since 4.3
	 */
	public HandlerMethodArgumentResolverComposite addResolvers(
			@Nullable HandlerMethodArgumentResolver... resolvers) {

		if (resolvers != null) {
			Collections.addAll(this.argumentResolvers, resolvers);
		}
		return this;
	}

	/**
	 * Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
	 */
	public HandlerMethodArgumentResolverComposite addResolvers(
			@Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {

		if (resolvers != null) {
			this.argumentResolvers.addAll(resolvers);
		}
		return this;
	}

	/**
	 * Return a read-only list with the contained resolvers, or an empty list.
	 */
	public List<HandlerMethodArgumentResolver> getResolvers() {
		return Collections.unmodifiableList(this.argumentResolvers);
	}

	/**
	 * Clear the list of configured resolvers.
	 * @since 4.3
	 */
	public void clear() {
		this.argumentResolvers.clear();
	}


	/**
	 * 调用getArgumentResolver()遍历argumentResolvers集合,判断是否有能够解析该parameter的解析器,如果有,返回true;否则返回false
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return getArgumentResolver(parameter) != null;
	}

	/**
	 * 调用getArgumentResolver()遍历argumentResolvers集合,获取能够解析该parameter的解析器,执行该解析器的resolveArgument()方法
	 * @throws IllegalArgumentException 如果没有支持的解析器,抛异常
	 */
	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException("Unsupported parameter type [" +
					parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}

	/**
	 * 遍历argumentResolvers集合,判断是否有能够解析该parameter的解析器,如果有,
	 * 返回HandlerMethodArgumentResolver,并添加到缓存中;如果没有,返回null
	 */
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

}

添加@ModelAttribute注解或实体类参数处理解析器ModelAttributeMethodProcessor

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	protected final Log logger = LogFactory.getLog(getClass());

	private final boolean annotationNotRequired;


	public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
		this.annotationNotRequired = annotationNotRequired;
	}

	/**
	 * 如果parameter添加了@ModelAttribute注解或者annotationNotRequired为true且不是基础类型,则返回true;否则返回false
	 */
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}

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

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				else {
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				// 参数校验
				validateIfApplicable(binder, parameter);
				// 如果校验失败,返回BindException异常
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

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

2.1 在supportsParameter(MethodParameter parameter)方法中,如果是添加了@ModelAttribute注解,或者,annotationNotRequired为true且不是基础类型,则返回true;否则返回false。结合上面RequestMappingHandlerAdapter bean初始化的时候调用的getDefaultArgumentResolvers() 方法,可以发现,系统中添加了两个ServletModelAttributeMethodProcessor,而ServletModelAttributeMethodProcessor是继承ModelAttributeMethodProcessor。

说明:两个对象ServletModelAttributeMethodProcessor的annotationNotRequired一个为false,一个为true。为false的专门用于处理添加@ModelAttribute注解的参数(只要添加了@ModelAttribute,即使是基本数据类型,也会解析),为true的专门用于处理非基本数据类型的参数。

public abstract class BeanUtils {
	
	/**
	 * 如果是基本数据类型或者是基本数据类型数组,返回true;否则返回false
	 */
	public static boolean isSimpleProperty(Class<?> type) {
		Assert.notNull(type, "'type' must not be null");
		return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
	}

	/**
	 * 判断是否为基本数据类型
	 */
	public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) ||
				Temporal.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}
}

2.2 在resolveArgument()方法中,调用validateIfApplicable()进行约束性校验。这里和上一篇

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

中的validateIfApplicable()参数校验是一样的,此处就不在重复说明。校验结束后,如果校验失败,抛BindException异常。

结尾

以上为本次分享的从源码分析Spring validation中带@ModelAttribute注解或实体类的参数的校验原理。限于篇幅,本篇就先分享到这里,希望对你有所帮助。

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值