“過(guò)時(shí)”的SpringMVC我們到底在用什么?深入分析DispatchServlet源碼

之前已經(jīng)分析過(guò)了Spring的IOC(《零基礎(chǔ)帶你看Spring源碼——IOC控制反轉(zhuǎn)》)與AOP(《從源碼入手,一文帶你讀懂Spring AOP面向切面編程》)的源碼,本次就來(lái)分析下SpringMVC。本文先簡(jiǎn)述下目前SpringMVC的使用情況,然后通過(guò)Demo的簡(jiǎn)單讓大家有一個(gè)初步的使用印象,然后帶著印象去看其中執(zhí)行的分發(fā)源碼。

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

Spring MVC,官方名字其實(shí)是Spring Web MVC,Maven上的包名也是spring-webmvc。從Spring誕生以來(lái),它就是一款基于Servlet Api的web架構(gòu)。值得一提的是,在Spring5的時(shí)候,出了一款新的Web架構(gòu),F(xiàn)lux,是基于事件驅(qū)動(dòng)模型(類似nodejs)做的。以后會(huì)寫一篇來(lái)專門介紹一下Flux,敬請(qǐng)關(guān)注。

MVC,可以說(shuō)是“上個(gè)世紀(jì)”最流行的前后端交互模型。它包含Model(業(yè)務(wù)模型)、View(用戶視圖)、Controller(控制器),把各部分分開組織,對(duì)代碼抽象與隔離的處理可謂是代碼設(shè)計(jì)的典范。

不過(guò)自從15年開始,隨著各種前端框架的崛起,使得前端后端的關(guān)系發(fā)生進(jìn)一步的演變,從MVC架構(gòu)演變成前后端分離的REST架構(gòu)了。以前MVC架構(gòu)每次請(qǐng)求都需要經(jīng)過(guò)控制器->模型->視圖的流程,演變成前端請(qǐng)求后端接口,返回JSON的這樣一種REST架構(gòu)。


問(wèn)題來(lái)了,我們到底還在用SpringMVC嗎?答案是,不全用。前后端做了代碼以及部署的分離,也就是說(shuō)后端并不感知前端的存在,所以對(duì)于后端而言,View(用戶視圖)也就無(wú)從可談了。Model(業(yè)務(wù)模型)發(fā)送性質(zhì)上的改變,以前是一個(gè)前端所需要的Model,給頁(yè)面讀取,現(xiàn)在是一個(gè)JSON格式給到前端,由前端自由處理。

而作為Web框架的核心,Controller(控制器)則是依然留存的。所以現(xiàn)在大家用SpringMVC用的更多是Controller這一層。當(dāng)然SpringMVC還有其他組件,包括filter、Http Caching、Web Security等等。本文只是著重MVC架構(gòu)中的Controller的功能,而Controller的核心組件則是DispatcherServlet。所以后面我們將通過(guò)Demo,來(lái)逐步深入了解下,DispatcherSevlet如何做到對(duì)請(qǐng)求控制分發(fā)的。

傳統(tǒng)SpringMVC啟動(dòng)簡(jiǎn)述

在傳統(tǒng)的SpringMVC中,需要配置web.xml和applicationContext.xml。前者是負(fù)責(zé)配置項(xiàng)目初始化的配置,如servlet、welcome頁(yè)面等,是JavaEE的規(guī)范。后者是初始化Spring Context的配置,主要是Bean的配置。

前文說(shuō)到,SpringMVC是基于Servlet的架構(gòu),而DispatcherServlet則是SpringMVC攔截處理所有請(qǐng)求的Servlet,所以web.xml需要配置DispatcherServlet。其他的還有contextLoaderListener,負(fù)責(zé)加載除DispatcherServlet外的所有context內(nèi)容,另外還需要通過(guò)contextConfigLoader指定Spring的配置文件(如applicationContext.xml)。

那么在項(xiàng)目啟動(dòng)的時(shí)候,加載web.xml首先會(huì)執(zhí)行contextLoaderListener,讓它初始化好Spring的Application context。后面有HTTP請(qǐng)求進(jìn)來(lái),則會(huì)落到DispatcherServlet上,讓它去做處理分發(fā)。

SpringBoot Web Demo搭建

自從Spring配置注解和SpringBoot誕生以來(lái),越來(lái)越少人去寫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>

看下這個(gè)starter包含什么內(nèi)容



綠框是springMVC的依賴,紅框是Spring自動(dòng)配置的依賴,藍(lán)框則是內(nèi)嵌tomcat的依賴。里面Spring的版本是5.0.8 RELEASE的。

SpringBoot啟動(dòng)類


測(cè)試controller


啟動(dòng)項(xiàng)目后,在瀏覽器里面輸入http://localhost:8080/hello?name=Zack。結(jié)果返回Hello Zack。

以上就是我們現(xiàn)在利用SpringMVC的基本內(nèi)容,下面我們來(lái)看下SpringMVC如何利用DispatcherServlet做攔截分發(fā)的。

DispatcherServlet源碼分析

當(dāng)一個(gè)請(qǐng)求進(jìn)來(lái)的時(shí)候,會(huì)先執(zhí)行各種filter,過(guò)濾掉最終需要的請(qǐng)求,然后會(huì)落到DispatcherServlet中的doService()方法。該方法是預(yù)先設(shè)置一些特殊請(qǐng)求參數(shù),然后再轉(zhuǎn)發(fā)給doDispatch()做真正的處理轉(zhuǎn)發(fā)。

看一下doDispatch()的注釋說(shuō)明


該方法的作用就是執(zhí)行實(shí)際分發(fā)到的handler。

  • Handler通過(guò)HandlerMapping的優(yōu)先級(jí)獲取。HandlerAdapter通過(guò)查詢DispatcherServlet已裝載的HandlerAdapter,并且支持該Handler而獲取的。
  • 所有的HTTP請(qǐng)求都是doDispatch()去處理的。具體是落到哪個(gè)方法去處理業(yè)務(wù)邏輯,取決于HandlerAdapters或者h(yuǎn)andlers。

從注釋可知,整個(gè)的分發(fā)邏輯核心,就在于HandlerAdapter和Handler。那這兩到底是什么東西?

官網(wǎng)上的說(shuō)明



HandlerAdapter協(xié)助DispatcherServlet去調(diào)用對(duì)應(yīng)的handler,忽略具體handler是怎么調(diào)用的。例如調(diào)用注解形式的controller需要處理注解,xml配置形式的要解析配置文件。這個(gè)適配器就是為了幫助DispatcherServlet屏蔽掉處理具體的細(xì)節(jié)。

至于Handler沒(méi)有清晰解釋,但我們debug源碼可以發(fā)現(xiàn),Handler其實(shí)就是實(shí)際分配到具體需要去處理的方法(對(duì)比下圖紅框和上面Demo的controller)。


回到doDispatch()這個(gè)方法的源碼上,看到getHandler()getHandlerAdapter()就是獲取Handler和HandlerAdapter所在。

getHandler()

看下getHandler()源碼


整個(gè)方法就那么幾行,不過(guò)需要注意有兩個(gè)點(diǎn)。一個(gè)是該方法是返回HandlerExecutionChain類型,而不是一個(gè)Handler。
image

HandlerExecutionChain其實(shí)就是Handler的一層封裝,還包含Handler對(duì)應(yīng)的interceptor攔截器,用于執(zhí)行Handler的一些前置和后置的操作。

另外一個(gè)點(diǎn),HandlerExecutionChain是按順序遍歷handlerMappings拿出來(lái)的。那HandlerMapping又是什么呢?


從官網(wǎng)說(shuō)明可知,它是一個(gè)請(qǐng)求和handler(實(shí)際是HandlerExecutionChain)的關(guān)聯(lián)Map,通俗的說(shuō)就是路由與處理邏輯的關(guān)聯(lián)。它主要有兩個(gè)實(shí)現(xiàn),一個(gè)是RequestMappingHandlerMapping(支持注解形式方法),另一個(gè)是SimpleUrlHandlerMapping(維護(hù)顯示注冊(cè)的URI資源)。

由此可推測(cè),在Spring啟動(dòng)的時(shí)候,就會(huì)去掃描注解、注冊(cè)的靜態(tài)資源,從而初始化這個(gè)handlerMappings。具體邏輯就在DispatcherServlet中的initHandlerMappings方法內(nèi)。


初始化的方法內(nèi),主要有三步:

  1. 從Spring的ApplicationContext中取出HandlerMapping的Bean
  2. 然后對(duì)上面取出來(lái)的Bean做優(yōu)先級(jí)排序,主要對(duì)是@Order注解的排序
  3. 如果上面取不出Bean,則用默認(rèn)策略。

對(duì)于第三點(diǎn)的默認(rèn)策略,可以找到DispatcherServlet.properties這個(gè)文件,里面配置了一些默認(rèn)HandlerMapping、HandlerAdapter等相關(guān)類。

在初始化handlerMappings后,如果有請(qǐng)求進(jìn)來(lái),后面的request就用請(qǐng)求的路由與HandlerMapping對(duì)比,最后找出HandlerHandlerExecutionChain)。

getHandlerAdapter()

在取出實(shí)際處理的Handler后,就需要用它找出support它的適配器(HandlerAdapter)。按照前面對(duì)HandlerAdapter的描述,對(duì)于Demo而言,support這個(gè)Handler必定是RequestMappingHandlerAdapter。

這個(gè)邏輯也非常簡(jiǎn)單,同樣是遍歷已初始化的handlerAdapters(初始化的過(guò)程類似handlerMappings),然后對(duì)于具體每個(gè)handlerAdapter,調(diào)用其support()方法,看是否支持。

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

HandlerAdapter.handle()

在獲取完HandlerHandlerAdapter后,就可以執(zhí)行HandlerAdapter中的handle方法,其實(shí)際只是調(diào)用Handler的方法。

我們按Demo例子,看下HttpRequestHandlerAdapterhandle()方法實(shí)現(xiàn)。


這個(gè)方法里面就是用HttpServlet的Request和Reponse去調(diào)用我們自己寫的controller里面的方法。需要注意的是,這個(gè)方法返回的是ModelAndView,但我們目前基于Rest架構(gòu)是已經(jīng)不用的了,所以方法返回null回去了。

Handler的前置后置處理

前面提到Handler是被封裝在HandlerExecutionChain里面的,其中還包含一些前置后置的攔截器。所以在執(zhí)行HandlerAdapter.handle()前后會(huì)有對(duì)HandlerExecutionChain的調(diào)用,執(zhí)行interceptor對(duì)前后置處理的方法

具體里面的實(shí)現(xiàn)就是執(zhí)行interceptorpreHandle()postHandle()方法。

回過(guò)頭來(lái)想下,這里的前后置處理會(huì)包括什么呢?在HandlerInterceptor注解上有說(shuō)明三個(gè)實(shí)現(xiàn)類,分別是UserRoleAuthorizationInterceptor(檢查用戶權(quán)限)、LocaleChangeInterceptor(修改本地時(shí)間)、ThemeChangeInterceptor(修改當(dāng)前主題)??梢钥闯?strong>HandlerInterceptor基本都是對(duì)請(qǐng)求的一些預(yù)處理和結(jié)果封裝。

總結(jié)

以上就是SpringMVC中DispatcherServlet的基本過(guò)程。下面來(lái)總結(jié)下以上內(nèi)容:

  1. 前后端的架構(gòu)演變導(dǎo)致SpringMVC的使用發(fā)生改變,更多著重在“C”上了。
  2. “C”的核心在DispatcherServletdoDispatcher()方法中。
  3. 利用request的路由,對(duì)比從已初始化的handlerMappingshandlerAdapters中獲取handlerhandlerAdapter。
  4. handler是封裝在HandlerExecutionChain中,其中還包括handler的前后置攔截器。
  5. 最后利用適配器模式,調(diào)用HandlerAdapter.handle()方法去執(zhí)行handler具體處理的業(yè)務(wù)邏輯。
  6. 在執(zhí)行具體業(yè)務(wù)邏輯前后會(huì)執(zhí)行封裝在HandlerExecutionChain里面的攔截器。

更多技術(shù)文章、精彩干貨,請(qǐng)關(guān)注
博客:zackku.com
微信公眾號(hào):Zack說(shuō)碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容