“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

公眾號_Zack說碼發表於2018-11-22

之前已經分析過了Spring的IOC(《零基礎帶你看Spring原始碼——IOC控制反轉》)與AOP(《從原始碼入手,一文帶你讀懂Spring AOP面向切面程式設計》)的原始碼,本次就來分析下SpringMVC。本文先簡述下目前SpringMVC的使用情況,然後通過Demo的簡單讓大家有一個初步的使用印象,然後帶著印象去看其中執行的分發原始碼。

到底什麼是Spring MVC,我們還在用嗎?

Spring MVC,官方名字其實是Spring Web MVC,Maven上的包名也是spring-webmvc。從Spring誕生以來,它就是一款基於Servlet Api的web架構。值得一提的是,在Spring5的時候,出了一款新的Web架構,Flux,是基於事件驅動模型(類似nodejs)做的。以後會寫一篇來專門介紹一下Flux,敬請關注。

MVC,可以說是“上個世紀”最流行的前後端互動模型。它包含Model(業務模型)、View(使用者檢視)、Controller(控制器),把各部分分開組織,對程式碼抽象與隔離的處理可謂是程式碼設計的典範。

不過自從15年開始,隨著各種前端框架的崛起,使得前端後端的關係發生進一步的演變,從MVC架構演變成前後端分離的REST架構了。以前MVC架構每次請求都需要經過控制器->模型->檢視的流程,演變成前端請求後端介面,返回JSON的這樣一種REST架構。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

問題來了,我們到底還在用SpringMVC嗎?答案是,不全用。前後端做了程式碼以及部署的分離,也就是說後端並不感知前端的存在,所以對於後端而言,View(使用者檢視)也就無從可談了。Model(業務模型)傳送性質上的改變,以前是一個前端所需要的Model,給頁面讀取,現在是一個JSON格式給到前端,由前端自由處理。

而作為Web框架的核心,Controller(控制器)則是依然留存的。所以現在大家用SpringMVC用的更多是Controller這一層。當然SpringMVC還有其他元件,包括filter、Http Caching、Web Security等等。本文只是著重MVC架構中的Controller的功能,而Controller的核心元件則是DispatcherServlet。所以後面我們將通過Demo,來逐步深入瞭解下,DispatcherSevlet如何做到對請求控制分發的。

傳統SpringMVC啟動簡述

在傳統的SpringMVC中,需要配置web.xml和applicationContext.xml。前者是負責配置專案初始化的配置,如servlet、welcome頁面等,是JavaEE的規範。後者是初始化Spring Context的配置,主要是Bean的配置。

前文說到,SpringMVC是基於Servlet的架構,而DispatcherServlet則是SpringMVC攔截處理所有請求的Servlet,所以web.xml需要配置DispatcherServlet。其他的還有contextLoaderListener,負責載入除DispatcherServlet外的所有context內容,另外還需要通過contextConfigLoader指定Spring的配置檔案(如applicationContext.xml)。

那麼在專案啟動的時候,載入web.xml首先會執行contextLoaderListener,讓它初始化好Spring的Application context。後面有HTTP請求進來,則會落到DispatcherServlet上,讓它去做處理分發。

SpringBoot Web Demo搭建

自從Spring配置註解和SpringBoot誕生以來,越來越少人去寫web.xml和applicationContext.xml配置檔案了。但為了方便直接瞭解Dispatcher的原理,Demo直接用SpringBoot的starter一鍵式搭建。

直接新增web的starter依賴

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>
複製程式碼

看下這個starter包含什麼內容

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
綠框是springMVC的依賴,紅框是Spring自動配置的依賴,藍框則是內嵌tomcat的依賴。裡面Spring的版本是5.0.8 RELEASE的。

SpringBoot啟動類

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

測試controller

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

啟動專案後,在瀏覽器裡面輸入http://localhost:8080/hello?name=Zack。結果返回Hello Zack

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

以上就是我們現在利用SpringMVC的基本內容,下面我們來看下SpringMVC如何利用DispatcherServlet做攔截分發的。

DispatcherServlet原始碼分析

當一個請求進來的時候,會先執行各種filter,過濾掉最終需要的請求,然後會落到DispatcherServlet中的**doService()方法。該方法是預先設定一些特殊請求引數,然後再轉發給doDispatch()**做真正的處理轉發。

看一下**doDispatch()**的註釋說明

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
該方法的作用就是執行實際分發到的handler。

  • Handler通過HandlerMapping的優先順序獲取。HandlerAdapter通過查詢DispatcherServlet已裝載的HandlerAdapter,並且支援該Handler而獲取的。
  • 所有的HTTP請求都是**doDispatch()**去處理的。具體是落到哪個方法去處理業務邏輯,取決於HandlerAdapters或者handlers。

從註釋可知,整個的分發邏輯核心,就在於HandlerAdapter和Handler。那這兩到底是什麼東西?

官網上的說明

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
HandlerAdapter協助DispatcherServlet去呼叫對應的handler,忽略具體handler是怎麼呼叫的。例如呼叫註解形式的controller需要處理註解,xml配置形式的要解析配置檔案。這個介面卡就是為了幫助DispatcherServlet遮蔽掉處理具體的細節。

至於Handler沒有清晰解釋,但我們debug原始碼可以發現,Handler其實就是實際分配到具體需要去處理的方法(對比下圖紅框和上面Demo的controller)。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

回到doDispatch()這個方法的原始碼上,看到getHandler()、**getHandlerAdapter()**就是獲取Handler和HandlerAdapter所在。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

getHandler()

看下getHandler()原始碼

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
整個方法就那麼幾行,不過需要注意有兩個點。一個是該方法是返回HandlerExecutionChain型別,而不是一個Handler。
“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
HandlerExecutionChain其實就是Handler的一層封裝,還包含Handler對應的interceptor攔截器,用於執行Handler的一些前置和後置的操作。

另外一個點,HandlerExecutionChain是按順序遍歷handlerMappings拿出來的。那HandlerMapping又是什麼呢?

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
從官網說明可知,它是一個請求和handler(實際是HandlerExecutionChain)的關聯Map,通俗的說就是路由與處理邏輯的關聯。它主要有兩個實現,一個是RequestMappingHandlerMapping(支援註解形式方法),另一個是SimpleUrlHandlerMapping(維護顯示註冊的URI資源)。

由此可推測,在Spring啟動的時候,就會去掃描註解、註冊的靜態資源,從而初始化這個handlerMappings。具體邏輯就在DispatcherServlet中的initHandlerMappings方法內。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
初始化的方法內,主要有三步:

  1. 從Spring的ApplicationContext中取出HandlerMapping的Bean
  2. 然後對上面取出來的Bean做優先順序排序,主要對是@Order註解的排序
  3. 如果上面取不出Bean,則用預設策略。

對於第三點的預設策略,可以找到DispatcherServlet.properties這個檔案,裡面配置了一些預設HandlerMapping、HandlerAdapter等相關類。

在初始化handlerMappings後,如果有請求進來,後面的request就用請求的路由與HandlerMapping對比,最後找出HandlerHandlerExecutionChain)。

getHandlerAdapter()

在取出實際處理的Handler後,就需要用它找出support它的介面卡(HandlerAdapter)。按照前面對HandlerAdapter的描述,對於Demo而言,support這個Handler必定是RequestMappingHandlerAdapter

這個邏輯也非常簡單,同樣是遍歷已初始化的handlerAdapters(初始化的過程類似handlerMappings),然後對於具體每個handlerAdapter,呼叫其**support()**方法,看是否支援。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

**supports()**方法也很簡單,就用instanceof判斷handler是否Adapter自己支援的類。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

HandlerAdapter.handle()

在獲取完HandlerHandlerAdapter後,就可以執行HandlerAdapter中的handle方法,其實際只是呼叫Handler的方法。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

我們按Demo例子,看下HttpRequestHandlerAdapterhandle()方法實現。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼
這個方法裡面就是用HttpServlet的Request和Reponse去呼叫我們自己寫的controller裡面的方法。需要注意的是,這個方法返回的是ModelAndView,但我們目前基於Rest架構是已經不用的了,所以方法返回null回去了。

Handler的前置後置處理

前面提到Handler是被封裝在HandlerExecutionChain裡面的,其中還包含一些前置後置的攔截器。所以在執行HandlerAdapter.handle()前後會有對HandlerExecutionChain的呼叫,執行interceptor對前後置處理的方法

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

具體裡面的實現就是執行interceptor的**preHandle()postHandle()**方法。

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

回過頭來想下,這裡的前後置處理會包括什麼呢?在HandlerInterceptor註解上有說明三個實現類,分別是UserRoleAuthorizationInterceptor(檢查使用者許可權)、LocaleChangeInterceptor(修改本地時間)、ThemeChangeInterceptor(修改當前主題)。可以看出HandlerInterceptor基本都是對請求的一些預處理和結果封裝。

總結

以上就是SpringMVC中DispatcherServlet的基本過程。下面來總結下以上內容:

  1. 前後端的架構演變導致SpringMVC的使用發生改變,更多著重在“C”上了。
  2. “C”的核心在DispatcherServlet的**doDispatcher()**方法中。
  3. 利用request的路由,對比從已初始化的handlerMappingshandlerAdapters中獲取handlerhandlerAdapter
  4. handler是封裝在HandlerExecutionChain中,其中還包括handler的前後置攔截器。
  5. 最後利用介面卡模式,呼叫HandlerAdapter.handle()方法去執行handler具體處理的業務邏輯。
  6. 在執行具體業務邏輯前後會執行封裝在HandlerExecutionChain裡面的攔截器。

更多技術文章、精彩乾貨,請關注
部落格:zackku.com
微信公眾號:Zack說碼

“過時”的SpringMVC我們到底在用什麼?深入分析DispatchServlet原始碼

相關文章