前言
最近在看Spring MVC的源碼,就把自己對MVC模式和對各種框架的實現(xiàn)的認識寫出來給大家看看,算是一個總結(jié).所以,懇請大家用懷疑的眼光來看待這篇文章,假如有認識不對的地方,麻煩指出.
MVC與WEB應(yīng)用
MVC是什么就不用我多說了.對于現(xiàn)有較成熟的Model-View-Control(MVC)框架而言,其注意的主要問題無外乎下面這些:
Model:
模型應(yīng)該包含由視圖顯示的數(shù)據(jù).在J2EE Web應(yīng)用中,數(shù)據(jù)通常應(yīng)該由普通的javabean組成.一旦一個控制器選擇了視圖,模型就要包含視圖相應(yīng)的數(shù)據(jù).模型本身不應(yīng)該進一步的訪問數(shù)據(jù),也不應(yīng)該和業(yè)務(wù)對象相聯(lián)系.
模型要解決的問題包括:
l 封裝要顯示的數(shù)據(jù)
l 我不認為模型要依賴于特定的框架
l 不一定非得是javabean
View:
視圖負責(zé)顯示出模型包含的信息,視圖不必了解控制器或是底層業(yè)務(wù)對象的具體實現(xiàn)
視圖要解決的問題包括:
l 在顯示給定數(shù)據(jù)模型的情況下顯示內(nèi)容
l 不應(yīng)該包含有業(yè)務(wù)邏輯
l 可能需要執(zhí)行顯示邏輯,比如顏色交替的顯示某個數(shù)組的各行
l 視圖最好不處理驗證的錯誤,數(shù)據(jù)的驗證應(yīng)該在由其他組件完成
l 視圖不應(yīng)該處理參數(shù),參數(shù)應(yīng)該交由控制器集中處理
Control:
控制器就好像MVC里的中樞神經(jīng),它也許會需要一些助手來幫助它比如解析視圖,解析參數(shù)等.控制器可以訪問到業(yè)務(wù)對象或者是它的代理是很重要的,比如Struts里的Action.
控制器要解決的問題包括:
l 檢查和抽取請求參數(shù)
l 調(diào)用業(yè)務(wù)對象,傳遞從請求中獲取的參數(shù)
l 創(chuàng)建模型,視圖講顯示對應(yīng)的模型
l 選擇一個合適的視圖發(fā)送給客戶端
l 控制器有時不會只有一個
現(xiàn)有的框架
現(xiàn)在已經(jīng)有很多的MVC的框架實現(xiàn).比較流行的應(yīng)該就是Struts和Webwork了
Struts
這是最流行的web框架,幾乎成為了實際上的工業(yè)標(biāo)準(zhǔn).除了上面討論的MVC模式應(yīng)該有的優(yōu)點以外.它還有如下一些缺點:
l 每個Action只生成一次,然后就被緩存起來,再次請求這個Action的時候就不會生成新的對象,而是重復(fù)使用第一次生成的對象,這就意味著每個Action必須是線程安全的
l 采用ActionForm封裝了表單數(shù)據(jù),但是卻只能對應(yīng)String類型的數(shù)據(jù), 雖然它可以使用工具Commons Beanutils進行類型轉(zhuǎn)化,但是僅僅是提供了對象級別的支持
l 嚴重的依賴于Servlet API, 測試比較困難(不過下一版Struts里的Action.execute的方法簽名講會換成execute(ActionContext actionContext),依賴也許不會那么嚴重)
l 框架本身的驗證規(guī)則比較簡單,一般都是依賴于Commons Validation進行驗證
l 想在Action前后做些處理很困難.有時甚至不得不自己去寫專門的控制器
l 由于Struts都是具體的類繼承,這樣很容易打破封裝?
l 提供各式各樣的自定義的標(biāo)簽,但是數(shù)據(jù)綁定太原始了,這樣就使頁面代碼依賴于Struts這個特定的框架,而它卻不是規(guī)范,我覺得這是很致命的
l 它太面向JSP了,盡管使用其他視圖技術(shù)是有可能的,但是使用的時候卻不是很方便
Webwork
這個框架雖然我沒使用過,但是卻一直在關(guān)注它的發(fā)展
Webwork的設(shè)計思想采用了比Struts更為聰明的一種方式,就技術(shù)角度上說比Struts要高出不少.它以Command模式為基礎(chǔ).分為Xwork和Webwork,而且框架并不依賴于Servlet API.
Xwork提供了很多核心功能:攔截器(Interceptor),運行時表單驗證,類型轉(zhuǎn)換,IoC容器等.
WebWork建立在Xwork之上,用于處理基于HTTP的響應(yīng)和請求.用Map和ActionContext封裝了Session,Application等這些Servlet對象.從而解除了和Servlet API的耦合.
但是它仍然不是完美的:
l 為每一個請求都創(chuàng)建一個Action可能有些浪費.(但是Servlet引擎也是為每個請求創(chuàng)建多個對象,但是也沒看出來對性能有多大的影響?)
l 當(dāng)項目越來越大的時候,配置文件可能會很零亂.好像它不支持多個配置文件
l 異常處理是Command模式里值得注意的問題:我們不知道某一特定命令可能會拋出什么特定的異常,所以execute()被迫拋出異常,而不論異常是運行時異常,還是已檢查異常
Spring MVC Framework的目標(biāo)
上面說了一些MVC的原理,以及現(xiàn)在主流框架的一些問題,現(xiàn)在來看Spring是如何處理的. Spring MVC框架根據(jù)不同的角色定義了很多接口,但是它最大的問題也是依賴于Servlet API
Spring MVC Framework有這樣一些特點:
l 它是基于組件技術(shù)的.全部的應(yīng)用對象,無論控制器和視圖,還是業(yè)務(wù)對象之類的都是java組件.并且和Spring提供的其他基礎(chǔ)結(jié)構(gòu)緊密集成.
l 不依賴于Servlet API(目標(biāo)雖是如此,但是在實現(xiàn)的時候確實是依賴于Servlet的)
l 可以任意使用各種視圖技術(shù),而不僅僅局限于JSP
l 支持各種請求資源的映射策略
l 它應(yīng)是易于擴展的
我認為評價一個框架,應(yīng)該有幾個原則
l 它應(yīng)該是易于使用的,易于測試的
Spring 易于使用嗎?我不這么覺得,尤其是它的配置文件.在最恐怖的情況下,各種業(yè)務(wù)邏輯,基礎(chǔ)設(shè)施也許會擁擠在一個配置文件里.而如事務(wù)處理這些基礎(chǔ)設(shè)施應(yīng)該是由容器管理而不是開發(fā)人員,就算把這些分開到幾個配置文件里,邏輯上雖然清晰了,但是基礎(chǔ)設(shè)置卻還是暴露在外邊
Spring易于測試嗎?對Spring進行單元測試很容易,測試起來很方便
l 應(yīng)該在多個層次上提供接口
Spring提供了很多接口,而幾乎每個接口都有默認的抽象實現(xiàn),每個抽象實現(xiàn)都有一些具體實現(xiàn),所以在可擴展性這點上Spring無疑是很優(yōu)秀的
l 框架內(nèi)部和框架外部應(yīng)該被區(qū)別對待
框架內(nèi)部可以很復(fù)雜,但是使用起來一定要簡單,Spring的內(nèi)部比較麻煩,但是它很好的隱藏了這種復(fù)雜性,使用起來很舒服,比如設(shè)置一個bean的屬性.僅僅是setPropertyValue
(String propertyName, Object value)就完成,至于怎么去設(shè)置,Spring完全隱藏了這種復(fù)雜性
l 完善的文檔和測試集
這個就不用說了,老外的東西,都很完善
Spring Web框架基本流程
知道了Spring MVC框架,現(xiàn)在來看看它的流程
Spring MVC Framework大至流程如下:
當(dāng)web程序啟動的時候,ContextLoaderServlet會把對應(yīng)的配置文件信息讀取出來,通過注射去初始化控制器DispatchServlet. 而當(dāng)接受到一個HTTP請求的時候, DispatchServlet會讓HandlerMapping去處理這個請求.HandlerMapping根據(jù)請求URL(不一定非要是URL,完全可以自定義,非常靈活)來選擇一個Controller. 然后DispatchServlet會在調(diào)用選定的Controller的handlerRequest方法,并且在這個方法前后調(diào)用這個Controller的interceptor(假如有配置的話),然后返回一個視圖和模型的集合ModelAndView.框架通過ViewResolver來解析視圖并且返回一個View對象,最后調(diào)用View的render方法返回到客戶端
DispatcherServlet
這是框架的控制器,是一個具體類,它通過運行時的上下文對象來初始化.控制器本身并不去控制流程,而只是是Controller的”控制器”,他只是把處理請求的責(zé)任委托給了對應(yīng)的Controller.
控制器繼承自抽象基類FrameworkServlet,它的屬性webApplicationContext就代表著這個web程序上下文,而這個上下文對象默認實現(xiàn)就是從一個XML文件讀取配置信息(當(dāng)然也可以是其他文件格式). WebApplicationContext其實是beans包的東西,這個包提供了這個Spring整個框架的基礎(chǔ)結(jié)構(gòu),以后我會分析這個包的內(nèi)容.但是現(xiàn)在僅僅需要知道WebApplicationContext代表一個web應(yīng)用的上下文對象.
現(xiàn)在來看看DispatchServlet是如何工作的:
DispatchServlet由于繼承自抽象基類FrameworkServlet,而FrameworkServlet里的doGet(),doPost()方法里有調(diào)用serviceWrapper(),跳到serviceWrapper()里去看,結(jié)果發(fā)現(xiàn)它有把具體實現(xiàn)委托給了doService(request, response); 方法.所以現(xiàn)在已經(jīng)很清楚了, DispatchServlet真正實現(xiàn)功能的是doService() 這個方法.
特別的, FrameworkServlet的initFrameworkServlet()這個方法是控制器的初始化方法,用來初始化HandlerMappings之類的對象,這也是延遲到子類實現(xiàn)的.其實就是一個Template模式的實現(xiàn).don’t call us, we will call u.總的看來,Spring就是通過這樣來實現(xiàn)它的控制反轉(zhuǎn)的:用框架來控制流程,而不是用戶
跳到doService()一看究竟,就會發(fā)現(xiàn)真正工作的又是另一個助手函數(shù)doDispatch(request, response),沒辦法,繼續(xù)看下去,發(fā)現(xiàn)這樣兩行代碼
HandlerExecutionChain mappedHandler = null;
mappedHandler = getHandler(processedRequest, false);
看HandlerExecutionChain源碼就發(fā)現(xiàn)它其實就是對Controller和它的Interceptors的進行了包裝;
getHandler()就是從HandlerMappings(這是一個List,存放的handlerMapping對象)中取出對應(yīng)的handlerMapping對象, 每個HandlerMapping對象代表一個Controller和URL的映射(其實在運行的時候是一個HandlerExecutionChain和URL的映射,而HandlerExecutionChain對象其實就是對Controller和它interceptors的一個包裝器,可以把HandlerMapping看成Controller和URL的映射).而這個HandlerMapping是通過配置文件在運行時注射進來的,一般是SimpleUrlHandlerMapping這個子類
取得了HandlerMapping對象,繼續(xù)向下看,發(fā)現(xiàn):
if (mappedHandler.getInterceptors() != null) {
for (int i = 0; i < mappedHandler.getInterceptors().length; i++) {
HandlerInterceptor interceptor = mappedHandler.getInterceptors()[i];
if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
return;
}
interceptorIndex = i;
}
}
這里就是在調(diào)用Controller的攔截器,原理就是這句了:
interceptor.preHandle(processedRequest, response, mappedHandler.getHandler(), mv);
preHandle方法傳入了mappedHandler.getHandler()這個參數(shù)來實現(xiàn)遞歸調(diào)用!而 interceptor.postHandle方法如此一般.只不過這個方法是在handleRequest方法后調(diào)用
繼續(xù)看下去:
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
發(fā)現(xiàn)Controller的handleRequest真正的操作又被代理給了HandlerAdapter的handle方法,并且返回一個ModelAndView,我想這里增加一層的意義應(yīng)該是為了解除Controller和DispatchServlet的耦合吧.
接著就很簡單了,調(diào)用render()方法,在這個方法里面由ViewResoler解析出視圖名,再調(diào)用視圖對象的render方法把合適的視圖展現(xiàn)給用戶
到此,控制器的流程就OVER了
HandlerMapping
通過使用HandlerMapping,控制器可以用URL和某一個Controller進行標(biāo)準(zhǔn)的映射,而實現(xiàn)URL映射的具體子類的UrlHandlerMapping.
Spring還允許我們自定義映射,比如通過Session,cookie或者用戶狀態(tài)來映射.而這一切僅僅只需要實現(xiàn)HandlerMapping接口而已.不過URL映射已經(jīng)能滿足大部分的要求
Controller
Controller 類似Structs的Action, Controller接口只有一個方法handleRequest(),放回一個ModelAndView對象,如同設(shè)計目標(biāo)所說的那樣,每個Controller都是一個java組件,所以它可以在上下文環(huán)境中任意配置,組件屬性都會在初始化的時候被配置.Spring自己提供了幾個具體的實現(xiàn).方便我們使用
ViewResolver
Controller通常返回包含視圖名字而不是視圖對象的ModelAndView對象.從而徹底的解除了控制器和視圖之間的耦合關(guān)系,并且在這里還可以提供國際化的支持.
在你的配置文件中你可以:
welcomeView.class = org.springframework.web.servlet.view. InternalResourceView
welcomeView.url=/welcome.jsp
也可以
welcomeView.class = org.springframework.web.servlet.view.xslt. XsltView
welcomeView.url=/xslt/default.xslt
View
這也是一個java組件,它不做任何請求處理或是業(yè)務(wù)邏輯,它僅僅獲取模型傳遞的數(shù)據(jù),并把數(shù)據(jù)顯示出來.它里面的 render方法按照如下流程工作:
l 設(shè)置模型的數(shù)據(jù)到request作用域
l 取得視圖的URL
l 轉(zhuǎn)發(fā)到對應(yīng)的URL
總結(jié):
Spring的web框架是一個很優(yōu)秀的框架,在這里只是走馬觀花的分析了Spring的工作流程和一些關(guān)鍵的類,但是并沒有去深入的去探討它背后所體現(xiàn)的思想,還有它的優(yōu)缺點等東西.這些都等下次再說吧
這里再次說明一下,上面說的只是我自己的想法,假如有不理解或者不正確的地方,請在下面留言指出或者查看官方的<Spring-Reference>,以后會逐漸推出自己對Spring其他的理解