1. 程式人生 > >Spring MVC:原理與使用

Spring MVC:原理與使用

本人上一篇博文提到了Spring的注入功能,這樣在存在物件依賴(具體意思可見上一篇博文)的時候就不用自己生成一個物件了,特別是對於較多的無狀態物件的時候,這個特別方便,加上Spring提供的用xml配置檔案和程式碼註解兩種方式,使得使用更加靈活。然而spring的功能遠不止如此,Spring的強大功能其實還在於做Java後臺框架,即Spring MVC,把後臺的邏輯和和檢視解耦分離,方便使用與擴充套件,特別是在大型專案中很有用。

PS:賣個自己的廣告,上一篇博文《Spring注入:配置與註解》 (如果是在其他抄襲網站看到這篇博文的人可以通過這個網址找到上一篇博文在哪裡,這樣會有一個比較連貫的瞭解,順便吐槽一下這些抄襲網站,自己爬取別人的博文放到自己的網站上,還不寫個引用自哪裡,這終究不太好吧

~~~http://blog.csdn.net/u011579138/article/details/51379066 

MVC

首先大概介紹一下什麼是MVC,由於自己也是渣渣,所以為方便初學者學習,還是介紹一下自己理解中的MVC(不一定是完全對的,僅作參考,老鳥可以直接跳過這一段)。MVC是三個東西的縮寫,modelviewcontroller

Model:業務模型類,例如:購物程式中一些包含使用者資訊的類,這些類只有getset的方法來獲取值,或者經常提到service,就是那些不需要具體值的物件,只是進行方法呼叫的那些類,這些都屬於model。可以簡單理解為處理業務邏輯的類

View:給使用者看的檢視,例如

html頁面,jscss效果這些。簡單理解就是給使用者看的東西

Controller:控制器,這樣描述可能不太好理解,例如,瀏覽器來了一個請求,這時候控制器(controller)就用來分發給具體業務模型(model),業務模型處理完之後返回一個檢視(view)給使用者,這就是一個完整流程。有初學者可能會控制器控制什麼,例如一個url,其中傳了個引數1可能是請求一個網頁,引數2可能是請求另一個網頁,這時候控制器就是用來把這個請求到底是交給哪個業務處理模型。

就像快遞,使用者寄一個快遞,控制器(controller)拿到快遞,根據使用者的請求(引數),交給對應地區的快遞員(model),快遞員處理完髮送任務之後返回一個結果(

view)給使用者,告訴使用者我搞定了。這就是一個mvc的流程。

Spring MVC 原理

其實是基於servlet的一個框架,servlet可以理解為網頁跟邏輯的對映的(具體還是請自學一下),spring是把所有的請求全部對映到一個叫DispatcherServlet的一個物件去,然後由這個物件進行分發請求,有點類似於controller的感覺。DispatcherServlet找到一個handler(這裡其實是通過在一個叫handlerMappingsList物件中來找handlerExecutionChain,在這個handlerExecutionChain裡面有handlerInterceptor,原始碼詳情如果後面本人有空會寫一篇部落格來討論原始碼實現,由於過於複雜,這裡就不介紹了太多)。找到handler之後,這時候會通過handler找到HandlerAdapter,這裡的handlerhandlerAdapter的區別,根據我查的幾篇部落格是這樣說的handler負責找到需要處理這個請求的類,adapter負責找到這個類中具體哪個方法處理這個請求(以下兩篇部落格都是這樣說的,第二篇中舉的例子已經過時,裡面那個adapter已經被官方廢棄了)。

這裡本人卻並不贊同這個說法,後面本人會解釋一下,這裡先繼續介紹spring MVC的流程,dapater找到之後會呼叫handle方法來呼叫處理這個請求的函式(即model)。一般這個函式會返回一些東西,經過conver的處理生成一個介面(即view),view生成好了就會返回給使用者,這樣一個處理流程就結束了。如下圖所示

 HandlerHandlerAdapter真正的作用?

但是本人通過檢視原始碼,發現並不是像這樣,其實在handler的時候就已經確定了哪個具體的方法來處理這個請求。如下圖,這裡其實已經找到了對應的方法,downloadReportFile,所以這裡並不是找到類,而且已經找到了類裡面的方法了,而且這裡的確定方法的標準其實就是判斷urlhttp的方法(GET POST)。

 

其次,再看看adapter是怎麼來的,是不是真的確定了具體的方法,先貼幾張原始碼(這裡不用慌,作為初學者擔心看程式碼很煩或看不懂,慢慢靜下心來,其實很容易看懂的,而且我會用詳細的語言來表述,所以不要跳過這段)

這裡可以看到通過getHandler得到了mappedHandler,其實mappedHandler是一個handlerExecutionChain(可以參考我上面那段提到的,如果文字太多可以用瀏覽器的ctrl+fhandlerExecutionChain那段)。找到之後檢查handlermappedHandler是不是空,然後通過handler獲得HandlerAdapter


再下一層,看看getHandlerAdapter的原理,從handlerAdapters這個容器中遍歷,如果支援這個handler就返回相應的handlerAdapter(照顧初學者:logger是用來打log日誌的工具,和程式碼邏輯無關)。

然後再下一層,看看spports裡面是怎麼實現的。可以看到就是判斷了一下handler的型別,並且看supportsInternal的返回結果,很簡單。

 

那麼再下一層看看supportsInternal這個函式怎麼實現,結果一看,就是返回true。所以你發現其實得到handlerAdapter其實和定位方法沒有任何關係。


所以我才質疑handlerAdapter的作用究竟是什麼。我有問過公司一個主管,結果發現他自己練spring本身的執行過程都不大清楚,所以也沒有得到這個問題的答案。

這裡的handlerAdapter有好3個,上面的原始碼是第一個的,但是後面兩個我也看了,只是檢查了一下是不是屬於那些型別,也沒有定位到具體的方法,也就是說handlerAdapter裡面根本就沒有用來指向哪個方法,指向具體的方法是在handler裡面。

 

 

所以在這裡個人猜測,handler是指向具體的方法,handlerAadpter是用來執行具體的方法的(在後面會看到,其實handlerAapter中有一個handler的方法,就是在這個方法中呼叫handler中指向的那個方法)

HandlerIntercetptor

這裡順便簡單解釋一下handlerIntercetptor的作用,這兩個攔截器其實就是個事件觸發機制,在handle這個請求前做一些事兒(HandlerExecutionChain中的applyPreHandle),在handle這個請求之後做些事兒(HandlerExecutionChain中的applyPostHandle),類似於Netty中的pipeline機制。也就是spring在讓你處理請求前或請求後,你可以通過新增interceptor做一些處理。

HandlerExecutionChain

看名字顧名思義就是執行鏈,這裡麵包含了一個handlerInterceptor的容器,預設有兩個interceptor,然後preHandle和postHandle都是空。如果自己要實現可以在裡面自己新增自己的interceptor。然後這裡面還包括了一個handler,上面已經說過了。

圖中mappedHandler就是HandlerExecutionChain,先呼叫所有inteceptor的PreHandle,然後再呼叫adapter的handle函式,再呼叫所有interceptor的postHandle。其中如果applyPreHandle如果返回false,也就是事件處理完畢了,就是說不需要handler中那個函式去處理了,所以這個時候就會直接return,例如有些引數明顯不合法,可以直接不作處理,如下圖

 

Spring MVC 用法

瞭解完概念,再來介紹一下用法。這裡的用法我只介紹用註解的方法,這種比較常用一些。Spring MVC中大部分的程式碼都已經寫好了,自己需要做的就是需要寫業務處理。

首先攔截請求,用註解注入一個類,在這個類中編寫方法,用@RequestMapping註解,value就是uri,params就是引數,也可以再加個method,判斷http的方法是GET還是POST,函式的體的引數和返回型別完全自己定義,沒有特殊的格式。(一開始我總不清楚這些函式為什麼要這麼多引數,是不是固定要這些的,後來看了原始碼發現其實這些是用java反射做的,引數是不限制個數的那種函式申明方式,返回型別是object,所以這些返回型別和引數沒有固定的)。不過一般可以定義自己需要的一些引數。

返回值的作用

沒有@ResponesBody

沒有這個註解,這注意返回的東西,是作為資源呼叫的標識使用,例如你返回一個a的string型別,會預設解析為找一個名字叫a的html或者js之類的資原始檔。如果要返回其他型別,就得看看spring預設支不支援解析咯,有可能自己還要寫解析的類,這個類就是ViewResolver,專門解析這些返回物件的(可以參見上面那個流程圖中的第五步)。當然這裡的資源都是靜態的,如果需要一些動態的引數,這時候就要用到modelMap了(上面那個流程圖也有,第六步),就是在ViewResolver處理之後再用到modelMap裡面的一些值來動態處理。


有@ResponseBody

如果這個註解,就意味著返回的物件不是作為資源識別符號來使用,而是作為http返回的包體內容。上面那種是屬於你返回個東西,spring幫你解析成view了返回,這是把你返回的東西直接當做內容返回了。顧名思義ResponseBody。這裡返回的內容也是需要解析,解析這個的叫converer,把返回的物件解析成可以放進http包體的內容。Spring預設支援了幾種返回,例如string,json,還有byteArray(預知詳情,自己可以試著看看原始碼)。所以如果你要返回一個你自己寫的物件,那就要自己寫個converer才行,不然最後沒有返回的。

這就是大概的用法,其實Spring還有很多種用法,裡面所支援的東西也還很多,例如可以和其他框架,像MyBatis或者Struts配合,但是本人能力有限,研究spring一週才勉強研究出這些東西,與其他框架配合也只做了一點與MyBatis相關的東西,以後若是有機會還是得多加練習才行。

另外ZZ再嘴多一點,照應開頭所說的。其實Spring MVC也就是基於Servlet的,所以不用Spring MVC這種辦法直接用Servlet也是可以實現網路請求處理的。而且spring通過大量呼叫判斷其實是會犧牲掉一些效能的。但是依舊還是有人使用spring,其實就在於大型專案管理和維護比較比較方便一點這樣邏輯會更清楚,因為這裡將model,view,controller給分開了,處理邏輯和返回介面在不同的地方,如果用servlet勢必會攪在一起。這大概就是spring的一個好處。但效能方面spring應該是處於劣勢。

PS:本人是個渣渣,但是每次在研究完一個東西之後還是會總結一下這個框架有啥優勢和不足,這樣才是一個我這種渣渣提升的一個方向,所以萬望諸君共勉。

因為前人,才能更高

PS:本篇部落格主要根據ZZ本人檢視原始碼和參考這篇博文,其他博文也有參考,但參考程度不多,故這裡就不列舉出來,但也很感激前人留下的文獻,如有轉發本文,請註明出處