- 前言
- Spring MVC 請求流程
- Spring MVC 兩大階段
- 初始化
- 處理請求
- DispatcherServlet#doDispatch
- DispatcherServlet#getHandler
- AbstractHandlerMapping#getHandler
- AbstractHandlerMethodMapping#getHandlerInternal
- AbstractHandlerMethodMapping#lookupHandlerMethod
- AbstractHandlerMethodMapping 的初始化
- AbstractHandlerMethodMapping#initHandlerMethods
- AbstractHandlerMethodMapping#detectHandlerMethods
- AbstractHandlerMethodMapping#register
- Spring MVC 兩大階段
- 總結
前言
對於 Web
應用程式而言,我們從瀏覽器發起一個請求,請求經過一系列的分發和處理,最終會進入到我們指定的方法之中,這一系列的的具體流程到底是怎麼樣的呢?
Spring MVC 請求流程
記得在初入職場的時候,面試前經常會背一背 Spring MVC
流程,印象最深的就是一個請求最先會經過 DispatcherServlet
進行分發處理,DispatcherServlet
就是我們 Spring MVC
的入口類,下面就是一個請求的大致流轉流程(圖片參考自 Spring In Action
):
- 一個請求過來之後會到達
DispatcherServlet
,但是DispatcherServlet
也並不知道這個請求要去哪裡。 DispatcherServlet
收到請求之後會去查詢處理器對映(HandlerMapping),從而根據瀏覽器傳送過來的URL
解析出請求最終應該呼叫哪個控制器。- 到達對應控制器(Controller)之後,會完成一些邏輯處理,而且在處理完成之後會生成一些返回資訊,也就是
Model
,然後還需要選擇對應的檢視名。 - 將模型(
Model
)和檢視(View
)傳遞給對應的檢視解析器(View Resolver),檢視解析器會將模型和檢視進行結合。 - 模型和檢視結合之後就會得到一個完整的檢視,最終將檢視返回前端。
上面就是一個傳統的完整的 Spring MVC
流程,為什麼要說這是傳統的流程呢?因為這個流程是用於前後端沒有分離的時候,後臺直接返回頁面給瀏覽器進行渲染,而現在大部分應用都是前後端分離,後臺直接生成一個 Json
字串就直接返回前端,不需要經過檢視解析器進行處理,也就是說前後端分離之後,流程就簡化成了 1-2-3-4-7
(其中第四步返回的一般是 Json 格式資料)。
Spring MVC 兩大階段
Spring MVC主要可以分為兩大過程,一是初始化,二就是處理請求。初始化的過程主要就是將我們定義好的 RequestMapping
對映路徑和 Controller
中的方法進行一一對映儲存,這樣當收到請求之後就可以處理請求呼叫對應的方法,從而響應請求。
初始化
初始化過程的入口方法是 DispatchServlet
的 init()
方法,而實際上 DispatchServlet
中並沒有這個方法,所以我們就繼續尋找父類,會發現 init
方法在其父類(FrameworkServlet)的父類 HttpServletBean
中。
HttpServletBean#init()
在這個方法中,首先會去家在一些 Servlet 相關配置(web.xml),然後會呼叫 initServletBean()
方法,這個方法是一個空的模板方法,業務邏輯由子類 FrameworkServlet
來實現。
FrameworkServlet#initServletBean
這個方法本身沒有什麼業務邏輯,主要是初始化 WebApplicationContext
物件,WebApplicationContext
繼承自 ApplicationContext
,主要是用來處理 web
應用的上下文。
FrameworkServlet#initWebApplicationContext
initWebApplicationContext()
方法主要就是為了找到一個上下文,找不到就會建立一個上下文,建立之後,最終會呼叫方法 configureAndRefreshWebApplicationContext(cwac)
方法,而這個方法最終在設定一些基本容器標識資訊之後會去呼叫 refresh()
方法,也就是初始化 ioc
容器。
當呼叫 refresh()
方法初始化 ioc
容器之後,最終會呼叫方法 onRefresh()
,這個方法也是一個模板鉤子方法,由子類實現,也就是回到了我們 Spring MVC
的入口類 DispatcherServlet
。
DispatchServlet#onRefresh
onRefresh()
方法就是 Spring MVC
初始化的最後一個步驟,在這個步驟當中會初始化 Spring MVC
流程中可能需要使用到的九大元件。
Spring MVC 九大元件
MultipartResolver
這個元件比較熟悉,主要就是用來處理檔案上傳請求,通過將普通的 Request
物件包裝成 MultipartHttpServletRequest
物件來進行處理。
LocaleResolver
LocaleResolver
用於初始化本地語言環境,其從 Request
物件中解析出當前所處的語言環境,如中國大陸則會解析出 zh-CN
等等,模板解析以及國際化的時候都會用到本地語言環境。
ThemeResolver
這個主要是使用者主題解析,在 Spring MVC
中,一套主題對應一個 .properties
檔案,可以存放和當前主題相關的所有資源,如圖片,css樣式等。
HandlerMapping
用於查詢處理器(Handler
),比如我們 Controller
中的方法,這個其實最主要就是用來儲存 url
和 呼叫方法的對映關係,儲存好對映關係之後,後續有請求進來,就可以知道呼叫哪個 Controller
中的哪個方法,以及方法的引數是哪些。
HandlerAdapter
這是一個介面卡,因為 Spring MVC
中支援很多種 Handler
,但是最終將請求交給 Servlet
時,只能是 doService(req,resp)
形式,所以 HandlerAdapter
就是用來適配轉換格式的。
HandlerExceptionResolver
這個元件主要是用來處理異常,不過看名字也很明顯,這個只會對處理 Handler
時產生的異常進行處理,然後會根據異常設定對應的 ModelAndView
,然後交給 Render
渲染成頁面。
RequestToViewNameTranslator
這個主鍵主要是從 Request
中獲取到檢視名稱。
ViewResolver
這個元件會依賴於 RequestToViewNameTranslator
元件獲取到的檢視名稱,因為檢視名稱是字串格式,所以這裡會將字串格式的檢視名稱轉換成為 View
型別檢視,最終經過一系列解析和變數替換等操作返回一個頁面到前端。
FlashMapManager
這個主鍵主要是用來管理 FlashMap
,那麼 FlashMap
又有什麼用呢?要明白這個那就不得不提到重定向了,有時候我們提交一個請求的時候會需要重定向,那麼假如引數過多或者說我們不想把引數拼接到 url
上(比如敏感資料之類的),這時候怎麼辦呢?因為引數不拼接在 url
上重定向是無法攜帶引數的。
FlashMap
就是為了解決這個問題,我們可以在請求發生重定向之前,將引數寫入 request
的屬性 OUTPUT_FLASH_MAP_ATTRIBUTE
中,這樣在重定向之後的 handler
中,Spring
會自動將其設定到 Model
中,這樣就可以從 Model
中取到我們傳遞的引數了。
處理請求
在九大元件初始化完成之後,Spring MVC
的初始化就完成了,接下來就是接收並處理請求了,那麼處理請求的入口在哪裡呢?處理請求的入口方法就是 DispatcherServlet
中的 doService
方法,而 doService
方法又會呼叫 doDispatch
方法。
DispatcherServlet#doDispatch
這個方法最關鍵的就是呼叫了 getHandler
方法,這個方法就是會獲取到前面九大元件中的 HandlerMapping
,然後進行反射呼叫對應的方法完成請求,完成請求之後後續還會經過檢視轉換之類的一些操作,最終返回 ModelAndView
,不過現在都是前後端分離,基本也不需要用到檢視模型,在這裡我們就不分析後續過程,主要就是分析 HandlerMapping
的初始化和查詢過程。
DispatcherServlet#getHandler
這個方法裡面會遍歷 handllerMappings
,這個 handllerMappings
是一個 List
集合,因為 HandlerMapping
有多重實現,也就是 HandlerMapping
不止一個實現,其最常用的兩個實現為 RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
。
AbstractHandlerMapping#getHandler
AbstractHandlerMapping
是一個抽象類,其 getHandlerInternal
這個方法也是一個模板方法:
getHandlerInternal
方法最終其會呼叫子類實現,而這裡的子類實現會有多個,其中最主要的就是 AbstractHandlerMethodMapping
和 AbstractUrlHandlerMapping
兩個抽象類,那麼最終到底會呼叫哪個實現類呢?
這時候如果拿捏不準我們就可以看一下類圖,上面我們提到,HandlerMapper
有兩個非常主要的實現類:RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
。那麼我們就分別來看一下這兩個類的類圖關係:
可以看到,這兩個實現類的抽象父類正好對應了 AbstractHandlerMapping
的兩個子類,所以這時候具體看哪個方法,那就看我們想看哪種型別了。
-
RequestMappingHandlerMapping:主要用來儲存
RequestMapping
註解相關的控制器和url
的對映關係。 -
BeanNameUrlHandlerMapping:主要用來處理
Bean name
直接以/
開頭的控制器和url
的對映關係。
其實除了這兩種 HandlerMapping
之外,Spring
中還有其他一些 HandllerMapping
,如 SimpleUrlHandlerMapping
等。
提到的這幾種 HandlerMapping
,對我們來說最常用,最熟悉的那肯定就是 RequestMappingHandlerMapping
,在這裡我們就以這個為例來進行分析,所以我們應該
AbstractHandlerMethodMapping#getHandlerInternal
這個方法本身也沒有什麼邏輯,其主要的核心查詢 Handler
邏輯在 lookupHandlerMethod
方法中,這個方法主要是為了獲取一個 HandlerMethod
物件,前面的方法都是 Object
,而到這裡變成了 HandlerMethod
型別,這是因為 Handler
有各種型別,目前我們已經基本跟到了具體型別之下,所以型別就變成了具體型別,而如果我們看的的另一條分支線,那麼返回的就會是其他物件,正是因為支援多種不同型別的 HandlerMapping
物件,所以最終為了統一執行,才會需要在獲得 Hanlder
之後,DispatcherServlet
中會再次通過呼叫 getHandlerAdapter
方法來進一步封裝成 HandlerAdapter
物件,才能進行方法的呼叫
AbstractHandlerMethodMapping#lookupHandlerMethod
這個方法主要會從 mappingRegistry
中獲取命中的方法,獲取之後還會經過一系列的判斷比較判斷比較,因為有些 url
會對應多個方法,而方法的請求型別不同,比如一個 GET
方法,一個 POST
方法,或者其他一些屬性不相同等等,都會導致最終命中到不同的方法,這些邏輯主要都是在 addMatchingMappings
方法去進一步實現,並最終將命中的結果加入到 matches
集合內。
在這個方法中,有一個物件非常關鍵,那就是 mappingRegistry
,因為最終我們根據 url
到這裡獲取到對應的 HandlerMtthod
,所以這個物件很關鍵:
看這個物件其實很明顯可以看出來,這個物件其實只是維護了一些 Map
物件,所以我們可以很容易猜測到,一定在某一個地方,將 url
和 HandlerMapping
或者 HandlerMethod
的對映關係存進來了,這時候其實我們可以根據 getMappingsByUrl
方法來進行反推,看看 urlLookup
這個 Map
是什麼時候被存入的,結合上面的類圖關係,一路反推,很容易就可以找到這個 Map
中的對映關係是 AbstractHandlerMethodMapping
物件的 afterPropertiesSet
方法實現的(AbstractHandlerMethodMapping
實現了 InitializingBean
介面),也就是當這個物件初始化完成之後,我們的 url
和 Handler
對映關係已經存入了 MappingRegistry
物件中的集合 Map
中。
AbstractHandlerMethodMapping 的初始化
afterPropertiesSet
方法中並沒有任何邏輯,而是直接呼叫了 initHandlerMethods
。
AbstractHandlerMethodMapping#initHandlerMethods
initHandlerMethods
方法中,首先還是會從 Spring
的上下文中獲取所有的 Bean
,然後會進一步從帶有 RequestMapping
註解和 Controller
註解中的 Bean
去解析並獲得 HandlerMethod
。
AbstractHandlerMethodMapping#detectHandlerMethods
這個方法中,其實就是通過反射獲取到 Controller
中的所有方法,然後呼叫 registerHandlerMethod
方法將相關資訊註冊到 MappingRegistry
物件中的各種 Map
集合之內:
AbstractHandlerMethodMapping#register
registerHandlerMethod
方法中會直接呼叫 AbstractHandlerMethodMapping
物件持有的 mappingRegistry
物件中的 regidter
方法,這裡會對 Controller
中方法上的一些元資訊進行各種解析,比如引數,路徑,請求方式等等,然後會將各種資訊註冊到對應的 Map
集合中,最終完成了整個初始化。
總結
本文重點以 RequestMappingHandlerMapping
為例子分析了在 Spring
當中如何初始化 HandlerMethod
,並最終在呼叫的時候又是如何根據 url
獲取到對應的方法並進行執行最終完成整個流程。