天天看點

Spring MVC framework深入總體分析

在當今的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的方法。