「過期」的SpringMVC咱們到底在用什麼?深刻分析DispatchServlet源碼

以前已經分析過了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,敬請關注。node

MVC,能夠說是「上個世紀」最流行的先後端交互模型。它包含Model(業務模型)、View(用戶視圖)、Controller(控制器),把各部分分開組織,對代碼抽象與隔離的處理可謂是代碼設計的典範。web

不過自從15年開始,隨着各類前端框架的崛起,使得前端後端的關係發生進一步的演變,從MVC架構演變成先後端分離的REST架構了。之前MVC架構每次請求都須要通過控制器->模型->視圖的流程,演變成前端請求後端接口,返回JSON的這樣一種REST架構。
spring

問題來了,咱們到底還在用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)。spring-mvc

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

SpringBoot Web Demo搭建

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

直接添加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說碼

相關文章
相關標籤/搜索