1. 程式人生 > >“過時”的SpringMVC我們到底在用什麽?深入分析DispatchServlet源碼

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

5.0 用戶 artifact dep 源碼 config -i 深入分析 博客

之前已經分析過了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嗎?答案是,不全用。前後端做了代碼以及部署的分離,也就是說後端並不感知前端的存在,所以對於後端而言,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的依賴,紅框是Spring自動配置的依賴,藍框則是內嵌tomcat的依賴。裏面Spring的版本是5.0.8 RELEASE的。

SpringBoot啟動類
技術分享圖片

測試controller
技術分享圖片

啟動項目後,在瀏覽器裏面輸入http://localhost:8080/hello?name=Zack。結果返回Hello Zack
技術分享圖片

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

DispatcherServlet源碼分析

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

看一下doDispatch()的註釋說明
技術分享圖片
該方法的作用就是執行實際分發到的handler。

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

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

官網上的說明
技術分享圖片
HandlerAdapter協助DispatcherServlet去調用對應的handler,忽略具體handler是怎麽調用的。例如調用註解形式的controller需要處理註解,xml配置形式的要解析配置文件。這個適配器就是為了幫助DispatcherServlet屏蔽掉處理具體的細節。

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

回到doDispatch()這個方法的源碼上,看到getHandler()getHandlerAdapter()就是獲取Handler和HandlerAdapter所在。
技術分享圖片

getHandler()

看下getHandler()源碼
技術分享圖片
整個方法就那麽幾行,不過需要註意有兩個點。一個是該方法是返回HandlerExecutionChain類型,而不是一個Handler。
技術分享圖片
HandlerExecutionChain其實就是Handler的一層封裝,還包含Handler對應的interceptor攔截器,用於執行Handler的一些前置和後置的操作。

另外一個點,HandlerExecutionChain是按順序遍歷handlerMappings拿出來的。那HandlerMapping又是什麽呢?
技術分享圖片
從官網說明可知,它是一個請求和handler(實際是HandlerExecutionChain)的關聯Map,通俗的說就是路由與處理邏輯的關聯。它主要有兩個實現,一個是RequestMappingHandlerMapping(支持註解形式方法),另一個是SimpleUrlHandlerMapping(維護顯示註冊的URI資源)。

由此可推測,在Spring啟動的時候,就會去掃描註解、註冊的靜態資源,從而初始化這個handlerMappings。具體邏輯就在DispatcherServlet中的initHandlerMappings方法內。
技術分享圖片
初始化的方法內,主要有三步:

  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()方法,看是否支持。
技術分享圖片

supports()方法也很簡單,就用instanceof判斷handler是否Adapter自己支持的類。
技術分享圖片

HandlerAdapter.handle()

在獲取完HandlerHandlerAdapter後,就可以執行HandlerAdapter中的handle方法,其實際只是調用Handler的方法。
技術分享圖片

我們按Demo例子,看下HttpRequestHandlerAdapterhandle()方法實現。
技術分享圖片
這個方法裏面就是用HttpServlet的Request和Reponse去調用我們自己寫的controller裏面的方法。需要註意的是,這個方法返回的是ModelAndView,但我們目前基於Rest架構是已經不用的了,所以方法返回null回去了。

Handler的前置後置處理

前面提到Handler是被封裝在HandlerExecutionChain裏面的,其中還包含一些前置後置的攔截器。所以在執行HandlerAdapter.handle()前後會有對HandlerExecutionChain的調用,執行interceptor對前後置處理的方法
技術分享圖片

具體裏面的實現就是執行interceptorpreHandle()postHandle()方法。
技術分享圖片

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

總結

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

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

更多技術文章、精彩幹貨,請關註
博客:zackku.com
微信公眾號:Zack說碼
技術分享圖片

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