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注解或实体类的参数的校验原理。限于篇幅,本篇就先分享到这里,希望对你有所帮助。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。