Spring MVC之RequestMappingHandlerAdapter詳解

       RequestMappingHandlerAdapter實現了HandlerAdapter接口,顧名思義,表示handler的adapter,這裏的handler指的是Spring處理具體請求的某個Controller的方法,也就是說HandlerAdapter指的是將當前請求適配到某個Handler的處理器。RequestMappingHandlerAdapter是HandlerAdapter的一個具體實現,主要用於將某個請求適配給@RequestMapping類型的Handler處理。java

       以下是HandlerMapping接口的聲明:web

public interface HandlerAdapter {
    // 用於判斷當前HandlerAdapter是否可以處理當前請求
	boolean supports(Object handler);
    
	// 若是當前HandlerAdapter可以用於適配當前請求,那麼就會處理當前請求中
    // 諸如參數和返回值等信息,以便可以直接委託給具體的Handler處理
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, 
        Object handler) throws Exception;

    // 獲取當前請求的最後更改時間,主要用於供給瀏覽器判斷當前請求是否修改過,
    // 從而判斷是否能夠直接使用以前緩存的結果
	long getLastModified(HttpServletRequest request, Object handler);
}

1. supports()

       HandlerAdapter.supports()方法的主要做用在於判斷當前的HandlerAdapter是否可以支持當前的handler的適配。這裏的handler指的是某個Controller的方法,其是由HandlerExecutionChain HandlerMapping.getHandler(HttpServletRequest)方法獲取到的。從這裏能夠看出,HandlerMapping的做用主要是根據request請求獲取可以處理當前request的handler,而HandlerAdapter的做用在於將request中的各個屬性,如request param適配爲handler可以處理的形式。數組

       關於HandlerAdapter.supports()方法,有這個方法的主要緣由是,HandlerMapping是能夠有多種實現的,Spring會遍歷這些具體的實現類,判斷哪個可以根據當前request產生一個handler,於是對於HandlerAdapter而言,其是不知道當前獲取到的handler具體是什麼形式的,不一樣的HandlerMapping產生的handler形式是不同的,好比RequestMappingHandlerMapping產生的handler則是封裝在HandlerMethod對象中的,於是這裏HandlerAdapter須要一個方法可以快速過濾掉當前產生的handler是否爲其可以進行適配的,這個方法就是HandlerAdapter.supports()方法。以下是該方法的實現:瀏覽器

// AbstractHandlerMethodAdapter
@Override
public final boolean supports(Object handler) {
    // 判斷當前handler是否爲HandlerMethod類型,而且判斷supportsInternal()方法返回值是否爲true,
    // 這裏supportsInternal()方法是提供給子類實現的一個方法,對於RequestMappingHandlerAdapter
    // 而言,其返回值始終是true,由於其只須要處理的handler是HandlerMethod類型的便可
    return (handler instanceof HandlerMethod 
            && supportsInternal((HandlerMethod) handler));
}
// RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    // 這裏RequestMappingHandlerAdapter只是對supportsInternal()返回true,由於其只須要
    // 處理的handler類型是HandlerMethod類型便可
    return true;
}

2. handle()

       在supports()方法判斷了所處理的handler是HandlerMethod類型以後,RequestMappingHandlerAdapter就會調用handle()方法處理當前請求。該方法的主要做用在於有五點:緩存

  • 獲取當前Spring容器中在方法上配置的標註了@ModelAttribute可是沒標註@RequestMapping註解的方法,在真正調用具體的handler以前會將這些方法依次進行調用;
  • 獲取當前Spring容器中標註了@InitBinder註解的方法,調用這些方法以對一些用戶自定義的參數進行轉換而且綁定;
  • 根據當前handler的方法參數標註的註解類型,如@RequestParam@ModelAttribute等,獲取其對應的ArgumentResolver,以將request中的參數轉換爲當前方法中對應註解的類型;
  • 配合轉換而來的參數,經過反射調用具體的handler方法;
  • 經過ReturnValueHandler對返回值進行適配,好比ModelAndView類型的返回值就由ModelAndViewMethodReturnValueHandler處理,最終將全部的處理結果都統一封裝爲一個ModelAndView類型的返回值,這也是RequestMappingHandlerAdapter.handle()方法的返回值類型。

       這裏咱們首先看看RequestMappingHandlerAdapter.handle()方法的實現源碼:session

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // 判斷當前是否須要支持在同一個session中只能線性地處理請求
    if (this.synchronizeOnSession) {
        // 獲取當前請求的session對象
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 爲當前session生成一個惟一的能夠用於鎖定的key
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 對HandlerMethod進行參數等的適配處理,並調用目標handler
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            // 若是當前不存在session,則直接對HandlerMethod進行適配
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    } else {
        // 若是當前不須要對session進行同步處理,則直接對HandlerMethod進行適配
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // 判斷當前請求頭中是否包含Cache-Control請求頭,若是不包含,則對當前response進行處理,
    // 爲其設置過時時間
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        // 若是當前SessionAttribute中存在配置的attributes,則爲其設置過時時間。
        // 這裏SessionAttribute主要是經過@SessionAttribute註解生成的
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        } else {
            // 若是當前不存在SessionAttributes,則判斷當前是否存在Cache-Control設置,
            // 若是存在,則按照該設置進行response處理,若是不存在,則設置response中的
            // Cache的過時時間爲-1,即當即失效
            prepareResponse(response);
        }
    }

    return mav;
}

       上述代碼主要作了兩部分處理:①判斷當前是否對session進行同步處理,若是須要,則對其調用進行加鎖,不須要則直接調用;②判斷請求頭中是否包含Cache-Control請求頭,若是不包含,則設置其Cache當即失效。能夠看到,對於HandlerMethod的具體處理是在invokeHandlerMethod()方法中進行的,以下是該方法的具體實現:併發

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 獲取容器中全局配置的InitBinder和當前HandlerMethod所對應的Controller中
        // 配置的InitBinder,用於進行參數的綁定
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 獲取容器中全局配置的ModelAttribute和當前當前HandlerMethod所對應的Controller
        // 中配置的ModelAttribute,這些配置的方法將會在目標方法調用以前進行調用
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 將handlerMethod封裝爲一個ServletInvocableHandlerMethod對象,
        // 該對象用於對當前request的總體調用流程進行了封裝
        ServletInvocableHandlerMethod invocableMethod =
            createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 設置當前容器中配置的全部ArgumentResolver
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 設置當前容器中配置的全部ReturnValueHandler
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 將前面建立的WebDataBinderFactory設置到ServletInvocableHandlerMethod中
        invocableMethod.setDataBinderFactory(binderFactory);
        // 設置ParameterNameDiscoverer,該對象將按照必定的規則獲取當前參數的名稱
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 這裏initModel()方法主要做用是調用前面獲取到的@ModelAttribute標註的方法,
        // 從而達到@ModelAttribute標註的方法可以在目標Handler調用以前調用的目的
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 獲取當前的AsyncWebRequest,這裏AsyncWebRequest的主要做用是用於判斷目標
        // handler的返回值是否爲WebAsyncTask或DefferredResult,若是是這兩種中的一種,
        // 則說明當前請求的處理應該是異步的。所謂的異步,指的是當前請求會將Controller中
        // 封裝的業務邏輯放到一個線程池中進行調用,待該調用有返回結果以後再返回到response中。
        // 這種處理的優勢在於用於請求分發的線程可以解放出來,從而處理更多的請求,只有待目標任務
        // 完成以後纔會回來將該異步任務的結果返回。
        AsyncWebRequest asyncWebRequest = WebAsyncUtils
            .createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // 封裝異步任務的線程池,request和interceptors到WebAsyncManager中
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        // 這裏就是用於判斷當前請求是否有異步任務結果的,若是存在,則對異步任務結果進行封裝
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) 
                asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            if (logger.isDebugEnabled()) {
                logger.debug("Found concurrent result value [" + result + "]");
            }
            // 封裝異步任務的處理結果,雖然封裝的是一個HandlerMethod,但只是Spring簡單的封裝
            // 的一個Callable對象,該對象中直接將調用結果返回了。這樣封裝的目的在於可以統一的
            // 進行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的調用
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        // 對請求參數進行處理,調用目標HandlerMethod,而且將返回值封裝爲一個ModelAndView對象
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // 對封裝的ModelAndView進行處理,主要是判斷當前請求是否進行了重定向,若是進行了重定向,
        // 還會判斷是否須要將FlashAttributes封裝到新的請求中
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        // 調用request destruction callbacks和對SessionAttributes進行處理
        webRequest.requestCompleted();
    }
}

       上述代碼是RequestMappingHandlerAdapter處理請求的主要流程,其主要包含四個部分:①獲取當前容器中使用@InitBinder註解註冊的屬性轉換器;②獲取當前容器中使用@ModelAttribute標註但沒有使用@RequestMapping標註的方法,而且在調用目標方法以前調用這些方法;③判斷目標handler返回值是否使用了WebAsyncTask或DefferredResult封裝,若是封裝了,則按照異步任務的方式進行執行;④處理請求參數,調用目標方法和處理返回值。這裏咱們首先看RequestMappingHandlerAdapter是如何處理標註@InitBinder的方法的,以下是getDataBinderFactory()方法的源碼:app

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) 
        throws Exception {
    // 判斷當前緩存中是否緩存了當前bean所須要裝配的InitBinder方法,若是存在,則直接從緩存中取,
    // 若是不存在,則在當前bean中進行掃描獲取
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        // 在當前bean中查找全部標註了@InitBinder註解的方法,這裏INIT_BINDER_METHODS就是一個
        // 選擇器,表示只獲取使用@InitBinder標註的方法
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    
    // 這裏initBinderAdviceCache是在RequestMappingHandlerAdapter初始化時同步初始化的,
    // 其內包含的方法有以下兩個特色:①當前方法所在類使用@ControllerAdvice進行標註了;
    // ②當前方法使用@InitBinder進行了標註。也就是說其內保存的方法能夠理解爲是全局類型
    // 的參數綁定方法
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
    this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
        // 這裏判斷的是當前配置的全局類型的InitBinder是否可以應用於當前bean,
        // 判斷的方式主要在@ControllerAdvice註解中進行了聲明,包括經過包名,類所在的包,
        // 接口或者註解的形式限定的範圍
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    });
    
    // 這裏是將當前HandlerMethod所在bean中的InitBinder添加到須要執行的initBinderMethods中。
    // 這裏從添加的順序能夠看出,全局類型的InitBinder會在當前bean中的InitBinder以前執行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    
    // 將須要執行的InitBinder封裝到InitBinderDataBinderFactory中
    return createDataBinderFactory(initBinderMethods);
}

       這裏獲取InitBinder的方式主要有兩種,一種是獲取全局配置的InitBinder,全局類型的InitBinder須要聲明的類上使用@ControllerAdvice進行標註,而且聲明方法上使用@InitBinder進行標註;另外一種則是獲取當前handler所在類中的使用@InitBinder註解標註的方法。這兩種InitBinder都會執行,只不過全局類型的InitBinder會先於局部類型的InitBinder執行。關於使用@InitBinder標註的方法的執行時間點,須要說明的是,由於其與參數綁定有關,於是其只會在參數綁定時纔會執行。異步

       這裏咱們繼續看RequestMappingHandlerAdapter是如何獲取@ModelAttribute標註的方法而且執行的,以下是getModelFactory()方法的源碼:async

private ModelFactory getModelFactory(HandlerMethod handlerMethod, 
        WebDataBinderFactory binderFactory) {
    // 這裏SessionAttributeHandler的做用是聲明幾個屬性,使其可以在多個請求之間共享,
    // 而且其可以保證當前request返回的model中始終保有這些屬性
    SessionAttributesHandler sessionAttrHandler = 
        getSessionAttributesHandler(handlerMethod);
    
    // 判斷緩存中是否保存有當前handler執行以前所須要執行的標註了@ModelAttribute的方法
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        // 若是緩存中沒有相關屬性,那麼就在當前bean中查找全部使用@ModelAttribute標註,可是
        // 沒有使用@RequestMapping標註的方法,並將這些方法緩存起來
        methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }
    
    // 獲取全局的使用@ModelAttribute標註,可是沒有使用@RequestMapping標註的方法,
    // 這裏全局類型的方法的聲明方式須要注意的是,其所在的bean必須使用@ControllerAdvice進行標註
    List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
    this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
        // 判斷@ControllerAdvice中指定的做用的bean範圍與當前bean是否匹配,匹配了纔會對其應用
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
        }
    });
    
    // 將當前方法中使用@ModelAttribute標註的方法添加到須要執行的attrMethods中。從這裏的添加順序
    // 能夠看出,全局類型的方法將會先於局部類型的方法執行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }
    
    // 將須要執行的方法等數據封裝爲ModelFactory對象
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

       上述getModelFactory()方法主要工做仍是獲取當前須要先於目標handler執行的方法,而且獲取的方式與前面的InitBinder很是的類似,這裏就再也不贅述。關於這裏獲取的方法,其具體的執行過程其實是在後面的ModelFactory.initModel()方法中進行。這裏咱們直接閱讀該方法的源碼:

public void initModel(NativeWebRequest request, ModelAndViewContainer container,
       HandlerMethod handlerMethod) throws Exception {

    // 在當前request中獲取使用@SessionAttribute註解聲明的參數
    Map<String, ?> sessionAttributes = 
        this.sessionAttributesHandler.retrieveAttributes(request);
    // 將@SessionAttribute聲明的參數封裝到ModelAndViewContainer中
    container.mergeAttributes(sessionAttributes);
    // 調用前面獲取的使用@ModelAttribute標註的方法
    invokeModelAttributeMethods(request, container);

    // 這裏首先獲取目標handler執行所需的參數中與@SessionAttribute同名或同類型的參數,
    // 也就是handler想要直接從@SessionAttribute中聲明的參數中獲取的參數。而後對這些參數
    // 進行遍歷,首先判斷request中是否包含該屬性,若是不包含,則從以前的SessionAttribute緩存
    // 中獲取,若是兩個都沒有,則直接拋出異常
    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if (value == null) {
                throw new HttpSessionRequiredException("Expected session attribute '" 
                    + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}

       這裏initModel()方法主要作了兩件事:①保證@SessionAttribute聲明的參數的存在;②調用使用@ModelAttribute標註的方法。咱們直接閱讀invokeModelAttributeMethods()方法的源碼:

private void invokeModelAttributeMethods(NativeWebRequest request, 
       ModelAndViewContainer container) throws Exception {

    while (!this.modelMethods.isEmpty()) {
        // 這裏getNextModelMethod()方法始終會獲取modelMethods中的第0號爲的方法,
        // 後續該方法執行完了以後則會將該方法從modelMethods移除掉,於是這裏while
        // 循環只須要判斷modelMethods是否爲空便可
        InvocableHandlerMethod modelMethod = 
            getNextModelMethod(container).getHandlerMethod();
        // 獲取當前方法中標註的ModelAttribute屬性,而後判斷當前request中是否有與該屬性中name字段
        // 標註的值相同的屬性,若是存在,而且當前ModelAttribute設置了不對該屬性進行綁定,那麼
        // 就直接略過當前方法的執行
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }

        // 經過ArgumentResolver對方法參數進行處理,而且調用目標方法
        Object returnValue = modelMethod.invokeForRequest(request, container);
        
        // 若是當前方法的返回值不爲空,則判斷當前@ModelAttribute是否設置了須要綁定返回值,
        // 若是設置了,則將返回值綁定到請求中,後續handler能夠直接使用該參數
        if (!modelMethod.isVoid()){
            String returnValueName = getNameForReturnValue(returnValue, 
                modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }
            
            // 若是request中不包含該參數,則將該返回值添加到ModelAndViewContainer中,
            // 供handler使用
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}

       這裏調用使用@ModelAttribute標註的方法的方式比較簡單,主要須要注意的是,對於調用結果,若是當前request中沒有同名的參數,則會將調用結果添加到ModelAndViewContainer中,以供給後續handler使用。

       在調用完使用上述方法以後,Spring會判斷當前handler的返回值是否爲WebAsyncTask或DefferredResult類型,若是是這兩種類型的一種,那麼就會將這些任務放入一個線程池中進行異步調用,而當前線程則能夠繼續進行請求的分發。這裏這種設計的目的是,默認狀況下Spring處理請求都是同步的,也就是說進行請求分發的線程是會調用用戶所聲明的handler方法的,那麼若是用戶聲明的handler執行時間較長,就可能致使Spring用於請求處理的線程都耗在了處理這些業務代碼上,也就致使後續的請求必須等待,這在高併發的場景中是不能被容許的,於是這裏Spring提供了一種異步任務處理的方式,也就是進行請求分發的線程只須要將用戶的業務任務放到線程池中執行便可,其自身能夠繼續進行其餘的請求的分發。若是線程池中的任務處理完成,其會通知Spring將處理結果返回給調用方。關於異步任務的處理流程,咱們後面會使用專門的章節進行講解,這裏只是簡單的講解其主要功能。

       在進行了相關前置方法調用和異步任務的判斷以後,RequestMappingHandlerAdapter就會開始調用目標handler了。調用過程在ServletInvocableHandlerMethod.invokeAndHandle()方法中,以下是該方法的源碼:

public void invokeAndHandle(ServletWebRequest webRequest, 
       ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 對目標handler的參數進行處理,而且調用目標handler
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 設置相關的返回狀態
    setResponseStatus(webRequest);

    // 若是請求處理完成,則設置requestHandled屬性
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null 
            || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        // 若是請求失敗,可是有錯誤緣由,那麼也會設置requestHandled屬性
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 遍歷當前容器中全部ReturnValueHandler,判斷哪一種handler支持當前返回值的處理,
        // 若是支持,則使用該handler處理該返回值
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", 
                returnValue), ex);
        }
        throw ex;
    }
}

       對於handler的調用過程,這裏主要分爲三個步驟:①處理請求參數進行處理,將request中的參數封裝爲當前handler的參數的形式;②經過反射調用當前handler;③對方法的返回值進行處理,以將其封裝爲一個ModleAndView對象。這裏第一步和第二步封裝在了invokeForRequest()方法中,咱們首先看該方法的源碼:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable 
        ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 將request中的參數轉換爲當前handler的參數形式
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), 
            getBeanType()) + "' with arguments " + Arrays.toString(args));
    }
    // 這裏doInvoke()方法主要是結合處理後的參數,使用反射對目標方法進行調用
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), 
            getBeanType()) + "] returned [" + returnValue + "]");
    }
    return returnValue;
}

// 本方法主要是經過當前容器中配置的ArgumentResolver對request中的參數進行轉化,
// 將其處理爲目標handler的參數的形式
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable 
       ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 獲取當前handler所聲明的全部參數,主要包括參數名,參數類型,參數位置,所標註的註解等等屬性
    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);
        // providedArgs是調用方提供的參數,這裏主要是判斷這些參數中是否有當前類型
        // 或其子類型的參數,若是有,則直接使用調用方提供的參數,對於請求處理而言,默認狀況下,
        // 調用方提供的參數都是長度爲0的數組
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        
        // 若是在調用方提供的參數中不能找到當前類型的參數值,則遍歷Spring容器中全部的
        // ArgumentResolver,判斷哪一種類型的Resolver支持對當前參數的解析,這裏的判斷
        // 方式比較簡單,好比RequestParamMethodArgumentResolver就是判斷當前參數
        // 是否使用@RequestParam註解進行了標註
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                // 若是可以找到對當前參數進行處理的ArgumentResolver,則調用其
                // resolveArgument()方法從request中獲取對應的參數值,而且進行轉換
                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;
}

       關於handler的調用,能夠看到,這裏的實現也是比較簡單的,首先是遍歷全部的參數,而且查找哪一種ArgumentResolver可以處理當前參數,找到了則按照具體的Resolver定義的方式進行處理便可。在全部的參數處理完成以後,RequestMappingHandlerAdapter就會使用反射調用目標handler。

       對於返回值的處理,其形式與對參數的處理很是類似,都是對ReturnValueHandler進行遍歷,判斷哪一種Handler可以支持當前返回值的處理,若是找到了,則按照其規則進行處理便可。以下是該過程的主要流程代碼:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
       ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 獲取可以處理當前返回值的Handler,好比若是返回值是ModelAndView類型,那麼這裏的handler就是
    // ModelAndViewMethodReturnValueHandler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " 
            + returnType.getParameterType().getName());
    }
    
    // 經過獲取到的handler處理返回值,並將其封裝到ModelAndViewContainer中
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

// 本方法的主要做用是獲取可以處理當前返回值的ReturnValueHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, 
        MethodParameter returnType) {
    // 判斷返回值是否爲異步類型的返回值,即WebAsyncTask或DefferredResult
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    
    // 對全部的ReturnValueHandler進行遍歷,判斷其是否支持當前返回值的處理。這裏若是當前返回值
    // 是異步類型的返回值,還會判斷當前ReturnValueHandler是否爲
    // AsyncHandlerMethodReturnValueHandler類型,若是不是,則會繼續查找
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        
        // 判斷是否支持返回值處理的主要位置,好比ModelAndViewMethodReturnValueHandler就會
        // 判斷返回值是否爲ModelAndView類型,若是是,則表示其是當前ReturnValuleHandler所支持的類型
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

3. 小結

       本文首先講解了RequestMappingHandlerMapping所作的工做與RequestMappingHandlerAdapter的區別,而後講解RequestMappingHandlerAdapter是如何判斷當前的handler是否爲其所支持的類型的,最後詳細講解了其是若是將request適配爲目標handler可以調用的形式的。總的來說,RequestMappingHandlerAdapter的主要做用就是調用RequestMappingHandlerMapping所獲取到的handler,而後將返回值封裝爲一個ModelAndView對象,該對象中保存了所要渲染的視圖名稱和渲染視圖時所須要的參數值,而具體的渲染過程則是經過View對象進行的。

相關文章
相關標籤/搜索