Spring MVC 底層源碼解讀

實例流程圖

在這裏插入圖片描述

組成

  • DispatcherServlet:HTTP請求處理程序/控制器的中央分配器,Spring MVC 項目的入口。

  • HandlerMapping:處理器映射

    根據 request 找到請求對應的 HandlerExecutionChain

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
    
  • HandlerExecutionChain:處理器執行鏈

    • 包含了對 Controller 進行包裝的處理器 handler(一般是 HandlerMethod 對象)

      • HandlerMethod:封裝由方法和bean組成的處理程序方法的信息。提供對方法參數、方法返回值、方法註釋等的方便訪問。其中的方法在後續使用 InvocableHandlerMethod 的 doInvoke 方法進行反射的時候會被調用到。
    • 包含了攔截器數組和攔截器列表,配合攔截器的 preHandle / postHandle / afterCompletion 可以對 handler 進行前置處理、後置處理、資源清理等增強操作。

      • preHandle

        調用時間:Controller方法處理之前

        執行順序:鏈式Intercepter情況下,Intercepter按照聲明的順序一個接一個執行

        若返回false,則中斷執行,注意:不會進入afterCompletion

      • postHandle

        調用前提:preHandle返回true

        調用時間:Controller方法處理完之後,DispatcherServlet進行視圖的渲染之前,也就是說在這個方法中你可以對ModelAndView進行操作

        執行順序:鏈式Intercepter情況下,Intercepter按照聲明的順序倒着執行

        備註:postHandle雖然post打頭,但post、get方法都能處理

      • afterCompletion

        調用前提:preHandle返回true

        調用時間:DispatcherServlet進行視圖的渲染之後

  • HandlerAdapter:處理器適配器

    • 其中最重要的 handle 方法可以返回一個 ModelAndView 對象。
    • supports 方法可以判斷是否支持傳入的處理器類型,如 HandlerMethod 等。
  • ModelAndView:這個類只是同時持有模型和視圖,使控制器能夠在一個返回值中同時返回模型和視圖。

  • ViewResolver:視圖解析器,其resolveViewName 方法會根據 viewName 解析出相應的 View 類型,例如是 ThymeleafView 或 JSP View.

新建項目

本文目的在於通過一個簡單的 Spring Boot 案例來解讀 Spring MVC 底層源碼。

  • 在 IntelliJ IDEA 中基於 Spring Boot + Spring Web + Thymeleaf+ Lombok 新建一個項目。

  • model 包下創建Person

    @Data
    @AllArgsConstructor
    public class Person {
    
        private String name;
    
        private Integer age;
    }
    
  • controller 包下創建PersonController

    @Controller
    @RequestMapping("persons")
    public class PersonController {
    
        @GetMapping("")
        public ModelAndView getPersons() {
            List<Person> persons = new ArrayList<>();
            persons.add(new Person("Jake", 27));
            persons.add(new Person("Heather", 27));
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("persons", persons);
            modelAndView.setViewName("person");
            return modelAndView;
        }
    
        @GetMapping("json")
        @ResponseBody
        public List<Person> getPersonsJson() {
            List<Person> persons = new ArrayList<>();
            persons.add(new Person("Jake", 27));
            persons.add(new Person("Heather", 27));
            return persons;
        }
    }
    
  • resources/templates 路徑下創建person.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>人員</title>
    </head>
    <body>
        <table>
            <thead>
                <tr>
                    <th>姓名</th>
                    <th>年齡</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="person:${persons}">
                    <td th:text="${person.name}"></td>
                    <td th:text="${person.age}"></td>
                </tr>
            </tbody>
        </table>
    </body>
    </html>
    

源碼詳解

ModelAndView

這種情況是在控制層返回ModelAndView對象,會在前端看到渲染的視圖。

  • 發起請求 http://localhost:8080/persons

  • 入口DispatcherServletdoService(HttpServletRequest request, HttpServletResponse response)

  • 請求分發doDispatch(HttpServletRequest request, HttpServletResponse response)

  • 根據請求獲取映射處理器HandlerExecutionChain mappedHandler = getHandler(processedRequest);

    • handlerMappings中找到以當前 request 作爲 key 的 handlerMapping

      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;
      }
      
    • 由 Debug 模式看出 handlerMappings中包含的元素如下:

      handlerMappings = {ArrayList@5507}  size = 5
       0 = {RequestMappingHandlerMapping@6466} 
       1 = {BeanNameUrlHandlerMapping@6467} 
       2 = {RouterFunctionMapping@6468} 
       3 = {SimpleUrlHandlerMapping@6469} 
       4 = {WelcomePageHandlerMapping@6470} 
      
    • 對於當前請求,進入if(handler != null)判斷時,對應的 mapping 和 handler 分別是:

      mapping = {RequestMappingHandlerMapping@6466} 
      
      handler = {HandlerExecutionChain@6534} "HandlerExecutionChain with [com.jake.spring.mvc.controller.PersonController#getPersons()] and 2 interceptors"
       handler = {HandlerMethod@6544} "com.jake.spring.mvc.controller.PersonController#getPersons()"
       interceptors = null
       interceptorList = {ArrayList@6545}  size = 2
        0 = {ConversionServiceExposingInterceptor@5577} 
        1 = {ResourceUrlProviderExposingInterceptor@5578} 
       interceptorIndex = -1
      

      說明此時使用的HandlerMappingRequestMappingHandlerMapping,其對應的handler是一個 HandlerExecution 對象,該對象中還有一個HandlerMethod類型的handler

  • HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    • getHandlerAdapter方法

      protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
          if (this.handlerAdapters != null) {
              for (HandlerAdapter adapter : this.handlerAdapters) {
                  if (adapter.supports(handler)) {
                      return adapter;
                  }
              }
          }
          throw new ServletException("No adapter for handler [" + handler +
                                     "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
      }
      
    • 由 Debug 可知, handler,即mappedHandler.getHandler() 的值是一個 HandlerMethod對象

      handler = {HandlerMethod@6544}com.jake.spring.mvc.controller.PersonController#getPersons()
      
    • 此時的 this.handlerAdapters爲:

      this.handlerAdapters = {ArrayList@5508}  size = 4
       0 = {RequestMappingHandlerAdapter@5604} 
       1 = {HandlerFunctionAdapter@5605} 
       2 = {HttpRequestHandlerAdapter@5606} 
       3 = {SimpleControllerHandlerAdapter@5607} 
      
    • 在進入if (adapter.supports(handler))時,得到的adapter

      adapter = {RequestMappingHandlerAdapter@5604}
      

      這裏的 supports 方法其實就是一個類型判斷,以HttpRequestHandlerAdapter爲例:

      @Override
      public boolean supports(Object handler) {
          return (handler instanceof HttpRequestHandler);
      }
      
  • 語句執行完成後,ha = {RequestMappingHandlerAdapter@5569}

  • 判斷是否要應用HandlerExecutionChain的變量 mappedHandler 的前置處理器,如果不應用直接返回。

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

    HandlerExecutionChainapplyPreHandle方法

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }
    
  • 調用mappedHandlerhandler

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

    進入AbstractHandlerMethodAdapterhandle方法:

    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }
    

    由 Debug 可知,handler=com.jake.spring.mvc.controller.PersonController#getPersons()

  • 此方法會調用當前類對象的handleInternal方法,而handleInternal方法只有一個類RequestMappingHandlerAdapter實現,其又調用了invokeHandlerMethod方法。

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    
  • 進入InvocableHandlerMethodinvokeAndHandle方法,此方法又調用了doInvoke方法,這個方法中真正實現了反射操作。

  • 進入PersonControllergetPersons方法,封裝好ModelAndView對象並返回

  • 再次回到DispatcherServletdoDispatch方法,此時

    mv = {ModelAndView@6338} "ModelAndView [view="person"; model={persons=[Person(name=Jake, age=27), Person(name=Heather, age=27)]}]"
    
  • 調用applyDefaultViewName給沒有viewNamerequest分配一個默認viewName

  • 調用mappedHandler.applyPostHandle(processedRequest, response, mv)做後置處理

    HandlerExecutionChainapplyPostHandle方法

    /**
     * Apply postHandle methods of registered interceptors.
     */
    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }
    

    postHandle 方法,顧名思義就是在當前請求進行處理之後,也就是 Controller 方法調用之後執行,但是它會在DispatcherServlet 進行視圖返回渲染之前被調用,所以我們可以在這個方法中對 Controller 處理之後的 ModelAndView 對象進行操作。

  • 調用processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)

    /**
     * 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);
            }
        }
    
        // Did the handler return a view to render?
        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);
        }
    }
    
  • 調用本類(DispatcherServlet)的render方法,其作用是渲染傳入的ModelAndView對象

    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;
        }
    }
    
  • 調用本類中的 resolveViewName 方法得到相應的view

    @Nullable
    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {
    
        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
    

    在進入if(view != null)時,Debug 中觀察到的變量如下:

    viewName = "person"
    model = {ModelMap@6527}  size = 1
     "persons" -> {ArrayList@6492}  size = 2
    locale = {Locale@6428} "zh_CN"
    request = {RequestFacade@5508} 
    viewResolver = {ContentNegotiatingViewResolver@6540} 
    view = {ThymeleafView@6541} 
    

    這裏使用的ViewResolver對象是ContentNegotiatingViewResolver類型的,另外,由於我們當前採用的是 Thymeleaf框架,所以View對象的實現類是ThymeleafView,該類是thymeleaf-spring5.jar中的一個類。再調用 view 的 render 方法,呈現指定模型的視圖,此方法的第一步是準備請求,以渲染 JSP 爲例,這意味着將模型對象設置爲請求屬性;第二步是視圖的實際呈現,例如通過 RequestDispatcher 包含 JSP。

  • 渲染完成後,斷點回到了processDispatchResult,執行mappedHandler.triggerAfterCompletion(request, response, null)來進行資源清理。

  • 如果在doDispatch時捕獲異常或錯誤,也會觸發HandlerInterceptorafterCompletion方法來進行資源清理。

  • 經過上述一系列步驟後,在瀏覽器中可以看到

    在這裏插入圖片描述

ResponseBody

這種情況直接返回 JSON 數據給前端

  • 訪問 http://localhost:8080/persons/json

  • 這種情況 doDispatch中的ModelAndView對象mvnull,其他均與返回視圖的情況一樣。

  • 在瀏覽器中可以看到

    在這裏插入圖片描述

過程總結

可以將上述的 Spring MVC 訪問實例以流程圖 + 源碼形式簡單總結:

在這裏插入圖片描述

  1. 客戶端發起請求,到達DispatchServletdoService方法,然後進入doDispatch方法。

  2. 調用getHandler方法,在此方法中由已初始化的HandlerMapping對象根據request獲取HandlerExecutionChain對象。

    mappedHandler = getHandler(processedRequest);
    
  3. 調用getHandlerAdapter方法,根據HandlerExecutionChain對象中的處理器 handler 來獲取 HandlerAdapter

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    

    執行HandlerAdapter對象的handle方法,獲取ModelAndView對象

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
  4. mv返回給DispatchServlet,以便其繼續利用mv來進行後置處理和渲染。

  5. 利用mv來進行視圖解析

    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    
    render(mv, request, response);
    
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    
    View view = viewResolver.resolveViewName(viewName, locale);
    
  6. 將模型渲染到前端視圖

    view.render(mv.getModelInternal(), request, response);
    
發佈了85 篇原創文章 · 獲贊 335 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章