承接【深刻淺出spring】Spring MVC 流程解析 -- HandlerAdapter中RequestMappingHandlerAdapter
一節,具體分析此adapter
調用handler
處理並獲取ModelAndView
的過程前端
這裏爲什麼文章名是InvocableHandlerMethod
,不該該是RequestMappingHandlerAdapter
麼?java
由於RequestMappingHandlerAdapter
在內部對於每一個請求,都會實例化一個ServletInvocableHandlerMethod
(InvocableHandlerMethod
的子類)進行處理,是整個處理的核心入口web
先貼一張處理流程圖,縱向表示方法的依次調用(核心邏輯),橫向表示同一方法中的前後處理流程。spring
從上面的流程圖能夠總結出幾個核心處理邏輯:segmentfault
@RequestMapping
註解的方法)先看下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
,此接口的定義:mvc
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
解析前端參數app
HandlerMethodArgumentResolver
入口是RequestMappingHandlerAdapter.afterPropertiesSet
ide
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塊:ui
HandlerMethodArgumentResolver
,若是想要知道每一個resolver
處理的入參場景能夠查看resolver
的supportsParameter
方法,查看了幾個,發現不少都是經過註解來區分的,好比@PathVariable, @RequestParam, @ModelAttribute
等。resolver
,經過註解來指定處理的參數類型。而後經過getCustomArgumentResolvers
方法會註冊到revolver
列表總RequestParamMethodArgumentResolver
和ServletModelAttributeMethodProcessor
作爲默認resolver,試圖處理入參沒有註解的狀況,具體支持哪類入參類型,還需查看supportsParameter
方法這裏咱們着重介紹下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
沒有重寫supportsParameter
,故執行的是其父類ModelAttributeMethodProcessor
的方法,源碼以下:
public boolean supportsParameter(MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); }
能夠看處處理兩類參數:
@ModelAttribute
註解標記的入參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)
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
等。