Spring MVC framework深入分析之一--總體分析
leopallas | 08 九月, 2005 14:54
在當今的MVC framework里,似乎Webwork2逐漸成為主流,
Webwork2+SpringFramework的組合變得越來越流行。這似乎意味著Spring自帶的MVC
framework遠比Webwork2差,所以大家紛紛用Webwork2來代替。確實,Spring的MVC
framework不算是整個Spring的核心部件,但它的威力卻超過了很多人的想象。很多人包括xiecc認為Spring的MVC
framework是非常優秀的,甚至比Webwork2更優秀。
下面列舉一下Spring的MVC framework在設計時做出的一些重要的決定,并將之和相關的MVC framework如Webwork2或struts進行對比:
一、 Spring的整個MVC配置是基于IOC容器的
與
struts或webwork2相比,這是一個ms有點奇怪的決定,看一下Spring
MVC的配置文件,最先看到的不是action或者form,而是一些有著特定名字的bean,Bean下面的配置是一些簡單或有點復雜的屬性。我們看到
的是機器更容易的數據結構,而不是人更容易理解的元素。
但是這恰恰是Spring的MVC強大的根源!因為它的配置就是Spring的核心IOC
容器的配置,這意味著所有IOC容器的威力都可以在這里展現,我們可以為所欲為地對Spring MVC進行擴展和增強,我們可以完成在其它MVC
framwork中很多難以想象的任務。想擴展新的URL映射方式嗎?要換一個themeResolver或LocalReolver的實現嗎?想在頁面
中顯示新類型的View(比如說RDF,呵呵,一個小秘密:xiecc是研究語義網的,雖然成天不務正業,不寫論文,只寫八卦)?甚至想直接在
Controller里定義AOP嗎?這些對Spring的MVC來說都是小菜一碟。
我沒有仔細研究過Webwork2的擴展機制,我知道通過
Webwork2的interceptor機制,可以進行很多的擴展,甚至有一個簡單簡單的IOC容器。但不管它有多強大,提供了多少擴展點。它的威力都
很難和真正的IOC容器相比。而struts的plugin功能則是出名的濫,雖然它也提供了plugin機制。
Spring采用IOC配置的另
一個原因是使Spring的MVC與Spring的IOC容器的整合變得非常的容易。Spring提供了與struts與webwork2的整合,但是這
樣整合都需要在進行間接的包裝,感覺總不是很自然。而且還會導致一個概念多個配置,webwork2就需要在Spring里配置bean,再配置自己的
xwork文件。想象一下吧,我們的bean直接就是一個controller,直接可以完成MVC的所有任務,這是多少爽的感覺。
Rod
Johnson采用IOC容器來實現的另一個原因是這會減少好多開發工作量。看一下urlMapping吧,它提供的property本身就是一個
HashMap,只有配置完成,我們的bean里的數據就自然存在了,哈哈,好爽吧。不用象struts那樣解析XML,再把它的內容一項一項地讀到
HashMap里。
雖然這樣的配置會有點怪異,但假如我們對Spring的IOC容器非常熟悉的話,會發現它非常的親切,也非常的簡單。
最
后是一個簡單的小秘密,Spring怎么知道某個bean的配置就是urlMapping?另一個bean的配置就是viewResolver?其實很簡
單,把所有的bean全部讀到內存里,然后通過bean的名字或類型去找就行了。通過名字去找就是簡單的getBean方法,通過類型去找則使用了
BeanFactoryUtils.beansOfTypeIncludingAncestors的靜態方法。
二、 Spring提供了明確的Model, View概念和相應的數據結構
在Spring里有一個有趣的數據類型叫做ModelAndView,它只是簡單地把要顯示的數據和顯示的結果封裝在一個類里。但是它卻提供了明確的MVC概念,尤其是model概念的強化,使程序的邏輯變得更清晰了。
記
得以前在Struts里寫程序里的時候,為了顯示數據經常自己把東西放到HttpSession或HttpServletRequest里(或set到
form里,雖然不太有用),這造成了model概念的模糊,而且也導致了struts與JSP頁面的緊耦合。假如我們要替換成Veloctiy,就得另
外加一個plugin,因為在velocity里數據是不需要不放到request里的。
Webwork2里強調的是與Web
framework解耦和它的command模式的簡單性,因此在它的action里只有簡單的get或set方法,假如返回數據,也只是簡單地返回一個
String。當然這樣的實現有它的好處,但是它淡化了model和view的概念。Rod
Johnson認為Webwork2里的Action同時包含了Action和Model的職責,這樣一個類的職責太多,不是一個很好的設計。當然
Jason
Carreira不太認同這種觀點,因為Action里的model對象完成可以delege給其它對象。但不管怎樣,這種爭論的根源在于
Webwork2里淡化了model, view甚至web的概念。仁者見仁,智者見智,最后的結果還是看個人喜歡好吧。
三、 Spring的Controller是Singleton的,或者是線程不安全的
和
Struts一樣,Spring的Controller是Singleton的,這意味著每個request過來,系統都會用原有的instance去處
理,這樣導致了兩個結果:我們不用每次創建Controller,減少了對象創建和垃圾收集的時間;由于只有一個Controller的
instance,當多個線程調用它的時候,它里面的instance變量不是線程安全的。
這也是Webwork2吹噓的地方,它的每個
Action都是線程安全的。因為每過來一個request,它就創建一個Action對象。由于現代JDK垃圾收集功能的效率已經不成問題,所以這種創
建完一個對象就扔掉的模式也得到了好多人的認可。Rod Johnson甚至以此為例證明J2EE提供的object pool功能是沒多大價值的。
但
是當人們在吹噓線程安全怎么怎么重要的時候,我想請問有多少人在多少情況下需要考慮線程安全?Rod
Johnson在分析EJB的時候也提出過其它問題,并不是沒有了EJB的線程安全魔法,世界就會滅亡的,大多數情況下,我們根本不需要考慮線程安全的問
題,也不考慮object pool。因為我們大多數情況下不需要保持instance狀態。
至少我寫了那么多的struts
Action,寫了那么多的Spring
Controller,幾乎沒有碰到需要在instance變量保持狀態的問題。當然也許是我寫的代碼不夠多,Struts的設計者Craig R.
McClanahan曾經說當時他設計struts時有兩個條件不成熟:當時沒有測試驅動開發的概念;當時JVM的垃圾收集性能太次。假如現在重新設計的
話,他也會采用每個request生成一個新對象的設計方法,這樣可以解決掉線程安全的問題了。
四、 Spring不象Webwork2或tapestry那樣去隱藏Servlet相關的元素如HttpServletRequest或HttpServletResponse
這
又是一個重要的設計決定。在Webwork2里我們沒有HttpServletRequest或者HttpServletResponse,只有
getter,
setter或ActionContext里數據,這樣的結果導致一個干凈的Action,一個與Web完全無關的Action,一個可以在任何環境下獨
立運行的bean。那么Webwork2的這樣一個基于Command模式的Action究竟給我們帶來了什么?我想主要有兩點:
1、 它使我們的Action可以非常容易地被測試。
2、 用戶可以在Action里添加業務邏輯,并被其它類重用。
然
而仔細跟Spring比較一下,我們就會發現這兩點功能所帶來的好處其實并不象我們想象的那么顯著。Spring的Controller類也可以非常輕松
被測試,看一下spring-mock下面的包吧,它提供的MockHttpServletRequest,
MockHttpServletResponse還有其它一些類讓測試Controller變得異常輕松。再看一下Action里的業務邏輯
吧,Jason
Carreira曾經說我們可以盡情地在Webwork2的Action里加業務邏輯,因為Action是不依賴于Web的。但是有多少人真正往
Action里加業務邏輯的?大多數人都會業務邏輯delegate給另一個Service類或Manager類。因為我們很清楚,往Action里加業
務邏輯會使整個體系的分層架構變得不清晰,不管怎樣,Web層就是Web層,業務層就是業務層,兩者的邏輯混在一起總會帶來問題的。而且往Action里
加業務邏輯會使用這個Action類變得龐大,Webwork2的Action是每個request都創建實例的,盡管帶來的性能影響不太大,但并不表示
每次都要把業務邏輯再new出來,業務邏輯在大多數的情況下應該是單例的。
不把request和response展現給用戶當然還會帶來功能上的
損失,也許一般的場合,用用webwork2提供的接口已經足夠了,但有時我們必須要知道request和response才能發揮出更大的威力。比如我
以前的一個項目里有一個通過遞歸動態生成的樹狀結構的頁面,在jsp頁面上顯示遞歸是痛苦或不可能的,因此我用response直接write出頁面,這
在spring里很easy,但在webwork里可能比較難了(偶不敢肯定,偶研究得不夠深,也許高手是有辦法的)。
五、 Spring提供了不錯但不夠充分的interceptor機制
回頭看一下struts,它在架構里甚至沒有給我們提供hook point的機會,我們沒有任何機會加入自己的interceptor。我們只能通過重載struts的RequestProcessor類來進行一點有限的擴展。
到
了Webwork2,似乎interceptor一下子成了整個Framework的核心,除了Action的核心部件,其它所有的東西都是
interceptor。它的超強的interceptor功能使們擴展整個架構變得非常方便。有人稱這種interceptor為AOP,Jason
Carreira則自豪地宣稱這個叫做pragamtic
AOP。我不認同這是AOP,它只是簡單的interceptor機制。但不管如何,它的interceptor確實有強大的功能。
Spring
也提供了它的interceptor機制,它的HandlerInterceptor三個interceptor方法:peHandle,
postHandle, afterCompletion。分別對應Controller執行前,Controller執行后和page
render之后。雖然大多數情況下已經夠用,但是從功能上來說顯然它沒有Webwork2強大。從AOP的角度來看,它沒有提供around
interceptor,而只有before與after
interceptor。這意味著我們無法在interceptor前后保持狀態,最簡單的情況假如我們要計算一個Controller的執行時間,我們
必須在執行完before后把begintime這個狀態保持住,再在after里把它調出來,但是顯然這個狀態保持會是個問題,我們不能把它放到
instance變量里,因為interceptor不是線程安全的。也許通過ThreadLocal可以解決這個問題,但是如此簡單的功能要用到這樣的
方法來處理,顯然這個Interceptor本身設計上還是有點問題的。
六、 Spring提供了MultiActionController,使它可以在一個類里包含多個Action
這
個設計和struts的DispatchAction有點類似,只不過提供了更靈活的機制。當我們的項目變大的時候,把功能類似的方法放到同一個
Action里完全值得的!Webwork2缺少這樣的機制。假如看一下Spring的源代碼,會發現其實實現
MultiActionController的工作量相當的少,只不過是用反射機制把解析出來的方法名執行一下就完事了。其實Webwork2也完全可以
提供這樣的機制。雖然從設計上來說確實不是很優雅,但是它確實很有用。
七、 Spring提供了更多的選擇方式
看看Spring里
提供的Controller吧,它提供了好多不同的Controller類。要生成Wizard嗎?要專門用于提交form的Controller嗎?要
執多個方法的類嗎?Spring提供了豐富的子類來擴展這些選擇。當然我們還可以很輕松地自己擴展這些功能。
再看看Spring的
ViewResolver吧,
它提供了無數不同類型的ViewResolver。更重要的是我們自定義我們的頁面映射方式。看看strtus,看看webwork2,都會存在頁面與
forward
name的一層間接轉換,我們必須在配置文件里配置好某個字符串(典型的是success)對應的是那個頁面。但是Spring里我們有了更大的自由度,
我們可以采用webwork2的策略,也可以采用更簡單的策略,如將JSP文件名去掉擴展名的映射方法。也許有人認為這種映射方式很幼稚,但是我覺得它是
非常有用的方式,即使在大項目里。
還有新的擴展嗎?看看Spring Web Flow吧,它是SpringFramework的子項目。它為一長串的基于頁面流的Wizard頁面提供了可配置的實現方式。在Spring 1.3里,它將是SpringFramework的一部分。
八、 Spring的tag
盡
管Spring的tag數量上少得可憐,但它卻是精心設計的。它的目標很簡單:讓美工可以輕松地編輯頁面。因為在Spring的頁面里Text仍然是
Text,checkbox仍然是CheckBox,而不象在struts或webwork2中的Tag。它只是用Springbind對輸入內容進行了
一下包裝。所以盡管頁面顯示代碼上會比Webwork2多,但這絕對是有價值的。
在接下來的幾章里,我會分析一下Spring是如何讓我們的Web應用不需要知道ApplicationContext就能夠訪問IOC容器的,然后會對Spring的設計和執行過程進行簡單的源碼分析,然后給出幾個擴展Spring MVC的方法。
Spring MVC framework深入分析之二--ApplicationContext之謎
leopallas | 09 九月, 2005 20:25
假如我們在寫一個基于Spring的普通應用程序,不管我們用了多么精妙的設計模式,進行了如何巧妙的設計,我們必須在某個地方執行這樣的代碼:
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
new String[] {"applicationContext.xml", "applicationContext-part2.xml"});
appContext.getBean("…");
也
許這樣的代碼算不上丑陋,但是它無疑破壞了程序的純潔性和透明性。我們的應用程序開始顯式地依賴SpringFramework,我們必須清楚地知道
Spring的配置文件有哪幾個,每個配置文件的加入或修改源代碼,我們必須在某些代碼模塊里調用丑陋的getBean方法來創造對象。
但
是所有的這些丑陋的事情似乎在我們的Web應用程序里消失啦,所有的代碼都是那么干凈,只有簡單的get與set及接口之間的調用,我們不需要知道
ApplicationContext,我們甚至不需要知道Spring。但是我們所有的對象卻又是通過Spring的
ApplicationContext來創造的!
看上去似乎很神奇,但是假如我們稍微思考一下,就會發現這是一件合情合理又如此簡單的事情,呵呵,只有第一個想到這個方法的人才是偉大的。讓我們仔細想一下普通應用程序和Web應用程序的最大區別在哪里?
其
實真正的區別只有一個,普通應用程序是一個主動執行的程序,而Web應用程序卻是被動的組件。這意味著Web應用程序無法自己主動去生成自己的線程去執行
某項任務,而必須借用Web容器中的一個線程。想象一下一個簡單的任務:我們想每隔一段時間執行一個任務,比如說在Console里打印出一行文字。在我
們的Web應用程序里應該怎么完成?在我不知道Servlet
Listener或Spring里提供的Schedule之前(其實Spring就是利用Servlet Listner初始化Application
Context時啟動schedule的),這么簡單的任務在一個Web應用程序里竟然是不可想象。還記得我當時采用的是最傻的做法:寫了一個單獨的應用
程序,在這應用程序的main函數里啟動了timetask。
但是如果換一種角度來看,整個Web應用程序生活在容器里也給我們帶
來了額外的好處,當我們讓出了對應用程序的控制權之后,我們可以讓容器幫我們完成很多本來很難處理的事情。其實IOC容器的真正作用也在于此,當我們把我
們的對象創建工作移交給IOC容器之后,我們發現整個程序變得如此清晰,如此透明,對象之間的關聯、哪些類需要事務處理或AOP功能、哪些類要遠程訪問,
所有這些復雜的事情在我們的程序里都不見了,我們只看到了簡單的get和set。
也許廢話太多,但我覺得經過這樣分析,其實
ApplicationContext之謎已經不再是謎了。真正的關鍵在于當我們的Web應用程序是被動的組件時,它除了可以錯用容器的線程之外還可以錯
用其它一些東西。我們可以讓容器來幫我們創建ApplicationContext,然后把它放在某個地方,然后在需要使用時讓容器從這個地方把
ApplicationContext讀出來,并執行相應的Controller就可以了。
這個"某個地方"就是ServletContext,而這個創建ApplicationContext的地方就是Servlet Listner,而取到ApplicationContext的地方是我們的DispatcherServlet。
仔
細想一下,其實Web服務器并沒有什么了不起的地方,它只是一個Java程序,它只是會在啟動的時候去ClassLoad某些指定文件夾下的lib或
classes,它會讀某個在WEB-INF下面一個叫做web.xml的配置文件,再做一些初始化工作。Servlet
Listener就是這個初始化工作的重要一步,服務器會讀出web.xml里配置好的所有listner,然后調用每個Listner的
contextInitialized方法(它還會去調每個Servlet的init方法,不過把初始化方法寫在Listner里才是天經地義的)。哈
哈,這也正是Spring
MVC創建ApplicationContext的最好時機,當我們在web.xml里配置好ContextLoaderListener的時
候,Spring就完成了ApplicationContext的創建過程,如果有人想研究源代碼的話可以去看一下,不過這個創建過程并不象想象中的那么
有趣,只是通過Class.forName和BeanUtils.instantiateClass創建出一個
WebApplicationContext,然后再讀了一下IOC容器的配置文件。
接下來的一個問題是我們要把創建的
ApplicationContext放在哪里?答案是ServletContext,其實沒必須對ServletContext進行深究,它只是可以一
個可以全局存放Web應用程序的場所,我們只要想象成一個全局的HashMap就可以了,我們可以要把它put進去,就可以在Servlet或其它地方把
它get出來。
Web服務器還要干的一件事件當然是在某個request到來時,它會啟動一個單獨的線程(這也是為何
Webwork可以把Context放到ThreadLocal里的原因),根據web.xml里的配置和request的URI匹配去執行相應的
Servlet。由于Servlet可以很輕松地讀到ServletContext,當然也可以很輕松地讀到ApplicationContext啦。接
下來的事情就比想象中要簡單啦,經過一些準備工作之后ApplicationContext中的URLMapping里配置好的某個
Controller,執行一下再rend某個view就可以了。其實struts或webwork2的執行過程也是如此,所以MVC
framwork分析透了其實真沒什么了不起,遠比O/R
Mapping或其它的framework簡單。雖然MVC的執行過程如此簡單,但是我們還需要了解一些細節上的事件,所以讓我們下次來討論一下
Spring MVC framework的執行過程吧。
Spring MVC framework深入分析之三--執行過程
leopallas | 10 九月, 2005 22:24
其實每個MVC framework
的執行過程都是大同小異的,當個request
過來時,它都通過一個Servlet
來響應request
,再根據request
的路徑名和配置將這個request dispatch
給一個Controller
執行,最后將之返回配置文件里對應的頁面。在Spring MVC
里,這個Servlet
的名字叫DispatchServlet
。稍看一下它的源碼會發現這是一很簡單的類。
下面是DispatchServlet的類圖:

簡單吧,這是典型的Template Method模式。每個類都會完成一些自己的本職工作,把不屬于自己的工作延遲到子類來完成。這些類的子職責在下面會有分析。其實整個SpringFramework用的最多的模式就是Template Method(Strategy也挺多,呵呵),也許任何Framework用的最多的都是Template Method模式。Why?看看Expert One on One J2EE Design and Development吧,至少Template Method和Strategy模式的分析這本書甚至比Head first Design Pattern還好。
我們先來看DispatchServlet的初始化執行過程分析吧:
我們知道每個Servlet在Web服務器啟動時都會有一個初始化的機會,這就是Servlet的init過程,這是配置Servlet的最好機會。我們可以在這個階段干些啥事情呢?
1、把初始化的那些init-param讀到Servlet的屬性里。我們知道Servlet的init-param是放在ServletConfig里的,我們可以用循環去取這些屬性。但是每次都這么干實在太累了,干嗎不把在Servlet里增加幾個property,再這些init-param直放到Servlet的property里呢?呵呵,以后那些初始參數都可以直接拿來用啦,真方便。DispatchServlet的一個祖先類叫做HttpServletBean就是專門干這個的。以后假如我們要寫自己的Servlet也可以直接繼承HttpServletBean這個類,這樣讀ServletConfig的操作都省掉了,哈哈!
2、從ServletContext里取出ApplicationContext,并擴展成自己的ApplicationContext.
在ApplicationContext之謎里我們已經提到我們可以用Servlet Listner執行的機會把ApplicationContext放到ServletContext中去。但是不是直接拿這個ApplicationContext就足夠了呢?no。我們先問一個簡單的問題吧:在Spring MVC里我們是不是只能配置一個Servlet呢?Struts就是那么干的,所以在Struts里所有的request過來都會交給一個Servlet去處理。但是在Spring MVC里,我們卻可以有好多個Servlet!它們可以處理不同類型的request,而且更重要的是它們的ApplicationContext不是相同的,它們共享了一個父ApplicationContext,也就是從ServletContext里取出來的那個,但是它們卻會根據自己的配置作擴展,形成這個Servlet特有的ApplicationContext。這個子的ApplicationContext里有自己的namespace,也就是將一個叫做(假如servlet名稱叫xiecc) xiecc-servlet.xml的配置文件讀進來,行成一個自己的ServletContext。所以這些過程全是在DispatchSevlet的一個父類FrameworkServlet里干的。
3、初始化接來要干的一件最重要的事情是初始DispatchServlet里的接口。如果用IOC容器的角度來說,其實是將ApplicationContext里定義好的接口注入到一個叫做DispatchServlet的bean里,只不過這個注入過程是手動的。注入的代碼大致如果下(部分角色的含義和作用以后會解釋):
initMultipartResolver();
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initViewResolvers();
每個init方法其實是屬性設置的過程,因為我們可以拿到自己的ApplicationContext,所以這一切都變得很輕松啦。比如說multipartResolver,直接用getWebApplicationContext().getBean(MULTIPART_RESOLVER_BEAN_NAME);就能拿到了。
不過很多東西在配置文件里是不需要寫的,比如說multipartResolver,如果在xml里取不到,Spring會初始化默認的MultipartResolver。
接下來我們分析一下DispatchServlet怎么處理Request的執行流程吧:
看一下DispatchServlet的源碼會發現它出奇的簡單。簡單的原因是類Template Method的Strategy模式,呵呵,這是我取的一個怪名字。因為DispatchServlet是不負責任何具體的操作的,它將具體的操作都delegate給了相應的接口,這是典型的Strategy模式。但是DispatchServlet里的doDispatch方法卻控制了執行流程,所有的request過來,我們都會按這樣的一個流程處理,這和Template Method里的由父類控制流程不謀而合。所以它仍然是Strategy模式,只不過主類還多了個控制流程的職責。
所以DispatchServlet本身的doService方法只是負責控制執行流程,而將所有具體的實現細節全都delegate給相應接口,難怪會那么簡單。整個流程也異常的簡單,我們甚至找不到循環跳轉,所有的東西都是一直線下來的,撇開一些細節不說,剩下的就是這以幾步了:
序號
|
步驟
|
具體內容
|
Delegate給誰干?
|
1
|
準備工作
|
設置theme, locale之類的東西??纯?/span>Request里是不是要上傳文件,如果需要就轉成MultipartRequest。
|
|
2
|
從request中取到HandlerExecutionChain
|
這個過程其實是提交給配置文件里定義的HandlerMapping 來做。HandlerExecutionChain是一個很有趣的東西,它包括就是我們要執行的Controller和一組interceptor,我們把它想象成一個執行單元就行啦。(細節上略有不同,下面會有分析)
|
HandlerMapping
|
3
|
執行HandlerExecutionChain
|
其實HandlerExecutionChain里已經包括了要執行的一切東西啦,我們只要把它分解開來執行就行啦。這有點象AOP或Webwork的執行方法pre interceptorsàControlleràpostInterceptors。
|
HandlerExecutionChain,它再delegate給一組interceptor和一個Controller
|
4
|
執行完的結果拿去顯示,也就是所謂的render
|
通過ViewResolver的轉換后,最后我們會delegate給一個View來完成最后的任務
|
ViewResolver
View
|
5
|
Render完后還有interceptor
|
Spring提供了三個interceptor的機會,前兩個Controller方法執行前后
|
HandlerExecutionChain里的interceptor
|
OK.是不是寫得很亂?我自己都覺得慚愧啦,沒辦法,只好讓我們再回頭分析一下我們碰到幾個角色吧:
1、HandlerMapping
HandlerMapping這個接口的定義非常簡單:
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
不就是根據request的URL path來取得相應的HandlerExecutionChain。
例如:我的URL是http://localhost:8080/blog/xiecc.htm
RequestURL的字符串一截,拿到了”/xiecc.htm”,再去每個HandlerMapping里一查(還記得初始化時我們已經將所有的HandlerMapping都從配置文件里注入進來了吧),假如我們在某個SimpleUrlHandlerMapping里找到了”/xiecc.htm”,我就立刻可以拿到它對應Controller和一組intercetpors了,拿過來組裝一下就是一個HandlerExecutionChain啦。
下面是HandlerMapping的類圖:

看上去有點麻煩,其實挺簡單,具體的類我就不分析啦。它的核心是:it’s all about HashMap。
還記得我們在Spring MVC最常用的HandlerMapping嗎?是SimpleUrlHandlerMapping,我們在配置它的時候,最核心的結構就是HashMap,哈哈!東西都在HashMap里,只要通過URL分析找到HashMap的key,比如說”/xiecc.htm”,用個get方法不就啥都取到了。
在Spring的配置文件里我們可以配置多個HandlerMapping,它會一個一個去找到的,直到找到跟URL匹配的那個Controller,要不然就返回null啦。
2、HandlerExecutionChain
我前面說了HandlerExecutionChain就是一個Controller和一組interceptors。這是我們執行一個request最基本的單元啦。
不過現實情況會稍有些出入,HandlerExecutionChain實際上包括的一個Object和一組interceptor。這個Object是Adaptor,它可以是Controller的Adaptor,也可以是其它類的Adaptor。但現實中我們一般用到的都是Controller,因此不詳細分析啦,這里用了Adaptor后大大降低了代碼的可讀性,來換取與Controller非緊耦合的靈活性。至少我現在認為這樣做不是太值。
3、Controller
Controller是Spring MVC執行的核心單元,也是程序員需要自完成的重要部分。用過Spring MVC的人應該都對它非常熟悉啦。所以不做太具體的分析。以下是它的類圖:

看一下類圖就知道啦,這又是Template Method的典型應用。Controller最大的優勢也正是利用Template Method,把Controller分解成不同功能的子類。想要把request里的東西populate到一個bean里嗎?直接繼承SimpleFormController就行啦。想要在Controller里寫多個方法嗎?用MultiActionController。這些Controller設計得面面俱到,但因為Controller的類層次太多,有的人會覺得煩。呵呵,隨個人喜好啦。
不過最核心的是不管這些Controller如何千變萬化,它們都實現了統一的Controller接口,這使DispatchAction調它時候根據不需要知道Controller的細節,嗯,the power of interface。
Controller里的數據綁定也是一個值得研究的東西,挺好玩的,不過這次沒空寫啦。
4、interceptor
Interceptor的接口定義如下:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
具體的我就不展開分析啦,我們只要記住interceptor的三個hook point(在AOP里叫join point,哈哈):Controller執行前,Controller執行后,頁面顯示完成后。
5、ViewResolver
ViewResolver是一個有趣的角色,它本身完成兩個功能:一是完成了View與實際頁面名稱對應關系的配置,二是View的工廠(這可是標準的工廠模式啊,每個ViewResolver負責生產自己的View)。
以下是ViewResolver接口的定義:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
用過Spring MVC的人都配置過ViewResolver,因此這里不詳細展開。
我將它對屬性分成兩類:一類是頁面文件配置,包括prefix, suffix;另一類是作為view的工廠注入到View里的屬性,如ContentType之類的。
以下是ViewResolver的類圖:

6、View
View是真正負責顯示頁面的地方。它的接口如下:
public interface View {
void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
其實這是一個簡單任務,給我一個HashMap,再給個頁面URI,把這個頁面顯示出來還不是小Case。不過不同的View顯示到頁面上的區別還是挺大的,如果是JSP,我只要把HashMap里的東西填到request里,再交給RequestDispatcher來forward一下就行了;如果是Velocity,那就把HashMap里的東西填到Veloticy的Context里,再把模板生成的東西merge到response的writer里就完事了。當然還有pdf或xls的View,我還沒空研究它,哪天有興趣了再看看吧,以下是View的類圖:

寫完這篇文章后,終于明白了什么叫做眼高手低。本來很希望寫得抽象些,最后卻發現我寫的東西跟一般的流水帳式的源碼分析其實區別不太大。嗚嗚,從具體到抽象很難,再把抽象的東西轉化成具體更難,但只有經過這樣的一層轉換,我們的認識才能有很大的提高,我們的水平才能進步。