1. 程式人生 > >對Spring 及SpringMVC的理解

對Spring 及SpringMVC的理解

Spring是一個輕型容器(light-weight container),其核心是Bean工廠(Bean Factory),用以構造我們所需要的M(Model)。在此基礎之上,Spring提供了AOP(Aspect-Oriented Programming, 面向層面的程式設計)的實現,用它來提供非管理環境下申明方式的事務、安全等服務;對Bean工廠的擴充套件ApplicationContext更加方便我們實現J2EE的應用;DAO/ORM的實現方便我們進行資料庫的開發;Web MVC和Spring Web提供了Java Web應用的框架或與其他流行的Web框架進行整合。

1)開源框架
2)IoC(控制反轉),將類的建立和依賴關係寫在配置檔案裡,由配置檔案注入,實現了鬆耦合
3)AOP 將安全,事務等於程式邏輯相對獨立的功能抽取出來,利用spring的配置檔案將這些功能插進去,實現了按照方面程式設計,提高了複用性

前言

最近在看Spring MVC的原始碼,就把自己對MVC模式和對各種框架的實現的認識寫出來給大家看看,算是一個總結.所以,懇請大家用懷疑的眼光來看待這篇文章,假如有認識不對的地方,麻煩指出.

MVC與WEB應用

MVC是什麼就不用我多說了.對於現有較成熟的Model-View-Control(MVC)框架而言,其注意的主要問題無外乎下面這些:

Model:

模型應該包含由檢視顯示的資料.在J2EE Web應用中,資料通常應該由普通的javabean組成.一旦一個控制器選擇了檢視,模型就要包含檢視相應的資料.模型本身不應該進一步的訪問資料,也不應該和業務物件相聯絡.

模型要解決的問題包括:

l          封裝要顯示的資料

l          我不認為模型要依賴於特定的框架

l          不一定非得是javabean

View:

檢視負責顯示出模型包含的資訊,檢視不必瞭解控制器或是底層業務物件的具體實現

檢視要解決的問題包括:

l          在顯示給定資料模型的情況下顯示內容

l          不應該包含有業務邏輯

l          可能需要執行顯示邏輯,比如顏色交替的顯示某個陣列的各行

l          檢視最好不處理驗證的錯誤,資料的驗證應該在由其他元件完成

l          檢視不應該處理引數,引數應該交由控制器集中處理

Control:

控制器就好像MVC裡的中樞神經,它也許會需要一些助手來幫助它比如解析檢視,解析引數等.控制器可以訪問到業務物件或者是它的代理是很重要的,比如Struts裡的Action.

控制器要解決的問題包括:

l          檢查和抽取請求引數

l          呼叫業務物件,傳遞從請求中獲取的引數

l          建立模型,檢視講顯示對應的模型

l          選擇一個合適的檢視傳送給客戶端

l          控制器有時不會只有一個

現有的框架

現在已經有很多的MVC的框架實現.比較流行的應該就是Struts和Webwork了

Struts

這是最流行的web框架,幾乎成為了實際上的工業標準.除了上面討論的MVC模式應該有的優點以外.它還有如下一些缺點:

l          每個Action只生成一次,然後就被快取起來,再次請求這個Action的時候就不會生成新的物件,而是重複使用第一次生成的物件,這就意味著每個Action必須是執行緒安全的

l          採用ActionForm封裝了表單資料,但是卻只能對應String型別的資料, 雖然它可以使用工具Commons Beanutils進行型別轉化,但是僅僅是提供了物件級別的支援

l          嚴重的依賴於Servlet API, 測試比較困難(不過下一版Struts裡的Action.execute的方法簽名講會換成execute(ActionContext actionContext),依賴也許不會那麼嚴重)

l          框架本身的驗證規則比較簡單,一般都是依賴於Commons Validation進行驗證

l          想在Action前後做些處理很困難.有時甚至不得不自己去寫專門的控制器

l          由於Struts都是具體的類繼承,這樣很容易打破封裝?

l          提供各式各樣的自定義的標籤,但是資料繫結太原始了,這樣就使頁面程式碼依賴於Struts這個特定的框架,而它卻不是規範,我覺得這是很致命的

l          它太面向JSP了,儘管使用其他檢視技術是有可能的,但是使用的時候卻不是很方便

Webwork

這個框架雖然我沒使用過,但是卻一直在關注它的發展

Webwork的設計思想採用了比Struts更為聰明的一種方式,就技術角度上說比Struts要高出不少.它以Command模式為基礎.分為Xwork和Webwork,而且框架並不依賴於Servlet API.

Xwork提供了很多核心功能:攔截器(Interceptor),執行時表單驗證,型別轉換,IoC容器等.

WebWork建立在Xwork之上,用於處理基於HTTP的響應和請求.用Map和ActionContext封裝了Session,Application等這些Servlet物件.從而解除了和Servlet API的耦合.

但是它仍然不是完美的:

l          為每一個請求都建立一個Action可能有些浪費.(但是Servlet引擎也是為每個請求建立多個物件,但是也沒看出來對效能有多大的影響?)

l          當專案越來越大的時候,配置檔案可能會很零亂.好像它不支援多個配置檔案

l          異常處理是Command模式裡值得注意的問題:我們不知道某一特定命令可能會丟擲什麼特定的異常,所以execute()被迫丟擲異常,而不論異常是執行時異常,還是已檢查異常

 Spring MVC Framework的目標

上面說了一些MVC的原理,以及現在主流框架的一些問題,現在來看Spring是如何處理的. Spring MVC框架根據不同的角色定義了很多介面,但是它最大的問題也是依賴於Servlet API

Spring MVC Framework有這樣一些特點:

l          它是基於元件技術的.全部的應用物件,無論控制器和檢視,還是業務物件之類的都是java元件.並且和Spring提供的其他基礎結構緊密整合.

l          不依賴於Servlet API(目標雖是如此,但是在實現的時候確實是依賴於Servlet的)

l          可以任意使用各種檢視技術,而不僅僅侷限於JSP

l          支援各種請求資源的對映策略

l          它應是易於擴充套件的

我認為評價一個框架,應該有幾個原則

l          它應該是易於使用的,易於測試的

Spring 易於使用嗎?我不這麼覺得,尤其是它的配置檔案.在最恐怖的情況下,各種業務邏輯,基礎設施也許會擁擠在一個配置檔案裡.而如事務處理這些基礎設施應該是由容器管理而不是開發人員,就算把這些分開到幾個配置檔案裡,邏輯上雖然清晰了,但是基礎設定卻還是暴露在外邊

Spring易於測試嗎?對Spring進行單元測試很容易,測試起來很方便

l          應該在多個層次上提供介面

Spring提供了很多介面,而幾乎每個介面都有預設的抽象實現,每個抽象實現都有一些具體實現,所以在可擴充套件性這點上Spring無疑是很優秀的

l          框架內部和框架外部應該被區別對待

框架內部可以很複雜,但是使用起來一定要簡單,Spring的內部比較麻煩,但是它很好的隱藏了這種複雜性,使用起來很舒服,比如設定一個bean的屬性.僅僅是setPropertyValue(String propertyName, Object value)就完成,至於怎麼去設定,Spring完全隱藏了這種複雜性

l          完善的文件和測試集

這個就不用說了,老外的東西,都很完善

 Spring Web框架基本流程

知道了Spring MVC框架,現在來看看它的流程

Spring MVC Framework大至流程如下:

當web程式啟動的時候,ContextLoaderServlet會把對應的配置檔案資訊讀取出來,通過注射去初始化控制器DispatchServlet. 而當接受到一個HTTP請求的時候, DispatchServlet會讓HandlerMapping去處理這個請求.HandlerMapping根據請求URL(不一定非要是URL,完全可以自定義,非常靈活)來選擇一個Controller. 然後DispatchServlet會在呼叫選定的Controller的handlerRequest方法,並且在這個方法前後呼叫這個Controller的interceptor(假如有配置的話),然後返回一個檢視和模型的集合ModelAndView.框架通過ViewResolver來解析檢視並且返回一個View物件,最後呼叫View的render方法返回到客戶端

DispatcherServlet

這是框架的控制器,是一個具體類,它通過執行時的上下文物件來初始化.控制器本身並不去控制流程,而只是是Controller的”控制器”,他只是把處理請求的責任委託給了對應的Controller.

控制器繼承自抽象基類FrameworkServlet,它的屬性webApplicationContext就代表著這個web程式上下文,而這個上下文物件預設實現就是從一個XML檔案讀取配置資訊(當然也可以是其他檔案格式). WebApplicationContext其實是beans包的東西,這個包提供了這個Spring整個框架的基礎結構,以後我會分析這個包的內容.但是現在僅僅需要知道WebApplicationContext代表一個web應用的上下文物件.

現在來看看DispatchServlet是如何工作的:

DispatchServlet由於繼承自抽象基類FrameworkServlet,而FrameworkServlet裡的doGet(),doPost()方法裡有呼叫serviceWrapper(),跳到serviceWrapper()裡去看,結果發現它有把具體實現委託給了doService(request, response); 方法.所以現在已經很清楚了, DispatchServlet真正實現功能的是doService() 這個方法.

特別的, FrameworkServlet的initFrameworkServlet()這個方法是控制器的初始化方法,用來初始化HandlerMappings之類的物件,這也是延遲到子類實現的.其實就是一個Template模式的實現.don’t call us, we will call u.總的看來,Spring就是通過這樣來實現它的控制反轉的:用框架來控制流程,而不是使用者

跳到doService()一看究竟,就會發現真正工作的又是另一個助手函式doDispatch(request, response),沒辦法,繼續看下去,發現這樣兩行程式碼

HandlerExecutionChain mappedHandler = null;

         mappedHandler = getHandler(processedRequest, false);

看HandlerExecutionChain原始碼就發現它其實就是對Controller和它的Interceptors的進行了包裝;

getHandler()就是從HandlerMappings(這是一個List,存放的handlerMapping物件)中取出對應的handlerMapping物件, 每個HandlerMapping物件代表一個Controller和URL的對映(其實在執行的時候是一個HandlerExecutionChain和URL的對映,而HandlerExecutionChain物件其實就是對Controller和它interceptors的一個包裝器,可以把HandlerMapping看成Controller和URL的對映).而這個HandlerMapping是通過配置檔案在執行時注射進來的,一般是SimpleUrlHandlerMapping這個子類

取得了HandlerMapping物件,繼續向下看,發現:

                  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;

                                               }

                                     }

這裡就是在呼叫Controller的攔截器,原理就是這句了:

         interceptor.preHandle(processedRequest, response, mappedHandler.getHandler(), mv);

preHandle方法傳入了mappedHandler.getHandler()這個引數來實現遞迴呼叫!而interceptor.postHandle方法如此一般.只不過這個方法是在handleRequest方法後呼叫

繼續看下去:

         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

發現Controller的handleRequest真正的操作又被代理給了HandlerAdapter的handle方法,並且返回一個ModelAndView,我想這裡增加一層的意義應該是為了解除Controller和DispatchServlet的耦合吧.

接著就很簡單了,呼叫render()方法,在這個方法裡面由ViewResoler解析出檢視名,再呼叫檢視物件的render方法把合適的檢視展現給使用者

到此,控制器的流程就OVER了

HandlerMapping

通過使用HandlerMapping,控制器可以用URL和某一個Controller進行標準的對映,而實現URL對映的具體子類的UrlHandlerMapping.

Spring還允許我們自定義對映,比如通過Session,cookie或者使用者狀態來對映.而這一切僅僅只需要實現HandlerMapping介面而已.不過URL對映已經能滿足大部分的要求

Controller

Controller 類似Structs的Action, Controller介面只有一個方法handleRequest(),放回一個ModelAndView物件,如同設計目標所說的那樣,每個Controller都是一個java元件,所以它可以在上下文環境中任意配置,元件屬性都會在初始化的時候被配置.Spring自己提供了幾個具體的實現.方便我們使用

ViewResolver

Controller通常返回包含檢視名字而不是檢視物件的ModelAndView物件.從而徹底的解除了控制器和檢視之間的耦合關係,並且在這裡還可以提供國際化的支援.

在你的配置檔案中你可以:

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元件,它不做任何請求處理或是業務邏輯,它僅僅獲取模型傳遞的資料,並把資料顯示出來.它裡面的 render方法按照如下流程工作:

l          設定模型的資料到request作用域

l          取得檢視的URL

l          轉發到對應的URL

總結:

Spring的web框架是一個很優秀的框架,在這裡只是走馬觀花的分析了Spring的工作流程和一些關鍵的類,但是並沒有去深入的去探討它背後所體現的思想,還有它的優缺點等東西.這些都等下次再說吧