SpringMVC原始碼剖析(二)- DispatcherServlet的前世今生
上一篇文章《SpringMVC原始碼剖析(一)- 從抽象和介面說起》中,我介紹了一次典型的SpringMVC請求處理過程中,相繼粉墨登場的各種核心類和介面。我刻意忽略了原始碼中的處理細節,只列出最簡單的類甚至是介面類,目的就是讓大家先從最高層次的抽象意義上來審視SpringMVC這個框架;我也刻意將SpringMVC和Struts2做對比,目的是讓大家看到,SpringMVC究竟吸取了Sturts2設計思想中的哪些精華,又彌補了它的哪些遺憾。
DispatcherServlet作為SpringMVC的核心之中的核心類,再怎麼強調它的重要性也不為過。SpringMVC所有的核心類和介面,都密集地出現在DispatcherServlet的原始碼中,SpringMVC原始碼剖析,很大程度上可以說也是在剖析DispatcherServlet這一個類。這一篇文章裡,我先說幾點關於DispatcherServlet的前世今生,希望能幫助你更好的理解它。
1.對擴充套件開放,對修改封閉
SpringMVC是一個基於著名的Open-Closed,即開閉原則進行設計的框架。在Spring官方文件裡面關於SpringMVC的介紹開宗明義地進行了說明:
1
|
A
key design principle in Spring Web MVC and in Spring in general is the “Open
for
extension,closed for
modification” principle. |
開閉原則是一個很寬泛的原則,具體體現到DispatcherServlet的原始碼中,我們可以大致摸得到一些線索:
- 類中所有的變數宣告,幾乎都以介面的形式給出,並沒有繫結在具體的實現類上。
- 使用模版方法模式,在父類中對基礎行為進行定義,讓子類實現模版方法擴充套件行為。
其中第一點,在一個框架的設計中尤為重要,也是貫徹開閉原則最重要的一點。因為當你通過一些高層次的介面或者抽象類,將一個類完成的邏輯或流程編寫完成後(具體點說,是通過一個介面的引用呼叫介面方法),整個邏輯或流程的功能就被確實的在原始碼中固定下來了。可是這時,這些介面或抽象類的具體實現者是誰,還沒有固定!這就給了你的系統或框架近乎無限的擴充套件性,因為你可以任意安排和實現這些類。
我認為,物件導向設計的精髓,是對現實世界中“行為和契約”的描述。這個“行為和契約”,體現在介面和抽象類的方法宣告中。軟體設計師要用物件導向的眼光去觀察和抽象這個世界中的事物,這裡的事物可以是一些商業邏輯、可以是一些處理流程,然後用高層次的介面去描述這些行為和契約。當你在越抽象的層次上將這些行為和契約描述清楚後,你所設計的系統就是越符合開閉原則的。
SpringMVC框架在物件導向設計上,做出了絕佳的示範。它通過高度抽象的介面,描述出了一次請求處理的流程,從而讓整個框架從一開始就是符合開閉原則的。同時它也提供了這些介面的一系列預設實現類,讓你不需要很複雜的配置,就能很好的使用SpringMVC進行開發。抽象的確是個利器,但是框架絕不能執行在空中樓閣中,SpringMVC提供的的這一系列預設實現類必須要有容身之所。聰明的你可能早已想到:Spring IOC容器。這就引出了我要說的第二點。
2.配置元素的物件化
所有的框架,都需要有這樣一個功能,叫做:配置元素的物件化。因為幾乎所有的框架,都將配置元素集中到外部的xml配置檔案中,然後在框架的初始化流程中,對這些配置檔案進行解析,再變成java世界中的一個個物件供框架使用,這整個過程,可以被稱為配置元素的物件化。為什麼要有配置檔案呢?這個問題的回答也是很簡單,因為沒有人會想要使用一個配置散佈在框架中各個java類原始碼裡面的框架。框架也不允許使用者這樣子做,因為框架在釋出的時候,提供的是一個個jar包檔案,jar包內是已經編譯好的class檔案。配置檔案由使用者外部提供,框架對它進行解析,使用者能得到集中配置的好處,框架也樂於這樣子,可以說是合情合理。
那麼作為Spring產品族的新成員,SpringMVC在設計的時候,相信設計者們不做它想,這一個“配置元素的物件化”功能既然不可避免,那麼使用Spring IOC容器,通過bean配置檔案來配置SpringMVC,絕對是不二之選。不可能像Struts2一樣,內部再搞一個別的容器,因為Spring容器本身已經是被高度設計,而且已經在java世界獲得巨大成功。從推廣的角度上來說,如果對spring容器的所有知識,都可以完整的應用到SpringMVC,那麼對於開發者無疑是一個極大的吸引力。
剩下的問題就只有:到底該如何將Spring容器和SpringMVC的初始化過程整合起來呢?
答案就是WebApplicationContext介面,更具體點說,是XmlWebApplicationContext這個Spring上下文實現類。SpringMVC也使用了這一個為了將Spring容器和Web環境整合而特意設計的Spring上下文類。我們開啟WebApplicationContext的原始碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package
org.springframework.web.context; import
javax.servlet.ServletContext; import
org.springframework.context.ApplicationContext; public
interface
WebApplicationContext extends
ApplicationContext { String
ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext. class .getName()
+ ".ROOT" ; String
SCOPE_REQUEST = "request" ; String
SCOPE_SESSION = "session" ; String
SCOPE_GLOBAL_SESSION = "globalSession" ; String
SCOPE_APPLICATION = "application" ; String
SERVLET_CONTEXT_BEAN_NAME = "servletContext" ; String
CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters" ; String
CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes" ; ServletContext
getServletContext(); } |
發現它是繼承於ApplicationContext這個普通Spring容器所使用的上下文介面類,除了一些常量的宣告,只多了一個可以獲取到ServletContext的getServletContext()方法。回到上面提到的“行為和契約的描述”上,我們可以大膽的斷言,Spring容器和Web環境的整合,是在ServletContext上做文章。
開啟所有使用了Spring的Web專案的web.xml檔案,必定有這樣一段配置:
1
2
3
|
< listener > < listener-class >org.springframework.web.context.ContextLoaderListener</ listener-class > </ listener > |
1
|
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this .context); |
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值,在上面WebApplicationContext的原始碼中的第一個常量中就被宣告,是WebApplicationContext.class.getName() + ".ROOT",更直接一點,它是“org.springframework.web.context.WebApplicationContext.ROOT”。ContextLoaderListener所初始化的這個Spring容器上下文,被稱為根上下文。
SpringMVC在DispatcherServlet的初始化過程中,同樣會初始化一個WebApplicationContext的實現類,作為自己獨有的上下文,這個獨有的上下文,會將上面的根上下文作為自己的父上下文,來存放SpringMVC的配置元素,然後同樣作為ServletContext的一個屬性,被設定到ServletContext中,只不過它的key就稍微有點不同,key和具體的DispatcherServlet註冊在web.xml檔案中的名字有關,從這一點也決定了,我們可以在web.xml檔案中註冊多個DispatcherServlet,因為Servlet容器中註冊的Servlet名字肯定不一樣,設定到Servlet環境中的key也肯定不同。
由於在Spring容器中,子上下文可以訪問到所有父上下文中的資訊,而父上下文訪問不到子上下文的資訊,這個根上下文,就很適合作為多個子上下文配置的集中點。以官方文件中的圖來說明:
3.前端控制器
前端控制器,即所謂的Front Controller,體現的是設計模式中的前端控制器模式。前端控制器處理所有從使用者過來的請求。所有使用者的請求都要通過前端控制器。SpringMVC框架和其他請求驅動的表示層框架一樣,也是圍繞一個將請求分發到相應控制器的核心Servlet來設計的。DispatcherServlet和其他框架中的Servlet不一樣的地方在於,它和Spring容器無縫整合在了一起,因此你可以在SpringMVC中使用Spring容器所有的特性。
DispatcherServlet這個前端控制器,在SpringMVC中的作用,以官方文件中的配圖來說明:
整個流程可以被大致描述為:一個http請求到達伺服器,被DispatcherServlet接收。DispatcherServlet將請求委派給合適的處理器Controller,此時處理控制權到達Controller物件。Controller內部完成請求的資料模型的建立和業務邏輯的處理,然後再將填充了資料後的模型即model和控制權一併交還給DispatcherServlet,委派DispatcherServlet來渲染響應。DispatcherServlet再將這些資料和適當的資料模版檢視結合,向Response輸出響應。
可以看到Model-View-Controller這三樣東西協同合作,共同體現出MVC的設計理念,三個層次可以分別獨立演化,整個系統架構又清晰又簡潔。這是SpringMVC為我們描述的美好願景,後面我們也將看到,SpringMVC為了實現這一承諾,究竟做出了什麼樣的努力。
轉載:http://my.oschina.net/lichhao/blog/100138
相關文章
- SpringMVC原始碼剖析(三)- DispatcherServlet的初始化流SpringMVC原始碼Servlet
- SpringMVC原始碼剖析(三)- DispatcherServlet的初始化流程SpringMVC原始碼Servlet
- SpringMVC原始碼剖析(四)- DispatcherServlet請求轉發的實現SpringMVC原始碼Servlet
- SpringMVC DispatcherServlet原始碼解析SpringMVCServlet原始碼
- SpringMVC系列原始碼:DispatcherServletSpringMVC原始碼Servlet
- SpringMVC 解析(二)DispatcherServletSpringMVCServlet
- springmvc原始碼 ---DispatcherServlet 處理請求SpringMVC原始碼Servlet
- SpringMVC原始碼解析系列2-DispatcherServletSpringMVC原始碼Servlet
- SpringMVC原始碼分析2:SpringMVC設計理念與DispatcherServletSpringMVC原始碼Servlet
- 專案管理的前世今生(二)(轉)專案管理
- PYTHON編碼的前世今生Python
- MySQL 的前世今生MySql
- 遊戲的前世今生遊戲
- Mybatis的前世今生MyBatis
- IPD的前世今生
- RabbitMQ的前世今生MQ
- Serverless 的前世今生Server
- JavaScript的前世今生JavaScript
- WebP 的前世今生Web
- RunLoop的前世今生OOP
- Webpack前世今生Web
- 計算機字元編碼的前世今生計算機字元
- Unicode的前世今生Unicode
- HTTP/2.0的前世今生HTTP
- 元件化的前世今生元件化
- React ref 的前世今生React
- 外掛的前世今生
- Https的前世今生HTTP
- React Portal的前世今生React
- Android的前世今生Android
- React Mixin 的前世今生React
- SDWebImage原始碼剖析(二)Web原始碼
- SpringMVC原始碼分析3:DispatcherServlet的初始化與請求轉發SpringMVC原始碼Servlet
- Serverless For Frontend 前世今生Server
- 一、MySQL前世今生MySql
- 詳細剖析Spring Cloud 和Spring Cloud Alibaba的前世今生SpringCloud
- 資料庫的前世今生資料庫
- iOS Device ID 的前世今生iOSdev