【深刻淺出spring】Spring MVC 流程解析 -- InvocableHandlerMethod

前言

承接【深刻淺出spring】Spring MVC 流程解析 -- HandlerAdapterRequestMappingHandlerAdapter一節,具體分析此adapter調用handler處理並獲取ModelAndView的過程前端

這裏爲什麼文章名是InvocableHandlerMethod,不該該是RequestMappingHandlerAdapter麼?java

由於RequestMappingHandlerAdapter在內部對於每一個請求,都會實例化一個ServletInvocableHandlerMethodInvocableHandlerMethod的子類)進行處理,是整個處理的核心入口web

概述

先貼一張處理流程圖,縱向表示方法的依次調用(核心邏輯),橫向表示同一方法中的前後處理流程。spring

圖片描述

從上面的流程圖能夠總結出幾個核心處理邏輯:segmentfault

  • 輸入參數處理,包括數據的轉換
  • 調用 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;
    }

從源碼很容易得出,和以前的handlerMappingshandlerAdapters相似,都是從一個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,而後調用resolverresolveArgument解析前端參數app

默認HandlerMethodArgumentResolver

入口是RequestMappingHandlerAdapter.afterPropertiesSetide

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

  • 能夠看到spring提供了很是多的HandlerMethodArgumentResolver,若是想要知道每一個resolver處理的入參場景能夠查看resolversupportsParameter方法,查看了幾個,發現不少都是經過註解來區分的,好比@PathVariable, @RequestParam, @ModelAttribute等。
  • 用戶能夠自定義resolver,經過註解來指定處理的參數類型。而後經過getCustomArgumentResolvers方法會註冊到revolver列表總
  • RequestParamMethodArgumentResolverServletModelAttributeMethodProcessor作爲默認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, 自定義類等)。

解析參數

下面來看下核心方法resolveArgumentRequestParamMethodArgumentResolver沒有重寫此方法,故真正執行的是其父類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

解析參數

執行父類ModelAttributeMethodProcessorresolveArgument方法

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的參數,對應的handlerModelAndViewMethodReturnValueHandler,處理邏輯比較簡單,把返回參數的model,view,status變量賦值給ModelAndViewContainer

總結

此問主要介紹了RequestMappingHandlerAdapter的處理流程,包括輸入參數解析、方法調用、返回參數處理,其中輸入參數解析轉換知識點比較多,特別是數據轉換這塊,後續會經過具體的例子詳細介紹。此文先以HandlerMethodArgumentResolverServletModelAttributeMethodProcessor爲例,大體對處理流程有一個概念,理解這兩個resolver的應用場景,前者處理簡單類型的入參(@RequestParam),後者處理複合類型的入參(能夠將前端參數聚合映射爲後端的自定義類,即數據模型,對應@ModelAttribute)。還有不少其餘數據綁定相關的註解並未在本文介紹,好比@PathVariable, @CookieValue, @RequestHeader, @RequestBody等。

相關文章
相關標籤/搜索