轉自:跟開濤學SpringMVChtml
在Web世界裏,具體步驟以下:前端
一、 Web瀏覽器(如IE)發起請求,如訪問http://sishuok.comjava
二、 Web服務器(如Tomcat)接收請求,處理請求(好比用戶新增,則將把用戶保存一下),最後產生響應(通常爲html)。web
三、web服務器處理完成後,返回內容給web客戶端(通常就是咱們的瀏覽器),客戶端對接收的內容進行處理(如web瀏覽器將會對接收到的html內容進行渲染以展現給客戶)。spring
所以,在Web世界裏:apache
都是Web客戶端發起請求,Web服務器接收、處理併產生響應。編程
通常Web服務器是不能主動通知Web客戶端更新內容。雖然如今有些技術如服務器推(如Comet)、還有如今的HTML5 websocket能夠實現Web服務器主動通知Web客戶端。設計模式
到此咱們瞭解了在web開發時的請求/響應模型,接下來咱們看一下標準的MVC模型是什麼。瀏覽器
MVC模型:是一種架構型的模式,自己不引入新功能,只是幫助咱們將開發的結構組織的更加合理,使展現與模型分離、流程控制邏輯、業務邏輯調用與展現邏輯分離。如圖1-2緩存
圖1-2
首先讓咱們瞭解下MVC(Model-View-Controller)三元組的概念:
Model(模型):數據模型,提供要展現的數據,所以包含數據和行爲,能夠認爲是領域模型或JavaBean組件(包含數據和行爲),不過如今通常都分離開來:Value Object(數據) 和 服務層(行爲)。也就是模型提供了模型數據查詢和模型數據的狀態更新等功能,包括數據和業務。
View(視圖):負責進行模型的展現,通常就是咱們見到的用戶界面,客戶想看到的東西。
Controller(控制器):接收用戶請求,委託給模型進行處理(狀態改變),處理完畢後把返回的模型數據返回給視圖,由視圖負責展現。 也就是說控制器作了個調度員的工做,。
從圖1-1咱們還看到,在標準的MVC中模型能主動推數據給視圖進行更新(觀察者設計模式,在模型上註冊視圖,當模型更新時自動更新視圖),但在Web開發中模型是沒法主動推給視圖(沒法主動更新用戶界面),由於在Web開發是請求-響應模型。
那接下來咱們看一下在Web裏MVC是什麼樣子,咱們稱其爲 Web MVC 來區別標準的MVC。
模型-視圖-控制器概念和標準MVC概念同樣,請參考1.2,咱們再看一下Web MVC標準架構,如圖1-3:
如圖1-3
在Web MVC模式下,模型沒法主動推數據給視圖,若是用戶想要視圖更新,須要再發送一次請求(即請求-響應模型)。
概念差很少了,咱們接下來了解下Web端開發的發展歷程,和使用代碼來演示一下Web MVC是如何實現的,還有爲何要使用MVC這個模式呢?
此處咱們只是簡單的敘述比較核心的歷程,如圖1-4
圖1-4
1.4.一、CGI:(Common Gateway Interface)公共網關接口,一種在web服務端使用的腳本技術,使用C或Perl語言編寫,用於接收web用戶請求並處理,最後動態產生響應給用戶,但每次請求將產生一個進程,重量級。
1.4.二、Servlet:一種JavaEE web組件技術,是一種在服務器端執行的web組件,用於接收web用戶請求並處理,最後動態產生響應給用戶。但每次請求只產生一個線程(並且有線程池),輕量級。並且能利用許多JavaEE技術(如JDBC等)。本質就是在java代碼裏面 輸出 html流。但表現邏輯、控制邏輯、業務邏輯調用混雜。如圖1-5
圖1-5
如圖1-5,這種作法是絕對不可取的,控制邏輯、表現代碼、業務邏輯對象調用混雜在一塊兒,最大的問題是直接在Java代碼裏面輸出Html,這樣前端開發人員沒法進行頁面風格等的設計與修改,即便修改也是很麻煩,所以實際項目這種作法不可取。
1.4.三、JSP:(Java Server Page):一種在服務器端執行的web組件,是一種運行在標準的HTML頁面中嵌入腳本語言(如今只支持Java)的模板頁面技術。本質就是在html代碼中嵌入java代碼。JSP最終仍是會被編譯爲Servlet,只不過比純Servlet開發頁面更簡單、方便。但表現邏輯、控制邏輯、業務邏輯調用仍是混雜。如圖1-6
圖1-6
如圖1-6,這種作法也是絕對不可取的,控制邏輯、表現代碼、業務邏輯對象調用混雜在一塊兒,但比直接在servlet裏輸出html要好一點,前端開發人員能夠進行簡單的頁面風格等的設計與修改(但若是嵌入的java腳本太多也是很難修改的),所以實際項目這種作法不可取。
JSP本質仍是Servlet,最終在運行時會生成一個Servlet(如tomcat,將在tomcatworkCatalinaweb應用名orgapachejsp下生成),但這種使得寫html簡單點,但還是控制邏輯、表現代碼、業務邏輯對象調用混雜在一塊兒。
1.4.四、Model1:能夠認爲是JSP的加強版,能夠認爲是jsp+javabean如圖1-7
特色:使用<jsp:useBean>標準動做,自動將請求參數封裝爲JavaBean組件;還必須使用java腳本執行控制邏輯。
圖1-7
此處咱們能夠看出,使用<jsp:useBean>標準動做能夠簡化javabean的獲取/建立,及將請求參數封裝到javabean,再看一下Model1架構,如圖1-8。
圖1-8 Model1架構
Model1架構中,JSP負責控制邏輯、表現邏輯、業務對象(javabean)的調用,只是比純JSP簡化了獲取請求參數和封裝請求參數。一樣是很差的,在項目中應該嚴禁使用(或最多再demo裏使用)。
1.4.五、Model2:在JavaEE世界裏,它能夠認爲就是Web MVC模型
Model2架構其實能夠認爲就是咱們所說的Web MVC模型,只是控制器採用Servlet、模型採用JavaBean、視圖採用JSP,如圖1-9
圖1-9 Model2架構
具體代碼事例以下:
從Model2架構能夠看出,視圖和模型分離了,控制邏輯和展現邏輯分離了。
但咱們也看到嚴重的缺點:
1. 一、控制器:
1.1.一、控制邏輯可能比較複雜,其實咱們能夠按照規約,如請求參數submitFlag=toAdd,咱們其實能夠直接調用toAdd方法,來簡化控制邏輯;並且每一個模塊基本須要一個控制器,形成控制邏輯可能很複雜;
1.1.二、請求參數到模型的封裝比較麻煩,若是能交給框架來作這件事情,咱們能夠從中獲得解放;
1.1.三、選擇下一個視圖,嚴重依賴Servlet API,這樣很難或基本不可能更換視圖;
1.1.四、給視圖傳輸要展現的模型數據,使用Servlet API,更換視圖技術也要一塊兒更換,很麻煩。
1.二、模型:
1.2.一、此處模型使用JavaBean,可能形成JavaBean組件類很龐大,通常如今項目都是採用三層架構,而不採用JavaBean。
1.三、視圖
1.3.一、如今被綁定在JSP,很難更換視圖,好比Velocity、FreeMarker;好比我要支持Excel、PDF視圖等等。
1.4.五、服務到工做者:Front Controller + Application Controller + Page Controller + Context
即,前端控制器+應用控制器+頁面控制器(也有稱其爲動做)+上下文,也是Web MVC,只是責任更加明確,詳情請參考《核心J2EE設計模式》和《企業應用架構模式》如圖1-10:
圖1-10
運行流程以下:
職責:
Front Controller:前端控制器,負責爲表現層提供統一訪問點,從而避免Model2中出現的重複的控制邏輯(由前端控制器統一回調相應的功能方法,如前邊的根據submitFlag=login轉調login方法);而且能夠爲多個請求提供共用的邏輯(如準備上下文等等),將選擇具體視圖和具體的功能處理(如login裏邊封裝請求參數到模型,並調用業務邏輯對象)分離。
Application Controller:應用控制器,前端控制器分離選擇具體視圖和具體的功能處理以後,須要有人來管理,應用控制器就是用來選擇具體視圖技術(視圖的管理)和具體的功能處理(頁面控制器/命令對象/動做管理),一種策略設計模式的應用,能夠很容易的切換視圖/頁面控制器,相互不產生影響。
Page Controller(Command):頁面控制器/動做/處理器:功能處理代碼,收集參數、封裝參數到模型,轉調業務對象處理模型,返回邏輯視圖名交給前端控制器(和具體的視圖技術解耦),由前端控制器委託給應用控制器選擇具體的視圖來展現,能夠是命令設計模式的實現。頁面控制器也被稱爲處理器或動做。
Context:上下文,還記得Model2中爲視圖準備要展現的模型數據嗎,咱們直接放在request中(Servlet API相關),有了上下文以後,咱們就能夠將相關數據放置在上下文,從而與協議無關(如Servlet API)的訪問/設置模型數據,通常經過ThreadLocal模式實現。
到此,咱們回顧了整個web開發架構的發展歷程,可能不一樣的web層框架在細節處理方面不一樣,但的目的是同樣的:
乾淨的web表現層:
模型和視圖的分離;
控制器中的控制邏輯與功能處理分離(收集並封裝參數到模型對象、業務對象調用);
控制器中的視圖選擇與具體視圖技術分離。
輕薄的web表現層:
作的事情越少越好,薄薄的,不該該包含無關代碼;
只負責收集並組織參數到模型對象,啓動業務對象的調用;
控制器只返回邏輯視圖名並由相應的應用控制器來選擇具體使用的視圖策略;
儘可能少使用框架特定API,保證容易測試。
到此咱們瞭解Web MVC的發展歷程,接下來讓咱們瞭解下Spring MVC究竟是什麼、架構及來個HelloWorld瞭解下具體怎麼使用吧。
本章具體代碼請參考 springmvc-chapter1工程。
Spring Web MVC是一種基於Java的實現了Web MVC設計模式的請求驅動類型的輕量級Web框架,即便用了MVC架構模式的思想,將web層進行職責解耦,基於請求驅動指的就是使用請求-響應模型,框架的目的就是幫助咱們簡化開發,Spring Web MVC也是要簡化咱們平常Web開發的。
另外還有一種基於組件的、事件驅動的Web框架在此就不介紹了,如Tapestry、JSF等。
Spring Web MVC也是服務到工做者模式的實現,但進行可優化。前端控制器是DispatcherServlet;
應用控制器其實拆爲處理器映射器(Handler Mapping)進行處理器管理和視圖解析器(View Resolver)進行視圖管理;頁面控制器/動做/處理器爲Controller接口(僅包含ModelAndView handleRequest(request, response)
方法)的實現(也能夠是任何的POJO類);支持本地化(Locale)解析、主題(Theme)解析及文件上傳等;提供了很是靈活的數據驗證、格式化和數據綁定機制;提供了強大的約定大於配置(慣例優先原則)的契約式編程支持。
√讓咱們能很是簡單的設計出乾淨的Web層和薄薄的Web層;
√進行更簡潔的Web層的開發;
√天生與Spring框架集成(如IoC容器、AOP等);
√提供強大的約定大於配置的契約式編程支持;
√能簡單的進行Web層的單元測試;
√支持靈活的URL到頁面控制器的映射;
√很是容易與其餘視圖技術集成,如Velocity、FreeMarker等等,由於模型數據不放在特定的API裏,而是放在一個Model裏(Map
數據結構實現,所以很容易被其餘框架使用);
√很是靈活的數據驗證、格式化和數據綁定機制,能使用任何對象進行數據綁定,沒必要實現特定框架的API;
√提供一套強大的JSP標籤庫,簡化JSP開發;
√支持靈活的本地化、主題等解析;
√更加簡單的異常處理;
√對靜態資源的支持;
√支持Restful風格。
Spring Web MVC框架也是一個基於請求驅動的Web框架,而且也使用了前端控制器模式來進行設計,再根據請求映射規則分發給相應的頁面控制器(動做/處理器)進行處理。首先讓咱們總體看一下Spring Web MVC處理請求的流程:
如圖2-1
圖2-1
具體執行步驟以下:
一、 首先用戶發送請求————>前端控制器,前端控制器根據請求信息(如URL)來決定選擇哪個頁面控制器進行處理並把請求委託給它,即之前的控制器的控制邏輯部分;圖2-1中的一、2步驟;
二、 頁面控制器接收到請求後,進行功能處理,首先須要收集和綁定請求參數到一個對象,這個對象在Spring Web MVC中叫命令對象,並進行驗證,而後將命令對象委託給業務對象進行處理;處理完畢後返回一個ModelAndView(模型數據和邏輯視圖名);圖2-1中的三、四、5步驟;
三、 前端控制器收回控制權,而後根據返回的邏輯視圖名,選擇相應的視圖進行渲染,並把模型數據傳入以便視圖渲染;圖2-1中的步驟六、7;
四、 前端控制器再次收回控制權,將響應返回給用戶,圖2-1中的步驟8;至此整個結束。
問題:
一、 請求如何給前端控制器?
二、 前端控制器如何根據請求信息選擇頁面控制器進行功能處理?
三、 如何支持多種頁面控制器呢?
四、 如何頁面控制器如何使用業務對象?
五、 頁面控制器如何返回模型數據?
六、 前端控制器如何根據頁面控制器返回的邏輯視圖名選擇具體的視圖進行渲染?
七、 不一樣的視圖技術如何使用相應的模型數據?
首先咱們知道有如上問題,那這些問題如何解決呢?請讓咱們先繼續,在後邊依次回答。
一、Spring Web MVC核心架構圖,如圖2-2
圖2-2
架構圖對應的DispatcherServlet核心代碼以下:
java代碼:
//前端控制器分派方法 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; int interceptorIndex = -1; try { ModelAndView mv; boolean errorView = false; try { //檢查是不是請求是不是multipart(如文件上傳),若是是將經過MultipartResolver解析 processedRequest = checkMultipart(request); //步驟二、請求處處理器(頁面控制器)的映射,經過HandlerMapping進行映射 mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } //步驟三、處理器適配,即將咱們的處理器包裝成相應的適配器(從而支持多種類型的處理器) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 304 Not Modified緩存支持 //此處省略具體代碼 // 執行處理器相關的攔截器的預處理(HandlerInterceptor.preHandle) //此處省略具體代碼 // 步驟四、由適配器執行處理器(調用處理器相應功能處理方法) mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } // 執行處理器相關的攔截器的後處理(HandlerInterceptor.postHandle) //此處省略具體代碼 } catch (ModelAndViewDefiningException ex) { logger.debug("ModelAndViewDefiningException encountered", ex); mv = ex.getModelAndView(); } catch (Exception ex) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(processedRequest, response, handler, ex); errorView = (mv != null); } //步驟5 步驟六、解析視圖並進行視圖的渲染 //步驟5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale)) //步驟6 視圖在渲染時會把Model傳入(view.render(mv.getModelInternal(), request, response);) if (mv != null && !mv.wasCleared()) { render(mv, processedRequest, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } // 執行處理器相關的攔截器的完成後處理(HandlerInterceptor.afterCompletion) //此處省略具體代碼 catch (Exception ex) { // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } catch (Error err) { ServletException ex = new NestedServletException("Handler processing failed", err); // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } finally { // Clean up any resources used by a multipart request. if (processedRequest != request) { cleanupMultipart(processedRequest); } } }
核心架構的具體流程步驟以下:
一、 首先用戶發送請求——>DispatcherServlet,前端控制器收到請求後本身不進行處理,而是委託給其餘的解析器進行處理,做爲統一訪問點,進行全局的流程控制;
二、 DispatcherServlet——>HandlerMapping, HandlerMapping將會把請求映射爲HandlerExecutionChain對象(包含一個Handler處理器(頁面控制器)對象、多個HandlerInterceptor攔截器)對象,經過這種策略模式,很容易添加新的映射策略;
三、 DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝爲適配器,從而支持多種類型的處理器,即適配器設計模式的應用,從而很容易支持不少類型的處理器;
四、 HandlerAdapter——>處理器功能處理方法的調用,HandlerAdapter將會根據適配的結果調用真正的處理器的功能處理方法,完成功能處理;並返回一個ModelAndView對象(包含模型數據、邏輯視圖名);
五、 ModelAndView的邏輯視圖名——> ViewResolver, ViewResolver將把邏輯視圖名解析爲具體的View,經過這種策略模式,很容易更換其餘視圖技術;
六、 View——>渲染,View會根據傳進來的Model模型數據進行渲染,此處的Model實際是一個Map數據結構,所以很容易支持其餘視圖技術;
七、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給用戶,到此一個流程結束。
此處咱們只是講了核心流程,沒有考慮攔截器、本地解析、文件上傳解析等,後邊再細述。
到此,再來看咱們前邊提出的問題:
一、 請求如何給前端控制器?這個應該在web.xml中進行部署描述,在HelloWorld中詳細講解。
二、 前端控制器如何根據請求信息選擇頁面控制器進行功能處理? 咱們須要配置HandlerMapping進行映射
三、 如何支持多種頁面控制器呢?配置HandlerAdapter從而支持多種類型的頁面控制器
四、 如何頁面控制器如何使用業務對象?能夠預料到,確定利用Spring IoC容器的依賴注入功能
五、 頁面控制器如何返回模型數據?使用ModelAndView返回
六、 前端控制器如何根據頁面控制器返回的邏輯視圖名選擇具體的視圖進行渲染? 使用ViewResolver進行解析
七、 不一樣的視圖技術如何使用相應的模型數據? 由於Model是一個Map數據結構,很容易支持其餘視圖技術
在此咱們能夠看出具體的核心開發步驟:
一、 DispatcherServlet在web.xml中的部署描述,從而攔截請求到Spring Web MVC
二、 HandlerMapping的配置,從而將請求映射處處理器
三、 HandlerAdapter的配置,從而支持多種類型的處理器
四、 ViewResolver的配置,從而將邏輯視圖名解析爲具體視圖技術
五、處理器(頁面控制器)的配置,從而進行功能處理
上邊的開發步驟咱們會在Hello World中詳細驗證。
一、清晰的角色劃分:前端控制器(DispatcherServlet
)、請求處處理器映射(HandlerMapping)、處理器適配器(HandlerAdapter)、視圖解析器(ViewResolver)、處理器或頁面控制器(Controller)、驗證器( Validator)、命令對象(Command 請求參數綁定到的對象就叫命令對象)、表單對象(Form Object 提供給表單展現和提交到的對象就叫表單對象)。
二、分工明確,並且擴展點至關靈活,能夠很容易擴展,雖然幾乎不須要;
三、因爲命令對象就是一個POJO,無需繼承框架特定API,可使用命令對象直接做爲業務對象;
四、和Spring 其餘框架無縫集成,是其它Web框架所不具有的;
五、可適配,經過HandlerAdapter能夠支持任意的類做爲處理器;
六、可定製性,HandlerMapping、ViewResolver等可以很是簡單的定製;
七、功能強大的數據驗證、格式化、綁定機制;
八、利用Spring提供的Mock對象可以很是簡單的進行Web層單元測試;
九、本地化、主題的解析的支持,使咱們更容易進行國際化和主題的切換。
十、強大的JSP標籤庫,使JSP編寫更容易。
………………還有好比RESTful風格的支持、簡單的文件上傳、約定大於配置的契約式編程支持、基於註解的零配置支持等等。
DispatcherServlet是前端控制器設計模式的實現,提供Spring Web MVC的集中訪問點,並且負責職責的分派,並且與Spring IoC容器無縫集成,從而能夠得到Spring的全部好處。 具體請參考第二章的圖2-1。
DispatcherServlet主要用做職責調度工做,自己主要用於控制流程,主要職責以下:
一、文件上傳解析,若是請求類型是multipart將經過MultipartResolver進行文件上傳解析;
二、經過HandlerMapping,將請求映射處處理器(返回一個HandlerExecutionChain,它包括一個處理器、多個HandlerInterceptor攔截器);
三、經過HandlerAdapter支持多種類型的處理器(HandlerExecutionChain中的處理器);
四、經過ViewResolver解析邏輯視圖名到具體視圖實現;
五、本地化解析;
六、渲染具體的視圖等;
七、若是執行過程當中遇到異常將交給HandlerExceptionResolver來解析。
從以上咱們能夠看出DispatcherServlet主要負責流程的控制(並且在流程中的每一個關鍵點都是很容易擴展的)。