文章已託管到GitHub,你們能夠去GitHub查看閱讀,歡迎老闆們前來Star! 搜索關注微信公衆號 碼出Offer 領取各類學習資料!前端
Spring MVC屬於SpringFrameWork的後續產品,已經融合在Spring Web Flow裏面。Spring 框架提供了構建 Web 應用程序的全功能 MVC 模塊。使用 Spring 可插入的 MVC 架構,從而在使用Spring進行WEB開發時,能夠選擇使用Spring的Spring MVC框架或集成其餘MVC開發框架。java
SpringMVC框架當然強大,可是其執行流程更是妙趣橫生。因此咱們此次要用一個簡單的例子去深究一下SpringMVC的底層執行流程!git
以下是SpringMVC的執行流程梗概圖,我會在後面的底層流程剖析中重點提到梗概圖中的這幾個零件,以及它們的做用!github
SpringMVC執行流程梗概圖(切記:該圖只是梳理思路,並不特別嚴謹,請諒解) |
---|
![]() |
既然,咱們要選擇剖析SpringMVC底層執行流程,那確定是要先分析咱們能所看到表面的MVC重要組件。這樣咱們分析完可視組件後,就能找到分析SpringMVC底層執行流程的入口,因此分析它的重要組件顯得更是重要!web
SpringMVC的重要組件是由核心的前端控制器(web.xml)
、後端控制器(Controller)
和spring-mvc.xml配置文件
組成。spring
上述得知,咱們執行流程剖析的入口既是核心的前端控制器,即web.xml
,那咱們有資格瞭解該前端控制器中配置了什麼!以下:後端
前端控制器 |
---|
![]() |
由上圖所知,前端控制器中所包含的便是同時啓動SpringMVC工廠和Spring工廠,讓兩個工廠同時運做處理請求,並做出響應。既然要剖析SpringMVC的底層執行流程,那咱們要從加載SpringMVC工廠的DispatcherServlet
提及。首先進入到DispatcherServlet中,查看源代碼全部方法,以下圖所示:設計模式
DispatcherServlet源碼全部方法 |
---|
![]() |
DispatcherServlet繼承FrameworkServlet |
![]() |
上圖所示,我進入到了DispatcherServlet中。既然說它是一個Servlet,那確定是須要尋找它的service方法,由於Service方法是Servlet的核心所在。因而我打開了IDEA的方法列表搜索service方法,未果。雖然未果,可是我發現兩個重要的線索,一是該Servlet中有一個doSerivce方法,二是DispatcherServlet繼承了FrameworkServlet,我想既然子類沒有service方法,父類確定有,因而我進入到了FrameworkServlet查看源代碼,以下圖所示:數組
FrameworkServlet源碼 |
---|
![]() |
我興沖沖在父類(FrameworkServlet)中找到了service方法,可是仍是感受高興的太早了,該service方法中除了resolve
方法獲取請求方式和processRequest
方法外,我一無所知。隨後居然發現了紅色箭頭所指向的東西super.service(request, response);
,這意味着什麼呢?這意味着它繼承了父類擁有的service方法,因而我點擊super句點後面的service方法查看源碼驚人的發現這個類居然是HttpServlet,顯然咱們找service方法的這條路走到盡頭了。在裏面有兩個方法存在一個是resolve
方法,它是獲取請求方式的。還有一個方法不知道是作什麼的,因而我點擊了進去查看源碼,以下圖所示:瀏覽器
processRequest方法源碼 |
---|
![]() |
既然咱們進去看到了processRequest方法的源碼,就要找重要的方法。何爲重要的方法呢,通常被try塊包裹的方法必然是重要方法,因而我找到了doService(request, response);
方法,並繼續點擊去看該doService方法的源碼,以下圖所示:
doService(request, response);方法源碼 |
---|
![]() |
逐漸失去耐心的我真的被驚訝到了,進入到doService方法後,也沒有跳到其餘的類中,而卻仍是在該類中跳到了一個空的doService();方法中。唉,探究究竟真的是件不容易的事情呀~我嘆了一口氣。冷靜下來一想,父類是空方法沒有實現,那核心邏輯代碼一定是在子類中了呀。這不是多態嘛!因而,我得出告終論,費勁吧難,找入口的邏輯代碼回過頭來仍是得看DispatcherServlet中的那個doService方法。此時我知道,這必將是一個漫長的探索之路。因而,我秉着探究原理的心態,再一次點進了被我錯過的那個DispatcherServlet中的doSerivce方法,以下圖:
DispatcherServlet中的doService()方法 |
---|
![]() |
既然肯定了這是探究底層原理的開始,那咱們就在doServie()方法中尋找重要的邏輯,因而我再一次的在try塊中找到了一個名爲doDispatch(request, response);
的方法(省略了前面的各類初始化和存儲域數據)。在探究底層原理的道路上,你會發現愈來愈接近真理,雖然這注定是一個漫長的探索過程,我也情願。因而,點擊進入到了doDispatch()方法中的源碼,以下圖所示:
doDispatch()方法源碼 |
---|
![]() |
走進了doDispatch()方法的源碼,才知道我沒有看錯你。裏面標有註釋的都是一些重要的執行邏輯方法。接下來咱們會一個個的分析,逐步深刻理解SpringMVC的執行流程。既然探索執行流程那就少不了Debug(Debug調試功能,Debug能很清晰的看到執行流程),因而我在getHandler()
方法的那一行打了一個斷點。下一步跟進執行流程進入到了getHandler()
方法,以下圖所示:
getHandler方法源碼(註釋解釋:爲當前請求尋找並返回一個handler對象) |
---|
![]() |
斷點停留到了這一行,由於getHandler()
的名字,顧名思義就是獲取Controller層中的Handler。它是怎麼獲取到的呢?咱們在斷點的變量顯示框中,看到handlerMappings是一個數組,其中有三個對象。他們能夠分別以不一樣的方式處理不一樣的Handler,其中咱們能夠點擊這個三個對象,一一把其對象展開查看重要屬性,以下圖所示:
0 = {RequestMappingHandlerMapping} |
---|
![]() |
2 = {SimpleUrlHandlerMapping} |
![]() |
如上圖得知,RequestMappingHandlerMapping對象識別了咱們Controller中的@RequestMapping註解和各個Handler上方的註解路徑。SimpleUrlHandlerMapping對象識別了處理靜態資源驅動所建立的那個默認Servlet,而處理靜態資源的默認Servlet路徑給了/**
,它識別了這個路徑。HanderMapping映射器中的對象,經過註解識別獲取到了Controller層的各個Handler請求路徑註解後,就執行到了下一行,以下圖:
getHandler方法源碼 |
---|
![]() |
經過註解能夠找到全部的Handler,其中全部的Handler就存儲在handlerMappings
中,因而它就遍歷了此對象。隨後根據各自的請求對象獲取對應的Handler並判空返回獲取到的對應Handler對象。繼續向下執行,你還會發現這麼一個東西,以下圖:
getHandler方法 |
---|
![]() |
對,你會發現即將返回的Handler是一個名爲HandlerExecutionChain的執行鏈。其中執行鏈內包含了即將返回的handler對象和一個interceptorList集合,其中集合內有兩個對象,這兩個對象就是攔截器。因此,不論是你本身使用了攔截器仍是沒有使用攔截器(內部底層有攔截器),這些攔截器和handler對象會以一個鏈條的形式執行(攔截器在前,handler對象在後)。則執行過程是遵循着先執行攔截器,後返回並執行handler對象的順序。返回了HandlerExecutionChain執行鏈,那麼就要開始執行執行鏈了!問題來了,到底是誰依次執行攔截器和handler對象呢?以下圖:
doDispatch()方法源碼 |
---|
![]() |
返回執行鏈後,繼續執行就執行到了這一行代碼,其註釋解釋爲爲當前請求對象尋找一個handler適配器。若是你學過適配器設計模式也許你會更容易理解,沒有學過也沒有關係,隨後的解釋你也能夠理解的。知道了它要爲請求對象尋找適配器,那麼咱們繼續執行,就獲得了以下啊信息:
getHandlerAdapter方法源碼 |
---|
![]() |
執行流程進入到了getHandlerAdapter
方法,遠遠看到這個方法有一種似曾相識的感受,對,它和HandlerMapping映射器很像,簡直就是孿生兄弟。該方法要根據當前返回的handler對象,爲其handler對象尋找一個適配器,而handlerAdapters集合對象中就存儲着三個適配器,想一想咱們在映射器中獲取執行鏈的時候是否是也三個呢?對的,他們是成對出現的,handler的對象找其對應的適配器才能夠繼續執行下去。找到與當前handler對象成對的適配器以後,就返回了該適配器。適配器返回後中間通過了以下方法:
doDispatch()方法源碼 |
---|
![]() |
中間通過了這一段代碼,獲取了請求對象的請求方式並對此進行了一系列的判斷操做。繼續執行到了下面,下面有一個if判斷,判斷執行了applyPreHandler
方法,此方法就是攔截器的前置方法。執行完攔截器的前置方法後,繼續向下執行,這時候就該執行以下代碼:
doDispatch()方法源碼 |
---|
![]() |
今後方法可見ha
對象是此時的handler對象,說明在執行handler對象以前執行了攔截器,這也是遵循了執行鏈的順序。繼續執行下去,將完成了請求參數對象的封裝和響應中Json字符串與對象的轉換後,返回了一個mv對象。那麼mv對象是什麼呢?實際上是在上面定義的ModelAndView對象。返回mv對象後,繼續執行便執行到了以下重要的執行邏輯:
doDispatch()方法源碼 |
---|
![]() |
其中在執行過程當中,判斷並執行了攔截器的後置方法。執行完後置方法後,進行了一系列的判斷,就開始執行了processDispatchResult(processdRequest, response, mappdeHandler, mv, dispatchException)
方法,該方法中攜帶了請求對象、響應對象、handler對象、ModelAndView對象等,進入到此方法源碼中,你會發現他進行了一系列的判斷,經過以下方法對ModelAndView對象進行了渲染:
render方法源碼 |
---|
![]() |
對ModelAndView對象進行渲染和視圖解析後,繼續跟進方法,由於勝利立刻就要來臨了。以下圖:
render方法源碼 |
---|
![]() |
繼續執行,就會發現它開始經過resolveViewName
方法來解析視圖了。因而,就進入到了該方法,以下圖:
resolveViewName方法源碼 |
---|
![]() |
首先,看到此方法的源碼,你能夠發現,viewResolvers
視圖解析器會解析ModelAndView對象,並返回了一個View對象。後來View對象也會被一個名叫render
的方法渲染,以下:
view.render() |
---|
![]() |
可見,此View對象並不簡單,它執行了一番事後,因爲個人網頁跳轉時使用的請求轉發,因而就到了以下頁面源碼:
InternalResourceView源碼 |
---|
![]() |
點擊此方法就會發現咱們熟悉的請求轉發了,此時它在這裏讀取解析了spring-mvc.xml
配置文件,爲內部默認的請求轉發拼接好了路徑forward:/XXX/XXX
(此時也解析了spring-mvc.xml配置文件內的其餘組件),以下圖:
請求轉發(InternalResourceView.java) |
---|
![]() |
若是是重定向呢,那麼就是以下類中的重定向方法,以下圖所示:
重定向(RedirectView.java) |
---|
![]() |
隨後,轉發或重定向跳轉至JSP頁面(視圖層)後,渲染數據到HTML中,並渲染完HTML內容後,輸出給瀏覽器並做出響應,在瀏覽器中顯示!
此SpringMVC我以打斷點調試的方式走了一遍底層的執行流程。我相信你本身打斷點調試也會有一個不錯的收穫!
- HandlerMapping(處理器映射器)
- HandlerAdapter(處理器適配器)
- ViewResolver(視圖解析器)
上面咱們經過Debug簡單的走了一遍SpringMVC的執行流程,可是前面所說的那麼多內部組件是怎麼來的呢?因而,我從DispatherServlet找到了一個方法initStrategies
,以下:
initStrategies方法源碼 |
---|
![]() |
在執行流程開始以前,作了內部組件的一系列初始化操做,這裏咱們以initHandlerMappings
方法進行追溯,找到 SpringMVC 的默認配置文件。進入 initHandlerMappings 方法,由於咱們並無進行配置(註解或者 Bean 標籤),因此該方法中的前兩種狀況都會跳過,會來到最下面的默認狀況處,調用了 getDefaultStrategies 方法,讀取默認的配置文件。
initHandlerMappings方法源碼 |
---|
![]() |
getDefaultStrategies方法源碼 |
![]() |
在 getDefaultStrategies 方法中,有一個 defaultStrategies,咱們當該類上面看一下,以下圖:
defaultStrategies源碼 |
---|
![]() |
這裏就是進行加載默認配置文件的地方,點擊 DEFAULT_STRATEGIES_PATH 常量,找到了默認的配置配置文件。
DEFAULT_STRATEGIES_PATH常量 |
---|
![]() |
因而我想辦法翻到了這個配置文件,裏面就初始化了各類組件,你們能夠查閱:
DispatcherServlet.properties配置文件 |
---|
![]() |