關(guān)于Spring MVC的使用,事實上已足夠簡單,《Spring 3.x企業(yè)應(yīng)用開發(fā)實戰(zhàn)》和《Spring實戰(zhàn)》也說得足夠好,尤其是前者。不多說。聊聊表現(xiàn)層的話題吧,如對本篇內(nèi)容感興趣,請讀《Struts2技術(shù)內(nèi)幕》,是的,你沒看錯,一個我自己都不再推薦使用的框架,我也當(dāng)然不會為它寫一篇,但這書,值得讀,當(dāng)年曾震得我驚如天人。
說到表現(xiàn)層,繞不過MVC。
落花散人《老子集注》:
在無數(shù)凡夫俗子的喋喋不休之下,大圣人原本清晰的概念變得模糊不清!
這是一切流行詞語的宿命:它太流行了,就無論什么人都來說一番,說來說去概念就不清了。
所以討論之前做出以下設(shè)定:
1.本文中說的模型,指的是一種有狀態(tài)無行為的對象,是用于承載業(yè)務(wù)邏輯結(jié)果數(shù)據(jù),準(zhǔn)備被渲染成頁面的東西,其后綴,可能是entity/bean/dto/vo等。
2.本文中說的頁面,指的是模型被灌到模板中后渲染出的結(jié)果,一般情況下是個html片段(也包括ajax或rest api的json響應(yīng),但不是本文重點)。
3.本文中說的控制器,指的是請求進(jìn)、響應(yīng)出的黑箱中,最上層的那個東西,負(fù)責(zé)接收請求和給出響應(yīng),但具體業(yè)務(wù)邏輯它交給業(yè)務(wù)層去處理。
4.本文中說的模板,指的是定義了頁面格式的那個東西,可能是標(biāo)簽化的jsp或freemarker文件等。
5.本文中說的請求,指的是HttpServletRequest表示的網(wǎng)絡(luò)請求,主要關(guān)注點包括路徑和參數(shù)。
6.本文中說的響應(yīng),指的是HttpServletResponse表示的網(wǎng)絡(luò)響應(yīng),性質(zhì)為把頁面給還到瀏覽器(也包括給還到其他http請求發(fā)起者,如移動端的rest請求,但不是本文重點)。
概念都很容易理解,那就從控制器具體說說。
這是上面少有的沒有舉例的東西,因為它的工作職責(zé)最多。
還是把它拆開看吧,既然「請求進(jìn)響應(yīng)出」,而「請求的主要關(guān)注點是路徑和參數(shù)」,那控制器就該承擔(dān)兩個職責(zé):
1)處理路徑
2)處理參數(shù)
處理路徑在實踐中常常就是:
1)把這個路徑的請求,映射給哪個方法?
——在servlet里是servlet-mapping,在種種框架中就是xml或annotation或約定優(yōu)于配置的設(shè)定。
2)把該方法的結(jié)果,灌裝到哪個模板,來生成結(jié)果頁面?
——在servlet里是forward,在種種框架里還是xml或annotation或約定優(yōu)于配置的設(shè)定。
處理參數(shù)在實踐中常常就是:
1)怎樣想辦法盡量簡單地把參數(shù)給到那個方法的手里?
——在servlet里是getParameter,在Struts1中是ActionForm,在Struts2中是Action屬性,在SpringMVC中是方法參數(shù)。
所以,本文中控制器的概念的組成包括請求分發(fā)器、響應(yīng)渲染器、表現(xiàn)層方法三部分。
請求分發(fā)器和響應(yīng)渲染器的代碼由框架提供,配置由程序員進(jìn)行,它們聯(lián)手為表現(xiàn)層方法掃清外圍的嘈雜,使表現(xiàn)層方法得以相對簡單地干正事。
而我們知道,干正事其實是業(yè)務(wù)層的職責(zé)……
所以表現(xiàn)層方法就很清閑,方法體大約是調(diào)用一下業(yè)務(wù)層,俗稱「表現(xiàn)層是個薄層」。
但表現(xiàn)層方法原本不薄。它原本應(yīng)當(dāng)做4件事:
1)獲得參數(shù),即request.getParameter()
2)執(zhí)行邏輯,即service.xx()
3)設(shè)置模型,即request.setAttribute()
4)渲染頁面,即request..forward()
但框架既然職責(zé)在于「讓程序員專注于業(yè)務(wù)」,就當(dāng)然該降低1) 3) 4)的復(fù)雜度。
對1)獲得參數(shù),框架的想法就是把參數(shù)綁定到一些容易被表現(xiàn)層方法處理的什么東西上,較先進(jìn)的是方法參數(shù)。
對3)設(shè)置模型,框架的想法就是讓程序員可以輕松地把數(shù)據(jù)放到什么地方,放到這個地方就相當(dāng)于放進(jìn)了模型里,較先進(jìn)的是方法返回值。
對4)渲染頁面,框架的想法就是讓響應(yīng)渲染器做這事,程序員盡量輕松地給響應(yīng)渲染器提供必要信息即可,較先進(jìn)的是基于約定,根據(jù)表現(xiàn)層類和方法名尋找相應(yīng)路徑的模板(類和方法名的約定同時也是指定請求分發(fā)的路由)。
于是,表現(xiàn)層方法的方法名、參數(shù)、返回值都分擔(dān)掉了責(zé)任,方法體就可以只調(diào)一句service.xx()就行了……
所以我覺得,SpringMVC那個Controller,配不上「控制器」這么八荒六合唯我獨尊的霸氣名字。
你一個只有方法沒有屬性的單例對象,每個方法體又只包括一行語句,如果不是托了Java是個面向?qū)ο笳Z言的福,這些方法完全可以散落出去,各自承擔(dān)責(zé)任,還要你包袱皮何用?
別說,之前還真散落過……Servlet就是每個控制器處理一個請求,Struts2的初衷也是。
但Java這熊孩子,在lambda橫空出世之前,每個方法都必須身處類之中(導(dǎo)致了各種各樣的設(shè)計模式的出現(xiàn),最典型的就是策略模式,實踐中常表現(xiàn)為令人聞風(fēng)喪膽的匿名內(nèi)部類),結(jié)果用Servlet時web.xml膨脹不堪。
Struts2實踐中更是完全違反了「命令模式」「面向?qū)ο蟆沟某踔?,一個類里負(fù)責(zé)各個業(yè)務(wù)的屬性都有,別提多亂了……
想想也是,你一個方法就一條語句,還要獨占一個類,委實不能再過分。
大家叫苦不迭,直到有了SpringMVC這個尤物,大家把各個模塊的表現(xiàn)層方法歸攏歸攏,放到一個類里,減輕配置和約定的負(fù)擔(dān)。
這樣一來,也可以用類名稱和方法名稱,分別匹配模板所存在的文件夾名稱和模板文件名稱,以方便響應(yīng)渲染時模板的指定。
類名稱匹配文件夾名稱……
我們可憐的Controller,終于其性質(zhì)不幸從一個類,墮落成了一個package……
其實,其他語言的主流MVC框架,也都是這么回事。
處理請求的根本載體是方法,映射請求的根本性質(zhì)是框架把請求映射到方法。
類夾在框架和方法之間,上不上下不下,高不成低不就,當(dāng)然只能淪落成package,美其名曰「表現(xiàn)層組件」。
Struts2還曾想扶它一把,給它點屬性爽爽,大家表示,實在扶不上墻,你還是當(dāng)無狀態(tài)單例吧。
為什么會這樣捏?
因為,處理請求給出響應(yīng)這件事,根本就是,面向過程的。
所以,表現(xiàn)層方法,理所當(dāng)然,應(yīng)當(dāng)是一等公民。
而Java,沒給它這個機會。
它只好,把類架空。
偉大的Spring MVC,終于讓我們看清了這一點。
請求進(jìn)、響應(yīng)出的責(zé)任,經(jīng)過層層包裹,終于成了參數(shù)進(jìn),返回值出。
這是如此理所當(dāng)然和天經(jīng)地義。
脫離了對象的方法是什么——是函數(shù)。
函數(shù)是什么——對一組給定的自變量,給出一個確切的因變量——也就是映射。
我們?yōu)g覽網(wǎng)頁的時候在做的是什么——給出一個操作,希求得到一個確切的合邏輯的結(jié)果。
我們想要結(jié)果。
我們做出動作。
有一個函數(shù),幫我們把動作映射成結(jié)果。
——所以還要你這個類何用?
當(dāng)然,雖然你在語義上已經(jīng)沒有價值,但在編程元素上你還是不可缺少,物盡其用,你就當(dāng)package吧!
《魏書.孝靜紀(jì)第十二》:
帝不悅,曰:「自古無不亡之國,朕亦何用此活!」
文襄怒曰:「朕!朕!狗腳朕!」