1. 程式人生 > >SpringMVC原始碼分析1:SpringMVC概述

SpringMVC原始碼分析1:SpringMVC概述

轉載自:https://blog.csdn.net/a724888/article/details/76014532

Web MVC簡介

1.1、Web開發中的請求-響應模型:

在Web世界裡,具體步驟如下:

1、  Web瀏覽器(如IE)發起請求,如訪問http://sishuok.com

2、  Web伺服器(如Tomcat)接收請求,處理請求(比如使用者新增,則將把使用者儲存一下),最後產生響應(一般為html)。

3、web伺服器處理完成後,返回內容給web客戶端(一般就是我們的瀏覽器),客戶端對接收的內容進行處理(如web瀏覽器將會對接收到的html內容進行渲染以展示給客戶)。

 

因此,在Web世界裡:

都是Web客戶端發起請求,Web伺服器接收、處理併產生響應。

 

一般Web伺服器是不能主動通知Web客戶端更新內容。雖然現在有些技術如伺服器推(如Comet)、還有現在的HTML5 websocket可以實現Web伺服器主動通知Web客戶端。

 

到此我們瞭解了在web開發時的請求/響應模型,接下來我們看一下標準的MVC模型是什麼。

 

1.2、標準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。

1.3、Web MVC概述

模型-檢視-控制器概念和標準MVC概念一樣,請參考1.2,我們再看一下Web MVC標準架構,如圖1-3:

 

如圖1-3

在Web MVC模式下,模型無法主動推資料給檢視,如果使用者想要檢視更新,需要再發送一次請求(即請求-響應模型)。

 

概念差不多了,我們接下來了解下Web端開發的發展歷程,和使用程式碼來演示一下Web MVC是如何實現的,還有為什麼要使用MVC這個模式呢?

 

1.4、Web端開發發展歷程

此處我們只是簡單的敘述比較核心的歷程,如圖1-4

圖1-4

 

1.4.1、CGI:(Common Gateway Interface)公共閘道器介面,一種在web服務端使用的指令碼技術,使用C或Perl語言編寫,用於接收web使用者請求並處理,最後動態產生響應給使用者,但每次請求將產生一個程序,重量級。

 

1.4.2、Servlet:一種JavaEE web元件技術,是一種在伺服器端執行的web元件,用於接收web使用者請求並處理,最後動態產生響應給使用者。但每次請求只產生一個執行緒(而且有執行緒池),輕量級。而且能利用許多JavaEE技術(如JDBC等)。本質就是在java程式碼裡面 輸出 html流。但表現邏輯、控制邏輯、業務邏輯呼叫混雜。如圖1-5    

圖1-5

如圖1-5,這種做法是絕對不可取的,控制邏輯、表現程式碼、業務邏輯物件呼叫混雜在一起,最大的問題是直接在Java程式碼裡面輸出Html,這樣前端開發人員無法進行頁面風格等的設計與修改,即使修改也是很麻煩,因此實際專案這種做法不可取。

 

1.4.3、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,將在tomcat\work\Catalina\web應用名\org\apache\jsp下生成),但這種使得寫html簡單點,但仍是控制邏輯、表現程式碼、業務邏輯物件呼叫混雜在一起。

 

1.4.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.5、Model2:在JavaEE世界裡,它可以認為就是Web MVC模型

Model2架構其實可以認為就是我們所說的Web MVC模型,只是控制器採用Servlet、模型採用JavaBean、檢視採用JSP,如圖1-9

圖1-9 Model2架構

 

具體程式碼事例如下:

 

從Model2架構可以看出,檢視和模型分離了,控制邏輯和展示邏輯分離了。

但我們也看到嚴重的缺點:

1.  1、控制器:

1.1.1、控制邏輯可能比較複雜,其實我們可以按照規約,如請求引數submitFlag=toAdd,我們其實可以直接呼叫toAdd方法,來簡化控制邏輯;而且每個模組基本需要一個控制器,造成控制邏輯可能很複雜;

1.1.2、請求引數到模型的封裝比較麻煩,如果能交給框架來做這件事情,我們可以從中得到解放;

1.1.3、選擇下一個檢視,嚴重依賴Servlet API,這樣很難或基本不可能更換檢視;

1.1.4、給檢視傳輸要展示的模型資料,使用Servlet API,更換檢視技術也要一起更換,很麻煩。

 

1.2、模型:

1.2.1、此處模型使用JavaBean,可能造成JavaBean元件類很龐大,一般現在專案都是採用三層架構,而不採用JavaBean。

 

1.3、檢視

1.3.1、現在被繫結在JSP,很難更換檢視,比如Velocity、FreeMarker;比如我要支援Excel、PDF檢視等等。

 

1.4.5、服務到工作者: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工程。

 

2.1、Spring Web MVC是什麼

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)解析及檔案上傳等;提供了非常靈活的資料驗證、格式化和資料繫結機制;提供了強大的約定大於配置(慣例優先原則)的契約式程式設計支援。

2.2、Spring Web MVC能幫我們做什麼

√讓我們能非常簡單的設計出乾淨的Web層和薄薄的Web層;

√進行更簡潔的Web層的開發;

√天生與Spring框架整合(如IoC容器、AOP等);

√提供強大的約定大於配置的契約式程式設計支援;

√能簡單的進行Web層的單元測試;

√支援靈活的URL到頁面控制器的對映;

√非常容易與其他檢視技術整合,如Velocity、FreeMarker等等,因為模型資料不放在特定的API裡,而是放在一個Model裡(Map資料結構實現,因此很容易被其他框架使用);

√非常靈活的資料驗證、格式化和資料繫結機制,能使用任何物件進行資料繫結,不必實現特定框架的API;

√提供一套強大的JSP標籤庫,簡化JSP開發;

√支援靈活的本地化、主題等解析;

√更加簡單的異常處理;

√對靜態資源的支援;

√支援Restful風格。

2.3、Spring Web MVC架構

Spring Web MVC框架也是一個基於請求驅動的Web框架,並且也使用了前端控制器模式來進行設計,再根據請求對映規則分發給相應的頁面控制器(動作/處理器)進行處理。首先讓我們整體看一下Spring Web MVC處理請求的流程:

 

2.3.1、Spring Web MVC處理請求的流程

如圖2-1

圖2-1

具體執行步驟如下:

1、  首先使用者傳送請求————>前端控制器,前端控制器根據請求資訊(如URL)來決定選擇哪一個頁面控制器進行處理並把請求委託給它,即以前的控制器的控制邏輯部分;圖2-1中的1、2步驟;

2、  頁面控制器接收到請求後,進行功能處理,首先需要收集和繫結請求引數到一個物件,這個物件在Spring Web MVC中叫命令物件,並進行驗證,然後將命令物件委託給業務物件進行處理;處理完畢後返回一個ModelAndView(模型資料和邏輯檢視名);圖2-1中的3、4、5步驟;

3、  前端控制器收回控制權,然後根據返回的邏輯檢視名,選擇相應的檢視進行渲染,並把模型資料傳入以便檢視渲染;圖2-1中的步驟6、7;

4、  前端控制器再次收回控制權,將響應返回給使用者,圖2-1中的步驟8;至此整個結束。

 

問題:

1、  請求如何給前端控制器?

2、  前端控制器如何根據請求資訊選擇頁面控制器進行功能處理?

3、  如何支援多種頁面控制器呢?

4、  如何頁面控制器如何使用業務物件?

5、  頁面控制器如何返回模型資料?

6、  前端控制器如何根據頁面控制器返回的邏輯檢視名選擇具體的檢視進行渲染?

7、  不同的檢視技術如何使用相應的模型資料?

 

首先我們知道有如上問題,那這些問題如何解決呢?請讓我們先繼續,在後邊依次回答。

 

2.3.2、Spring Web MVC架構

1、Spring Web MVC核心架構圖,如圖2-2

圖2-2

 

 

架構圖對應的DispatcherServlet核心程式碼如下:

 

java程式碼:

Java程式碼  

  1. //前端控制器分派方法  
    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);  
                       //步驟2、請求到處理器(頁面控制器)的對映,通過HandlerMapping進行對映  
                    mappedHandler = getHandler(processedRequest, false);  
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {  
                        noHandlerFound(processedRequest, response);  
                        return;  
                    }  
                       //步驟3、處理器適配,即將我們的處理器包裝成相應的介面卡(從而支援多種型別的處理器)  
                    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());  
      
                      // 304 Not Modified快取支援  
                    //此處省略具體程式碼  
      
                    // 執行處理器相關的攔截器的預處理(HandlerInterceptor.preHandle)  
                    //此處省略具體程式碼  
      
                    // 步驟4、由介面卡執行處理器(呼叫處理器相應功能處理方法)  
                    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 步驟6、解析檢視並進行檢視的渲染  
    //步驟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);  
                }  
            }  
        }  

     

 

核心架構的具體流程步驟如下:

1、  首先使用者傳送請求——>DispatcherServlet,前端控制器收到請求後自己不進行處理,而是委託給其他的解析器進行處理,作為統一訪問點,進行全域性的流程控制;

2、  DispatcherServlet——>HandlerMapping, HandlerMapping將會把請求對映為HandlerExecutionChain物件(包含一個Handler處理器(頁面控制器)物件、多個HandlerInterceptor攔截器)物件,通過這種策略模式,很容易新增新的對映策略;

3、  DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝為介面卡,從而支援多種型別的處理器,即介面卡設計模式的應用,從而很容易支援很多型別的處理器;

4、  HandlerAdapter——>處理器功能處理方法的呼叫,HandlerAdapter將會根據適配的結果呼叫真正的處理器的功能處理方法,完成功能處理;並返回一個ModelAndView物件(包含模型資料、邏輯檢視名);

5、  ModelAndView的邏輯檢視名——> ViewResolver, ViewResolver將把邏輯檢視名解析為具體的View,通過這種策略模式,很容易更換其他檢視技術;

6、  View——>渲染,View會根據傳進來的Model模型資料進行渲染,此處的Model實際是一個Map資料結構,因此很容易支援其他檢視技術;

7、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給使用者,到此一個流程結束。

 

此處我們只是講了核心流程,沒有考慮攔截器、本地解析、檔案上傳解析等,後邊再細述。

 

到此,再來看我們前邊提出的問題:

 

 

1、  請求如何給前端控制器?這個應該在web.xml中進行部署描述,在HelloWorld中詳細講解。

2、  前端控制器如何根據請求資訊選擇頁面控制器進行功能處理? 我們需要配置HandlerMapping進行對映

3、  如何支援多種頁面控制器呢?配置HandlerAdapter從而支援多種型別的頁面控制器

4、  如何頁面控制器如何使用業務物件?可以預料到,肯定利用Spring IoC容器的依賴注入功能

5、  頁面控制器如何返回模型資料?使用ModelAndView返回

6、  前端控制器如何根據頁面控制器返回的邏輯檢視名選擇具體的檢視進行渲染? 使用ViewResolver進行解析

7、  不同的檢視技術如何使用相應的模型資料? 因為Model是一個Map資料結構,很容易支援其他檢視技術

 

在此我們可以看出具體的核心開發步驟:

1、  DispatcherServlet在web.xml中的部署描述,從而攔截請求到Spring Web MVC

2、  HandlerMapping的配置,從而將請求對映到處理器

3、  HandlerAdapter的配置,從而支援多種型別的處理器

4、  ViewResolver的配置,從而將邏輯檢視名解析為具體檢視技術

5、處理器(頁面控制器)的配置,從而進行功能處理

 

上邊的開發步驟我們會在Hello World中詳細驗證。

 

2.4、Spring Web MVC優勢

1、清晰的角色劃分:前端控制器(DispatcherServlet)、請求到處理器對映(HandlerMapping)、處理器介面卡(HandlerAdapter)、檢視解析器(ViewResolver)、處理器或頁面控制器(Controller)、驗證器(   Validator)、命令物件(Command  請求引數繫結到的物件就叫命令物件)、表單物件(Form Object 提供給表單展示和提交到的物件就叫表單物件)。

2、分工明確,而且擴充套件點相當靈活,可以很容易擴充套件,雖然幾乎不需要;

3、由於命令物件就是一個POJO,無需繼承框架特定API,可以使用命令物件直接作為業務物件;

4、和Spring 其他框架無縫整合,是其它Web框架所不具備的;

5、可適配,通過HandlerAdapter可以支援任意的類作為處理器;

6、可定製性,HandlerMapping、ViewResolver等能夠非常簡單的定製;

7、功能強大的資料驗證、格式化、繫結機制;

8、利用Spring提供的Mock物件能夠非常簡單的進行Web層單元測試;

9、本地化、主題的解析的支援,使我們更容易進行國際化和主題的切換。

10、強大的JSP標籤庫,使JSP編寫更容易。

………………還有比如RESTful風格的支援、簡單的檔案上傳、約定大於配置的契約式程式設計支援、基於註解的零配置支援等等。

 

 

3.1、DispatcherServlet作用

DispatcherServlet是前端控制器設計模式的實現,提供Spring Web MVC的集中訪問點,而且負責職責的分派,而且與Spring IoC容器無縫整合,從而可以獲得Spring的所有好處。 具體請參考第二章的圖2-1。

 

DispatcherServlet主要用作職責排程工作,本身主要用於控制流程,主要職責如下:

1、檔案上傳解析,如果請求型別是multipart將通過MultipartResolver進行檔案上傳解析;

2、通過HandlerMapping,將請求對映到處理器(返回一個HandlerExecutionChain,它包括一個處理器、多個HandlerInterceptor攔截器);

3、通過HandlerAdapter支援多種型別的處理器(HandlerExecutionChain中的處理器);

4、通過ViewResolver解析邏輯檢視名到具體檢視實現;

5、本地化解析;

6、渲染具體的檢視等;

7、如果執行過程中遇到異常將交給HandlerExceptionResolver來解析。

 

從以上我們可以看出DispatcherServlet主要負責流程的控制(而且在流程中的每個關鍵點都是很容易擴充套件的)。

 

3.2、DispatcherServlet在web.xml中的配置

  1. <servlet>
    <servlet-name>chapter2</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
    <servlet-name>chapter2</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>
    

     

 

load-on-startup:表示啟動容器時初始化該Servlet;

url-pattern:表示哪些請求交給Spring Web MVC處理, “/” 是用來定義預設servlet對映的。也可以如“*.html”表示攔截所有以html為副檔名的請求。

 

該DispatcherServlet預設使用WebApplicationContext作為上下文,Spring預設配置檔案為“/WEB-INF/[servlet名字]-servlet.xml”。

 

DispatcherServlet也可以配置自己的初始化引數,覆蓋預設配置:

摘自Spring Reference

引數 描述
contextClass 實現WebApplicationContext介面的類,當前的servlet用它來建立上下文。如果這個引數沒有指定, 預設使用XmlWebApplicationContext。
contextConfigLocation 傳給上下文例項(由contextClass指定)的字串,用來指定上下文的位置。這個字串可以被分成多個字串(使用逗號作為分隔符) 來支援多個上下文(在多上下文的情況下,如果同一個bean被定義兩次,後面一個優先)。
namespace WebApplicationContext名稱空間。預設值是[server-name]-servlet。

 

因此我們可以通過新增初始化引數

 

  1. <servlet>
    <servlet-name>chapter2</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-servlet-config.xml</param-value>
    </init-param>
    </servlet>

     

 

如果使用如上配置,Spring Web MVC框架將載入“classpath:spring-servlet-config.xml”來進行初始化上下文而不是“/WEB-INF/[servlet名字]-servlet.xml”。

3.3、上下文關係

整合Web環境的通用配置:

  1. <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
    classpath:spring-common-config.xml,
    classpath:spring-budget-config.xml
    </param-value>
    </context-param>
    <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

     

 

如上配置是Spring整合Web環境的通用配置;一般用於載入除Web層的Bean(如DAO、Service等),以便於與其他任何Web框架整合。

contextConfigLocation:表示用於載入Bean的配置檔案;

contextClass:表示用於載入Bean的ApplicationContext實現類,預設WebApplicationContext。

 

 

建立完畢後會將該上下文放在ServletContext:

servletContext.setAttribute(

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,

this.context);

 

ContextLoaderListener初始化的上下文和DispatcherServlet初始化的上下文關係,如圖3-1

圖3-1

從圖中可以看出:

ContextLoaderListener初始化的上下文載入的Bean是對於整個應用程式共享的,不管是使用什麼表現層技術,一般如DAO層、Service層Bean;

DispatcherServlet初始化的上下文載入的Bean是隻對Spring Web MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,該初始化上下文應該只加載Web相關元件。

3.4、DispatcherServlet初始化順序

繼承體系結構如下所示:

1、HttpServletBean繼承HttpServlet,因此在Web容器啟動時將呼叫它的init方法,該初始化方法的主要作用

:::將Servlet初始化引數(init-param)設定到該元件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通過BeanWrapper簡化設值過程,方便後續使用;

:::提供給子類初始化擴充套件點,initServletBean(),該方法由FrameworkServlet覆蓋。

 

  1. public abstract class HttpServletBean extends HttpServlet implements EnvironmentAware{
    @Override
    public final void init() throws ServletException {
    //省略部分程式碼
    //1、如下程式碼的作用是將Servlet初始化引數設定到該元件上
    //如contextAttribute、contextClass、namespace、contextConfigLocation;
    try {
    PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
    initBeanWrapper(bw);
    bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
    //…………省略其他程式碼
    }
    //2、提供給子類初始化的擴充套件點,該方法由FrameworkServlet覆蓋
    initServletBean();
    if (logger.isDebugEnabled()) {
    logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
    }
    //…………省略其他程式碼
    }

     

 

 

2、FrameworkServlet繼承HttpServletBean,通過initServletBean()進行Web上下文初始化,該方法主要覆蓋一下兩件事情:

    初始化web上下文;

    提供給子類初始化擴充套件點;

  1. public abstract class FrameworkServlet extends HttpServletBean {
    @Override
    protected final void initServletBean() throws ServletException {
    //省略部分程式碼
    try {
    //1、初始化Web上下文
    this.webApplicationContext = initWebApplicationContext();
    //2、提供給子類初始化的擴充套件點
    initFrameworkServlet();
    }
    //省略部分程式碼
    }
    }

     

 

  1. protected WebApplicationContext initWebApplicationContext() {
    //ROOT上下文(ContextLoaderListener載入的)
    WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    if (this.webApplicationContext != null) {
    // 1、在建立該Servlet注入的上下文
    wac = this.webApplicationContext;
    if (wac instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    if (!cwac.isActive()) {
    if (cwac.getParent() == null) {
    cwac.setParent(rootContext);
    }
    configureAndRefreshWebApplicationContext(cwac);
    }
    }
    }
    if (wac == null) {
    //2、查詢已經繫結的上下文
    wac = findWebApplicationContext();
    }
    if (wac == null) {
    //3、如果沒有找到相應的上下文,並指定父親為ContextLoaderListener
    wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
    //4、重新整理上下文(執行一些初始化)
    onRefresh(wac);
    }
    if (this.publishContext) {
    // Publish the context as a servlet context attribute.
    String attrName = getServletContextAttributeName();
    getServletContext().setAttribute(attrName, wac);
    //省略部分程式碼
    }
    return wac;
    }

     

 

從initWebApplicationContext()方法可以看出,基本上如果ContextLoaderListener載入了上下文將作為根上下文(DispatcherServlet的父容器)。

 

最後呼叫了onRefresh()方法執行容器的一些初始化,這個方法由子類實現,來進行擴充套件。

 

 

3、DispatcherServlet繼承FrameworkServlet,並實現了onRefresh()方法提供一些前端控制器相關的配置:

 

  1. public class DispatcherServlet extends FrameworkServlet {
    //實現子類的onRefresh()方法,該方法委託為initStrategies()方法。
    @Override
    protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
    }
    
    //初始化預設的Spring Web MVC框架使用的策略(如HandlerMapping)
    protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
    }
    }

     

 

從如上程式碼可以看出,DispatcherServlet啟動時會進行我們需要的Web層Bean的配置,如HandlerMapping、HandlerAdapter等,而且如果我們沒有配置,還會給我們提供預設的配置。

 

從如上程式碼我們可以看出,整個DispatcherServlet初始化的過程和做了些什麼事情,具體主要做了如下兩件事情:

1、初始化Spring Web MVC使用的Web上下文,並且可能指定父容器為(ContextLoaderListener載入了根上下文);

2、初始化DispatcherServlet使用的策略,如HandlerMapping、HandlerAdapter等。

 

 

伺服器啟動時的日誌分析(此處加上了ContextLoaderListener從而啟動ROOT上下文容器):

 

 資訊: Initializing Spring root WebApplicationContext //由ContextLoaderListener啟動ROOT上下文

 

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.support.XmlWebApplicationContext - Refreshing Root WebApplicationContext: startup date [Mon Mar 12 13:33:55 CST 2012]; root of context hierarchy

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader - Loading bean definitions

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 0 bean definitions from location pattern [/WEB-INF/ContextLoaderListener.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for Root WebApplicationContext: org.s[email protected]1c05ffd: defining beans []; root of factory hierarchy

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for Root WebApplicationContext:

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.ContextLoader - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT] //將ROOT上下文繫結到ServletContext

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 438 ms  //到此ROOT上下文啟動完畢

 

 2012-03-12 13:33:55 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Initializing servlet 'chapter2'

資訊: Initializing Spring FrameworkServlet 'chapter2'  //開始初始化FrameworkServlet對應的Web上下文

2012-03-12 13:33:55 [main] INFO  org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'chapter2': initialization started

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Servlet with name 'chapter2' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [Root WebApplicationContext: startup date [Mon Mar 12 13:33:55 CST 2012]; root of context hierarchy]

//此處使用Root WebApplicationContext作為父容器。

2012-03-12 13:33:55 [main] INFO  org.springframework.web.context.support.XmlWebApplicationContext - Refreshing WebApplicationContext for namespace 'chapter2-servlet': startup date [Mon Mar 12 13:33:55 CST 2012]; parent: Root WebApplicationContext

2012-03-12 13:33:55 [main] INFO  org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from ServletContext resource [/WEB-INF/chapter2-servlet.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader - Loading bean definitions

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name[org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0]  //我們配置的HandlerMapping

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name[org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0] //我們配置的HandlerAdapter

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0] //我們配置的ViewResolver

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.BeanDefinitionParserDelegate - No XML 'id' specified - using '/hello' as bean name and [] as aliases 

//我們的處理器(HelloWorldController)

2012-03-12 13:33:55 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 4 bean definitions from location pattern [/WEB-INF/chapter2-servlet.xml]

2012-03-12 13:33:55 [main] DEBUG org.springframework.web.context.support.XmlWebApplicationContext - Bean factory for WebApplicationContext for namespace 'chapter2-servlet': org.s[email protected]1372656: defining beans [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0,/hello]; parent: org.s[email protected]1c05ffd

//到此容器註冊的Bean初始化完畢

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Unable to locate MultipartResolver with name 'multipartResolver': no multipart request handling provided

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'

//預設的LocaleResolver註冊

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'

//預設的ThemeResolver註冊

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0'

//發現我們定義的HandlerMapping 不再使用預設的HandlerMapping。

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0'

//發現我們定義的HandlerAdapter 不再使用預設的HandlerAdapter。

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver'

//異常處理解析器ExceptionResolver

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver'

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'

 

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Published WebApplicationContext of servlet 'chapter2' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.chapter2]

//繫結FrameworkServlet初始化的Web上下文到ServletContext

2012-03-12 13:33:56 [main] INFO  org.springframework.web.servlet.DispatcherServlet - FrameworkServlet 'chapter2': initialization completed in  297 ms

2012-03-12 13:33:56 [main] DEBUG org.springframework.web.servlet.DispatcherServlet - Servlet 'chapter2' configured successfully

//到此完整流程結束 

 

 

 

從如上日誌我們也可以看出,DispatcherServlet會進行一些預設的配置。接下來我們看一下預設配置吧。

 

 

 

3.5、DispatcherServlet預設配置

DispatcherServlet的預設配置在DispatcherServlet.properties(和DispatcherServlet類在一個包下)中,而且是當Spring配置檔案中沒有指定配置時使用的預設策略:

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

 

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

 

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\

    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

 

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

 

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\

    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\

    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

 

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

 

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

 

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 

 

 

從如上配置可以看出DispatcherServlet在啟動時會自動註冊這些特殊的Bean,無需我們註冊,如果我們註冊了,預設的將不會註冊。

 

因此如第二章的BeanNameUrlHandlerMapping、SimpleControllerHandlerAdapter是不需要註冊的,DispatcherServlet預設會註冊這兩個Bean。

 

從DispatcherServlet.properties可以看出有許多特殊的Bean,那接下來我們就看看Spring Web MVC主要有哪些特殊的Bean。

3.6、DispatcherServlet中使用的特殊的Bean

DispatcherServlet預設使用WebApplicationContext作為上下文,因此我們來看一下該上下文中有哪些特殊的Bean:

1、Controller:處理器/頁面控制器,做的是MVC中的C的事情,但控制邏輯轉移到前端控制器了,用於對請求進行處理;

2、HandlerMapping:請求到處理器的對映,如果對映成功返回一個HandlerExecutionChain物件(包含一個Handler處理器(頁面控制器)物件、多個HandlerInterceptor攔截器)物件;如BeanNameUrlHandlerMapping將URL與Bean名字對映,對映成功的Bean就是此處的處理器;

3、HandlerAdapter:HandlerAdapter將會把處理器包裝為介面卡,從而支援多種型別的處理器,即介面卡設計模式的應用,從而很容易支援很多型別的處理器;如SimpleControllerHandlerAdapter將對實現了Controller介面的Bean進行適配,並且掉處理器的handleRequest方法進行功能處理;

4、ViewResolver:ViewResolver將把邏輯檢視名解析為具體的View,通過這種策略模式,很容易更換其他檢視技術;如InternalResourceViewResolver將邏輯檢視名對映為jsp檢視;

5、LocalResover:本地化解析,因為Spring支援國際化,因此LocalResover解析客戶端的Locale資訊從而方便進行國際化;

6、ThemeResovler:主題解析,通過它來實現一個頁面多套風格,即常見的類似於軟體面板效果;

7、MultipartResolver:檔案上傳解析,用於支援檔案上傳;

8、HandlerExceptionResolver:處理器異常解析,可以將異常對映到相應的統一錯誤介面,從而顯示使用者友好的介面(而不是給使用者看到具體的錯誤資訊);

9、RequestToViewNameTranslator:當處理器沒有返回邏輯檢視名等相關資訊時,自動將請求URL對映為邏輯檢視名;

10、FlashMapManager:用於管理FlashMap的策略介面,FlashMap用於儲存一個請求的輸出,當進入另一個請求時作為該請求的輸入,通常用於重定向場景,後邊會細述。

 

 

到此DispatcherServlet我們已經瞭解了,接下來我們就需要把上邊提到的特殊Bean挨個擊破,那首先從控制器開始吧。