天天看點

SpringMVC 源碼:misc

目錄

1. 從源碼的角度來看 SpringMVC

2. 分析spring mvc源碼

3. SpringMVC 深度探險

SpringMVC深度探險(一) —— SpringMVC前傳

SpringMVC深度探險(二) —— SpringMVC概覽

SpringMVC深度探險(三) —— DispatcherServlet與初始化主線

SpringMVC深度探險(四) —— SpringMVC核心配置檔案詳解

1. 從源碼的角度來看 SpringMVC

https://mp.weixin.qq.com/s?src=11&timestamp=1564039945&ver=1749&signature=h4KBhAruR7LGtFBln7w*u746pUFL5xgCQ-O61ALC5SxIMvtgBuE*yZxaKVR93yHMiqcACZYL0VREFz3ww6UAurpLSuER4*JLXqkZDqrSA37b-2rV2q*1nWnF*qoF227F&new=1

SpringMVC核心流程圖

SpringMVC 源碼:misc

簡單總結

首先請求進入DispatcherServlet 由DispatcherServlet 從HandlerMappings中提取對應的Handler  

此時隻是擷取到了對應的Handle,然後得去尋找對應的擴充卡,即:HandlerAdapter

拿到對應HandlerAdapter時,這時候開始調用對應的Handler處理業務邏輯了(這時候實際上已經執行完了我們的Controller) 執行完成之後傳回一個ModeAndView

這時候交給我們的ViewResolver通過視圖名稱查找出對應的視圖然後傳回

最後 渲染視圖 傳回渲染後的視圖 -->響應請求

SpringMVC 源碼解析

首先我們檢視繼承關系(關鍵檢視藍色箭頭路線) 會發現DispatcherServlet無非就是一個HttpServlet

SpringMVC 源碼:misc

由此,我們可以去檢視Servlet的關鍵方法:service,doGet,doPost  

  •  service:
@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 {
super.service(request, response);
}
}
           
  •  doGet:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

processRequest(request, response);
}
           
  •  doPost:
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

   processRequest(request, response);
}
           

這裡會發現無論是哪個方法最後都調用了 processRequest(request, response);我們把焦點放在這個方法上,會發現一個核心的方法:

doService(request, response);然後會發現 這個方法貌似,呃..有點不一樣:

protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;

//它居然是一個抽象方法...這就得回到剛剛的繼承關系中,找到他的子類了:DispatcherServlet
           

反正我看到這個方法的實作的時候,腦海裡就浮現出4個字:花 裡 胡 哨 。代碼太多,就不放在筆記裡面了,太占地方了.. 為什麼這樣說呢? 因為你看完之後會發現關鍵在于:doDispatch(request, response);是的,沒看錯,這一行才是關鍵!

我們把視角切入這個方法 (至于代碼..還是不放進來了.. ) 總結一下:

把要用的變量都定義好:比如我們的ModelAndView以及異常..等等;

下面即将看到的是一個熟悉的陌生人(噢不,關鍵詞) 

processedRequest = checkMultipart(request);

 "Multipart" 這個關鍵詞好像在哪見過??..讓我想想..(漸漸步入了知識盲區) 哦對!在檔案上傳的時候!(勉強想起來了。。) 是的,其實這行代碼就是判斷目前請求是否是一個二進制請求(有沒有帶檔案) 當然 這裡隻是提一下,并不是本文的核心内容。。。(有時間的小夥伴可以自己去了解一下)

好的,現在回到我們的主題,來看看這個方法:

mappedHandler = getHandler(processedRequest);

看過我們上面流程圖的同學應該會知道他現在在幹嘛。 現在來擷取我們的Handler了..從哪擷取呢? 從他的HandlerMapping裡面擷取。

問題1:至于這個HandlerMappings 哪裡來的呢 

這個等下讨論 我們先來看看他擷取的代碼:

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping hm : this.handlerMappings) {
         if (logger.isTraceEnabled()) {
            logger.trace(
                  "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
         }
         HandlerExecutionChain handler = hm.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}
           

我們能看見他是在周遊一個handlerMappings 

問題2:至于這個handlerMapping是什麼呢

SpringMVC 源碼:misc

是2個我們不認識的東西,至于是什麼,現在說了也不知道,我們繼續往下走,可以看見圖檔上1188行代碼, 他從這個mapping 裡面擷取了一個handler 如果擷取到了 這個方法就走完了, 不然就下一次循環

問題1解釋:

我們先回到剛剛那個問題,這個HandlerMapping 哪裡來的呢。 不多說,上圖:

SpringMVC 源碼:misc

我們在源碼包的DispatcherServlet.properties檔案下會看見, 他定義了圖檔裡的這些屬性。 重點放在方框内,第一個屬性,就是我們剛看見的HandlerMappings, 也就是說 HandlerMappings也就是他自己事先定義好的呢。至于第二個屬性,咱們待會兒見~

也就是說SpringMVC自己自帶了2個HandlerMapping 來供我們選擇 至于 為什麼要有2個呢? 這時候得啟動項目從斷點的角度來看看了;

我們用2種方式來注冊Controller 分别是:

  • 作為Bean的形式:
@Component("/test")
public class TesrController  implements org.springframework.web.servlet.mvc.Controller{


    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        System.out.println("1");
        return null;
    }
}
           
  • 以Annotation形式:
@Controller
public class AnnotationController {

    @RequestMapping("/test2")
    public Object test(){

        System.out.println("test");

        return null;
    }
}
           

我們先用Bean的方式來跑:

視角走到我們的mappedHandler = getHandler(processedRequest);裡面

SpringMVC 源碼:misc

問題2解釋:

來,跟着箭頭走,我們發現 我們以Bean的形式注冊的Controller 可以從這個BeanNameUrlHandlerMapping裡面擷取到對應的Handler ; 這裡 我們是不是對于這個HandlerMapping有了懵懂的了解了?

猜想1:

 我們來猜一下 如果是以Annotation的形式注冊的Controller的話 就會被第二個HandlerMapping擷取到。 至于對不對 這個問題我們先留着。

我們先讓代碼繼續走,會發現 Handler傳回出來緊接着會執行下面這個方法,這個方法我們從流程圖中可以了解到,就是在找一個擴充卡。

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

問題3:何為擴充卡? 

我們先來看看他這個方法裡面幹了啥:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
      for (HandlerAdapter ha : this.handlerAdapters) {
         if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
         }
         if (ha.supports(handler)) {
            return ha;
         }
      }
   }
   throw new ServletException("No adapter for handler [" + handler +
         "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
           

其實能看見他是從一個handlerAdapters屬性裡面周遊了我們的擴充卡 這個handlerAdapters哪來的呢? 跟我們的HandlerMappings一樣 在他的配置檔案裡面有寫,就是我們剛剛所說的 待會兒見的那個東西~ 不多說 上圖:

SpringMVC 源碼:misc
SpringMVC 源碼:misc

問題3解釋:

至于什麼是擴充卡,我們結合Handler來講, 就如我們在最開始的總結時所說的, 一開始隻是找到了Handler 現在要執行了,但是有個問題,Handler不止一個, 自然而然對應的執行方式就不同了, 這時候擴充卡的概念就出來了:對應不同的Handler的執行方案。

當找到合适的擴充卡的時候, 基本上就已經收尾了,因為後面在做了一些判斷之後(判斷請求類型之類的),就開始執行了你的Handler了,上代碼:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
           

這個mv就是我們的ModlAndView 其實執行完這一行 我們的Controller的邏輯已經執行完了, 剩下的就是尋找視圖 渲染圖的事情了....

我們這裡隻是使用了Bean的形式執行了一遍流程 假設使用Annotation呢?

SpringMVC BeanName方式和Annotation方式注冊Controller源碼分析

現在我們來使用Annotation來注冊Controller看看。我們這裡隻看不同的地方。

猜想1證明:

首先在這個HandlerMappings這裡之前的那個就不行了 這裡采用了另外一個HandlerMapping 其實也就證明了我們的猜想1

SpringMVC 源碼:misc

然後就是到了我們的擴充卡了:

SpringMVC 源碼:misc

這裡我們會看到用的是這個擴充卡 而我們的Bean方式注冊的Controller 的話 使用的是另外兩個擴充卡來的,至于有什麼差別呢? 我們來看看他執行的時候:

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

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

//我們的Annotation的形式 是拿到這個handler作為一個HandlerMethod 也就是一個方法對象來執行 這時候我們看看Bean是什麼樣子的:

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {

   return ((Controller) handler).handleRequest(request, response);
}
           

由最開始可以看到 我們如果以Bean的形式注冊Controller的話 我們的實作一個Controller的接口 在這裡 他把我們的handler強制轉換為一個Controller來執行了。 

總結

其實我們的SpringMVC關鍵的概念就在于Handler(處理器) 和Adapter(擴充卡)

通過一個關鍵的HandlerMappings 找到合适處理你的Controller的Handler 然後再通過HandlerAdapters找到一個合适的HandlerAdapter 來執行Handler即Controller裡面的邏輯。 最後再傳回ModlAndView...

2. 分析spring mvc源碼

在分析spring mvc源碼之前,先看一張圖:

SpringMVC 源碼:misc

請求處理的過程:

1.DispatcherServelt作為前端控制器,攔截request對象。

2.DispatcherServlet接收到request對象之後,查詢HandlerMapping,得到一個HandlerExecutionChain對象。

3.DispatcherServlet将Handler對象交由HandlerAdapter(擴充卡模式的典型應用),調用相應的controller方法。

4.Controller方法傳回ModelAndView對象,DispatcherServlet将view交由ViewResolver進行解析,得到相應的視圖。用model渲染視圖。

5.傳回響應結果。

整個過程的流程其實就是DispatcherServelt中doDispatch()方法的調用過程。

protected void doDispatch(HttpServletRequest request,
        HttpServletResponse response) throws Exception {
        //第一步攔截request對象,doDispatch()方法在doService()方法中被調用,request對象是經過處理的。
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

            try {
                //和檔案的上傳和下載下傳有關系,判斷請求的類型是否是multipart類型
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // Determine handler for the current request.
                //主要看這裡,這裡是得到HandlerExecutionChain的方法。關于Handler()方法向下看
                mappedHandler = getHandler(processedRequest);

                if ((mappedHandler == null) ||
                        (mappedHandler.getHandler() == null)) {
                    noHandlerFound(processedRequest, response);

                    return;
                }

                // Determine handler adapter for the current request.
                //這裡已經擷取到HandlerExecutionChain對象,接下來就要擷取HandlerAdapter對象,調用Handler對象的方法。
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler
                //有關浏覽器緩存的設定(304)
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);

                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request,
                            mappedHandler.getHandler());

                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" +
                            getRequestUri(request) + "] is: " + lastModified);
                    }

                    if (new ServletWebRequest(request, response).checkNotModified(
                                lastModified) && isGet) {
                        return;
                    }
                }

                //pan'du
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

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

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

                //解析視圖,資料渲染
                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            }

            processDispatchResult(processedRequest, response, mappedHandler,
                mv, dispatchException);
        } catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Error err) {
            triggerAfterCompletionWithError(processedRequest, response,
                mappedHandler, 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);
                }
            }
        }
    }

    protected HandlerExecutionChain getHandler(HttpServletRequest request)
        throws Exception {
        //周遊HandlerMappingList對象(存儲若幹個HandlerMapping對象),不斷調用,直到不為空為止,否則傳回null
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler map [" + hm +
                    "] in DispatcherServlet with name '" + getServletName() +
                    "'");
            }

            HandlerExecutionChain handler = hm.getHandler(request);

            if (handler != null) {
                return handler;
            }
        }

        return null;
    }
           

3. SpringMVC 深度探險

SpringMVC深度探險(一) —— SpringMVC前傳

兩種截然不同的針對表示層的解決方案的設計思路: 

  • 以伺服器端應用程式為主導來進行架構設計
  • 以浏覽器頁面元件(及其自身的事件觸發模型)為主導來進行架構設計

業界對于上述這兩種不同的設計模型也賦予了不同的名詞定義:前一種被稱之為MVC模型;後一種則被稱之為元件模型,也有稱之為事件模型。 

在MVC模型中,浏覽器端和伺服器端的互動關系非常明确:無論采取什麼樣的架構,總是以一個明确的URL作為中心,輔之以參數請求。是以,URL看上去就像是一個明文的契約,當然,真正蘊藏在背後的是Http協定。所有的這些東西都被放在了台面上,我們可以非常明确地擷取到一次互動中所有的Http資訊。這也是MVC模型中最為突出的一個特點。 

一些與MVC模型截然不同的特點: 

1. 架構通過對HTML進行行為擴充來幹預和控制浏覽器與伺服器的互動過程。 

我們可以發現,Tapestry5的請求頁面被加入了更多的HTML擴充,這些擴充包括對HTML标簽的擴充以及HTML标簽中屬性的擴充。而這些擴充中,有不少直接幹預了浏覽器與伺服器的互動。例如,上面例子中的t:validate="required,minlength=3"擴充實際上就會被自動映射到伺服器端程式中帶有@Component(id="password")标注的PasswordField元件上,并在送出時自動進行元件化校驗。而當頁面上的送出按鈕被點選觸發時,預設在伺服器端的onSuccess方法會形成響應并調用其内部邏輯。 

2. 頁面元件的實作是整個元件模型的絕對核心 

從上述的例子中,我們可以看到元件模型的實作不僅需要伺服器端實作,還需要在頁面上指定與某個特定元件進行事件綁定。兩者缺一不可,必須互相配合,共同完成。是以整個Web程式的互動能力完全取決于頁面元件的實作好壞。 

3. 頁面元件與伺服器端響應程式之間的映射契約并不基于Http協定進行 

在上面的例子中,從頁面元件到伺服器端的響應程式之間的映射關系是通過名稱契約而定的。而頁面上的每個元件可以指定映射到伺服器端程式的具體某一個方法。我們可以看到這種映射方式并不是一種基于URL或者Http協定的映射方式,而是一種命名指定的方式。 

在元件模型中,浏覽器端和伺服器端的互動關系并不以一個具體的URL為核心,我們在上述的例子中甚至完全沒有看到任何URL的影子。不過這種事件響應式的方式,也提供給我們另外一個程式設計的思路,而這種基于契約式的請求-響應映射也得到了一部分程式員的喜愛。因而元件模型的粉絲數量也是很多的。 

https://downpour.iteye.com/blog/1330537

SpringMVC 源碼:misc

SpringMVC深度探險(二) —— SpringMVC概覽

SpringMVC深度探險(三) —— DispatcherServlet與初始化主線

SpringMVC深度探險(四) —— SpringMVC核心配置檔案詳解