Spring框架系列(14) - SpringMVC實現原理之DispatcherServlet處理請求的過程

pdai發表於2022-07-06

前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。@pdai

DispatcherServlet處理請求的過程?

一個請求發出,經過DispatcherServlet進行了什麼樣的處理,最後將內容返回的呢?

回顧整理處理流程

首先讓我們整體看一下Spring Web MVC 處理請求的流程:

核心架構的具體流程步驟如下:

  1. 首先使用者傳送請求——>DispatcherServlet,前端控制器收到請求後自己不進行處理,而是委託給其他的解析器進行
    處理,作為統一訪問點,進行全域性的流程控制;
  2. DispatcherServlet——>HandlerMapping, HandlerMapping 將會把請求對映為 HandlerExecutionChain 物件(包含一
    個Handler 處理器(頁面控制器)物件、多個HandlerInterceptor 攔截器)物件,通過這種策略模式,很容易新增新
    的對映策略;
  3. DispatcherServlet——>HandlerAdapter,HandlerAdapter 將會把處理器包裝為介面卡,從而支援多種型別的處理器,
    即介面卡設計模式的應用,從而很容易支援很多型別的處理器;
  4. HandlerAdapter——>處理器功能處理方法的呼叫,HandlerAdapter 將會根據適配的結果呼叫真正的處理器的功能處
    理方法,完成功能處理;並返回一個ModelAndView 物件(包含模型資料、邏輯檢視名);
  5. ModelAndView 的邏輯檢視名——> ViewResolver,ViewResolver 將把邏輯檢視名解析為具體的View,通過這種策
    略模式,很容易更換其他檢視技術;
  6. View——>渲染,View 會根據傳進來的Model 模型資料進行渲染,此處的Model 實際是一個Map 資料結構,因此
    很容易支援其他檢視技術;
  7. 返回控制權給DispatcherServlet,由DispatcherServlet 返回響應給使用者,到此一個流程結束。

doGet入口

我們以上個demo中這個GET請求為例,請求URL是http://localhost:8080/011_spring_framework_demo_springmvc_war_exploded/user

我們知道servlet處理get請求是doGet方法,所以我們去找DispatcherServlet類結構中的doGet方法。

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

  processRequest(request, response);
}

processRequest處理請求的方法如下:

/**
  * Process this request, publishing an event regardless of the outcome.
  * <p>The actual event handling is performed by the abstract
  * {@link #doService} template method.
  */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

  // 計算處理請求的時間
  long startTime = System.currentTimeMillis();
  Throwable failureCause = null;

  LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  LocaleContext localeContext = buildLocaleContext(request);

  RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

  // 初始化context
  initContextHolders(request, localeContext, requestAttributes);

  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 {
    // 重置context
    resetContextHolders(request, previousLocaleContext, previousAttributes);
    if (requestAttributes != null) {
      requestAttributes.requestCompleted();
    }
    logResult(request, response, failureCause, asyncManager);
    publishRequestHandledEvent(request, response, startTime, failureCause);
  }
}

本質上就是呼叫doService方法,由DispatchServlet類實現

/**
  * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
  * for the actual dispatching.
  */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  logRequest(request);

  // 儲存下請求之前的引數.
  Map<String, Object> attributesSnapshot = null;
  if (WebUtils.isIncludeRequest(request)) {
    attributesSnapshot = new HashMap<>();
    Enumeration<?> attrNames = request.getAttributeNames();
    while (attrNames.hasMoreElements()) {
      String attrName = (String) attrNames.nextElement();
      if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
        attributesSnapshot.put(attrName, request.getAttribute(attrName));
      }
    }
  }

  // 方便後續 handlers 和 view 要使用它們.
  request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

  if (this.flashMapManager != null) {
    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
      request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  }

  RequestPath previousRequestPath = null;
  if (this.parseRequestPath) {
    previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
    ServletRequestPathUtils.parseAndCache(request);
  }

  try {
    // 看這裡,終於將這個請求分發出去了
    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 (this.parseRequestPath) {
      ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
    }
  }
}

請求分發

doDispatch方法是真正處理請求的核心方法

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 {
      // 判斷是不是檔案上傳型別的request
      processedRequest = checkMultipart(request);
      multipartRequestParsed = (processedRequest != request);

      // 根據request獲取匹配的handler.
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }

      // 根據handler獲取匹配的handlerAdapter
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

      // 如果handler支援last-modified頭處理
      String method = request.getMethod();
      boolean isGet = HttpMethod.GET.matches(method);
      if (isGet || HttpMethod.HEAD.matches(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
          return;
        }
      }

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

      // 真正handle處理,並返回modelAndView
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

      // 通過檢視的prefix和postfix獲取完整的檢視名
      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);
    }

    // 處理handler處理的結果,顯然就是對ModelAndView 或者 出現的Excpetion處理
    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);
      }
    }
  }
}

對映和介面卡處理

對於真正的handle方法,我們看下其處理流程

/**
  * This implementation expects the handler to be an {@link HandlerMethod}.
  */
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {

  return handleInternal(request, response, (HandlerMethod) handler);
}

交給handleInternal方法處理,以RequestMappingHandlerAdapter這個HandlerAdapter中的處理方法為例

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

  ModelAndView mav;
  checkRequest(request);

  // Execute invokeHandlerMethod in synchronized block if required.
  if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
      Object mutex = WebUtils.getSessionMutex(session);
      synchronized (mutex) {
        mav = invokeHandlerMethod(request, response, handlerMethod);
      }
    }
    else {
      // No HttpSession available -> no mutex necessary
      mav = invokeHandlerMethod(request, response, handlerMethod);
    }
  }
  else {
    // No synchronization on session demanded at all...
    mav = invokeHandlerMethod(request, response, handlerMethod);
  }

  if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
      applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    }
    else {
      prepareResponse(response);
    }
  }

  return mav;
}

然後執行invokeHandlerMethod這個方法,用來對RequestMapping(usercontroller中的list方法)進行處理

/**
  * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
  * if view resolution is required.
  * @since 4.2
  * @see #createInvocableHandlerMethod(HandlerMethod)
  */
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

  ServletWebRequest webRequest = new ServletWebRequest(request, response);
  try {
    
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    // 重要:設定handler(controller#list)方法上的引數,返回值處理,繫結databinder等
    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
      invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
      invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    
    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    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();
      LogFormatUtils.traceDebug(logger, traceOn -> {
        String formatted = LogFormatUtils.formatValue(result, !traceOn);
        return "Resume with async result [" + formatted + "]";
      });
      invocableMethod = invocableMethod.wrapConcurrentResult(result);
    }

    // 執行controller中方法
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
      return null;
    }

    return getModelAndView(mavContainer, modelFactory, webRequest);
  }
  finally {
    webRequest.requestCompleted();
  }
}

invokeAndHandle交給UserController中具體執行list方法執行

後續invoke執行的方法,直接看整個請求流程的呼叫鏈即可

執行後獲得檢視和Model

檢視渲染

接下來繼續執行processDispatchResult方法,對檢視和model(如果有異常則對異常處理)進行處理(顯然就是渲染頁面了)

/**
  * Handle the result of handler selection and handler invocation, which is
  * either a ModelAndView or an Exception to be resolved to a ModelAndView.
  */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    @Nullable Exception exception) throws Exception {

  boolean errorView = false;

  // 如果處理過程有異常,則異常處理
  if (exception != null) {
    if (exception instanceof ModelAndViewDefiningException) {
      logger.debug("ModelAndViewDefiningException encountered", exception);
      mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    }
    else {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
    }
  }

  // 是否需要渲染檢視
  if (mv != null && !mv.wasCleared()) {
    render(mv, request, response); // 渲染檢視
    if (errorView) {
      WebUtils.clearErrorRequestAttributes(request);
    }
  }
  else {
    if (logger.isTraceEnabled()) {
      logger.trace("No view rendering, null ModelAndView returned.");
    }
  }

  if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    // Concurrent handling started during a forward
    return;
  }

  if (mappedHandler != null) {
    // Exception (if any) is already handled..
    mappedHandler.triggerAfterCompletion(request, response, null);
  }
}

接下來顯然就是渲染檢視了, spring在initStrategies方法中初始化的元件(LocaleResovler等)就派上用場了。

/**
  * Render the given ModelAndView.
  * <p>This is the last stage in handling a request. It may involve resolving the view by name.
  * @param mv the ModelAndView to render
  * @param request current HTTP servlet request
  * @param response current HTTP servlet response
  * @throws ServletException if view is missing or cannot be resolved
  * @throws Exception if there's a problem rendering the view
  */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
  // Determine locale for request and apply it to the response.
  Locale locale =
      (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
  response.setLocale(locale);

  View view;
  String viewName = mv.getViewName();
  if (viewName != null) {
    // We need to resolve the view name.
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    if (view == null) {
      throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
          "' in servlet with name '" + getServletName() + "'");
    }
  }
  else {
    // No need to lookup: the ModelAndView object contains the actual View object.
    view = mv.getView();
    if (view == null) {
      throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
          "View object in servlet with name '" + getServletName() + "'");
    }
  }

  // Delegate to the View object for rendering.
  if (logger.isTraceEnabled()) {
    logger.trace("Rendering view [" + view + "] ");
  }
  try {
    if (mv.getStatus() != null) {
      response.setStatus(mv.getStatus().value());
    }
    view.render(mv.getModelInternal(), request, response);
  }
  catch (Exception ex) {
    if (logger.isDebugEnabled()) {
      logger.debug("Error rendering view [" + view + "]", ex);
    }
    throw ex;
  }
}

後續就是通過viewResolver進行解析了,這裡就不再繼續看程式碼了,上述流程基本上夠幫助你構建相關的認知了。

最後無非是返回控制權給DispatcherServlet,由DispatcherServlet 返回響應給使用者。

最後的最後我們看下請求的日誌:

21:45:53.390 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - GET "/011_spring_framework_demo_springmvc_war_exploded/user", parameters={}
21:45:53.400 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to tech.pdai.springframework.springmvc.controller.UserController#list(HttpServletRequest, HttpServletResponse)
22:51:14.504 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.view.JstlView - View name 'userList', model {dateTime=Fri Apr 22 21:45:53 CST 2022, userList=[tech.pdai.springframework.springmvc.entity.User@7b8c8dc]}
22:51:14.550 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.view.JstlView - Forwarding to [/WEB-INF/views/userList.jsp]
22:51:44.395 [http-nio-8080-exec-6] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK

更多文章

首先, 從Spring框架的整體架構和組成對整體框架有個認知。

  • Spring基礎 - Spring和Spring框架組成
    • Spring是什麼?它是怎麼誕生的?有哪些主要的元件和核心功能呢? 本文通過這幾個問題幫助你構築Spring和Spring Framework的整體認知。

其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。

基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。

  • Spring基礎 - SpringMVC請求流程和案例
    • 前文我們介紹了Spring框架和Spring框架中最為重要的兩個技術點(IOC和AOP),那我們如何更好的構建上層的應用呢(比如web 應用),這便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技術基礎上,遵循上述Web MVC的規範推出的web開發框架,目的是為了簡化Java棧的web開發。 本文主要介紹SpringMVC的請求流程和基礎案例的編寫和執行。

Spring進階 - IoC,AOP以及SpringMVC的原始碼分析

  • Spring進階 - Spring IOC實現原理詳解之IOC體系結構設計
    • 在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計
  • Spring進階 - Spring IOC實現原理詳解之IOC初始化流程
    • 上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的
  • Spring進階 - Spring IOC實現原理詳解之Bean例項化(生命週期,迴圈依賴等)
    • 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個ConcurrentHashMap<String, Object>;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中例項化Bean物件呢,這是本文主要研究的內容?
  • Spring進階 - Spring AOP實現原理詳解之切面實現
    • 前文,我們分析了Spring IOC的初始化過程和Bean的生命週期等,而Spring AOP也是基於IOC的Bean載入來實現的。本文主要介紹Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor,為後續交給代理增強實現做準備的過程)。
  • Spring進階 - Spring AOP實現原理詳解之AOP代理
    • 上文我們介紹了Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor)。本文在此基礎上繼續介紹,代理(cglib代理和JDK代理)的實現過程。
  • Spring進階 - Spring AOP實現原理詳解之Cglib代理實現
    • 我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。
  • Spring進階 - Spring AOP實現原理詳解之JDK代理實現
    • 上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet初始化的過程
    • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第一篇:DispatcherServlet的初始化過程的原始碼解析。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet處理請求的過程
    • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。

相關文章