SpringMVC請求對映handler原始碼解讀

是馮吉榮呀發表於2021-03-16

請求對映原始碼

首先看一張請求完整流轉圖(這裡感謝部落格園上這位大神的圖,部落格地址我忘記了):

前臺傳送給後臺的訪問請求是如何找到對應的控制器對映並執行後續的後臺操作呢,其核心為DispatcherServlet.java與HandlerMapper。在spring boot初始化的時候,將會載入所有的請求與對應的處理器對映為HandlerMapper元件。我們可以在springMVC的自動配置類中找到對應的Bean。

@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
    @Qualifier("mvcConversionService") FormattingConversionService conversionService,
    @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    // Must be @Primary for MvcUriComponentsBuilder to work
    return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
                                              resourceUrlProvider);
}

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}

請求將首先執行FrameworkServlet下的service方法根據request請求的method找到對應的do**方法。

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

    HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
    if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
        processRequest(request, response);
    }
    else {
        //父類根據method引數執行doGet,doPost,doDelete等
        super.service(request, response);
    }
}

而這些do**其都會進入核心方法,以doGet為例。

@Overrideprotected 
final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //核心方法
    processRequest(request, response);
}
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    try {
    //進入此核心方法
    doService(request, response);
}
catch (ServletException | IOException ex) {
    failureCause = ex;
    throw ex;
}
catch (Throwable ex) {
    failureCause = ex;
    throw new NestedServletException("Request processing failed", ex);
}

finally {
    resetContextHolders(request, previousLocaleContext, previousAttributes);
    if (requestAttributes != null) {
        requestAttributes.requestCompleted();
    }
    logResult(request, response, failureCause, asyncManager);
    publishRequestHandledEvent(request, response, startTime, failureCause);
}

processRequest()方法中重點在doService(request, response);,而其核心處理邏輯位於DispatchServletl類重寫的方法,如下。

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  ····

      try {
          //這裡為實際分發控制器的邏輯,其內部是找到對應的handlerMapper
          doDispatch(request, response);
      }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
            if (requestPath != null) {
                ServletRequestPathUtils.clearParsedRequestPath(request);
            }
        }
}

接下來看分發處理邏輯方法,其中重要的方法都使用了原生的註釋。接下來分別分析核心原始碼。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

首先是分析getHandler(),找到對應的處理器對映邏輯。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

我們將斷點標記在getHandler方法上時,可以清除看到handlerMappings,如圖。

這裡,使用者請求與處理器的對映關係都在RequestMapperHandlerMapping中,而歡迎頁處理請求則在WelcomePageHanderMapping中進行對映。

以下為RequestMapperHandlerMapping中對映部分截圖,可以看到使用者的所有請求對映這裡面都有:

getHandler()後的方法是通過比較request請求中method與HandlerMapper中相同url下的method,再進行唯一性校驗,不通過異常,通過找到唯一的handler。

後續,通過handler找到處理的設配器,通過介面卡得到一個ModelAndView物件,這個物件就是最後返回給前端頁面的物件。

至此,一個請求完整對映到返回前端結束。

說明:這是實現了FramworkServlet的doService方法,FramworkServlet繼承自HttpServlet,並且重寫了父類中的doGet(),doPost(),doPut(),doDelete 等方法,在這些重寫的方法裡都呼叫了 processRquest() 方法做請求處理,進入processRquest()可以看到裡面呼叫了FramworkServlet中定義的doService() 方法。

相關文章