前言
承接【深入浅出spring】Spring MVC 流程解析 -- HandlerAdapter中RequestMappingHandlerAdapter
一节,具体分析此adapter
调用handler
处理并获取ModelAndView
的过程
这里为何文章名是InvocableHandlerMethod
,不应该是RequestMappingHandlerAdapter
么?
因为RequestMappingHandlerAdapter
在内部对于每个请求,都会实例化一个ServletInvocableHandlerMethod
(InvocableHandlerMethod
的子类)进行处理,是整个处理的核心入口
概述
先贴一张处理流程图,纵向表示方法的依次调用(核心逻辑),横向表示同一方法中的先后处理流程。
从上面的流程图可以总结出几个核心处理逻辑:
- 输入参数处理,包括数据的转换
- 调用 handler method,获取返回参数。(采用反射的方式,调用
@RequestMapping
注解的方法) - 处理返回参数,并包装成ModelAndView
输入参数处理
概述
先看下InvocableHadlerMethod.getMethodArgumentValues
方法,即,获取方法的输入参数。
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 核心逻辑,获取能够处理入参的ArgumentResolver,然后解析参数
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getExecutable().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
从源码很容易得出,和之前的handlerMappings
、handlerAdapters
类似,都是从一个bean列表中遍历,找到一个能够处理的bean,然后调用bean的核心方法处理。这里的这个列表就是HandlerMethodArgumentResolver
,此接口的定义:
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
很清晰,通过supportsParameter
筛选符合条件的resolver
,然后调用resolver
的resolveArgument
解析前端参数
默认HandlerMethodArgumentResolver
入口是RequestMappingHandlerAdapter.afterPropertiesSet
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
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);
}
}
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
// 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());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
getDefaultArgumentResolvers
方法的逻辑主要分3块:
- 可以看到spring提供了非常多的
HandlerMethodArgumentResolver
,如果想要知道每个resolver
处理的入参场景可以查看resolver
的supportsParameter
方法,查看了几个,发现很多都是通过注解来区分的,比如@PathVariable, @RequestParam, @ModelAttribute
等。 - 用户可以自定义
resolver
,通过注解来指定处理的参数类型。然后通过getCustomArgumentResolvers
方法会注册到revolver
列表总 -
RequestParamMethodArgumentResolver
和ServletModelAttributeMethodProcessor
做为默认resolver,试图处理入参没有注解的情况,具体支持哪类入参类型,还需查看supportsParameter
方法
这里我们着重介绍下RequestParamMethodArgumentResolver
RequestParamMethodArgumentResolver
支持哪些参数处理
RequestParamMethodArgumentResolver.supportsParameter
方法源码
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
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;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
简单总结,就是处理标注了@RequestParam
的参数;在没有显示标注注解时,处理简单类型的参数(包括String,int,array,float,Date 等,不包括 Map, Collection, 自定义类等)。
解析参数
下面来看下核心方法resolveArgument
,RequestParamMethodArgumentResolver
没有重写此方法,故真正执行的是其父类AbstractNamedValueMethodArgumentResolver
的方法
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
其中核心逻辑是arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
,继续跟踪源码,会发现最后会调用的是TypeConverterDelegate.convertIfNecessary
方法,这个方法比较长,不贴了,总结下来核心逻辑如下:
-
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName)
,试图根据后端变量的类型和名词来查找是否有自定义的PropertyEditor
用于后续的字段转换 - 如果没有自定义的
PropertyEditor
,会试图用ConversionService
来转换参数,转换成功直接返回,转换失败则抛异常。ConversionService
会默认注册Converter
,具体初始化源码WebMvcAutoConfiguration.mvcConversionService
-
PropertyEditor
优先于ConversionService
,即如果自定义了PropertyEditor
,会优先使用自定义的PropertyEditor
来做参数转换。
关于参数转换相关的topic会另起一篇文章,重点解释,涉及DataBinder,ConversionService
的相关机制和原理,以及PropertyEditor, Formatter, Converter
的应用场景
ServletModelAttributeMethodProcessor
支持哪些参数处理
ServletModelAttributeMethodProcessor
没有重写supportsParameter
,故执行的是其父类ModelAttributeMethodProcessor
的方法,源码如下:
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
可以看到处理两类参数:
-
@ModelAttribute
注解标记的入参 - 没有注解标记(上面默认注册resolver的代码中有体现,catch-all 那段),且参数为非简单类型(正好和
RequestParamMethodArgumentResolver
互补,两者的并集就是全集了)。因此如果入参没有显式标注注解的话,默认处理的resolver要么是RequestParamMethodArgumentResolver
,要么是ServletModelAttributeMethodProcessor
解析参数
执行父类ModelAttributeMethodProcessor
的resolveArgument
方法
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();
}
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);
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;
}
核心逻辑总结如下:
- 变量名获取。这里有坑,如果不通过
@ModelAttribute
显式指定参数名,默认会根据参数名称来生成参数名。比如List<String> names
的参数名会变成stringList
,自定义类会用类名做为参数名(自定义类的参数,其实参数名无所谓,不会根据参数名从前端请求中获取数据) - 创建复合类型参数的实例,通过反射调用类的构造方法进行初始化,即上述代码中的
attribute = createAttribute(name, parameter, binderFactory, webRequest)
- 如果参数有自定义转换逻辑(DataBinder),进行数据转换。与
RequestParamMethodArgumentResolver
的数据转换逻辑一样,入口同样是DataBinder.convertIfNecessary
返回参数处理
原理和输入参数类似,遍历HandlerMethodReturnValueHandler
列表,根据返回参数类型,选择对应的HandlerMethodReturnValueHandler
的实现类,通过调用方法handleReturnValue
实现返回参数的处理。
HandlerMethodReturnValueHandler
接口定义
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
这里不具体展开了,对于返回类型是ModelAndView
的参数,对应的handler
是ModelAndViewMethodReturnValueHandler
,处理逻辑比较简单,把返回参数的model,view,status
变量赋值给ModelAndViewContainer
总结
此问主要介绍了RequestMappingHandlerAdapter
的处理流程,包括输入参数解析、方法调用、返回参数处理,其中输入参数解析转换知识点比较多,特别是数据转换这块,后续会通过具体的例子详细介绍。此文先以HandlerMethodArgumentResolver
和ServletModelAttributeMethodProcessor
为例,大致对处理流程有一个概念,理解这两个resolver
的应用场景,前者处理简单类型的入参(@RequestParam
),后者处理复合类型的入参(可以将前端参数聚合映射为后端的自定义类,即数据模型,对应@ModelAttribute
)。还有很多其他数据绑定相关的注解并未在本文介绍,比如@PathVariable, @CookieValue, @RequestHeader, @RequestBody
等。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。