Spring技術內幕(4)Spring MVC與Web環境

本章環境

Spring MVC 概述、Web環境中的SpringMVC、上下文在Web容器中的啓動、Spring MVC的設計與實現、Spring MVC視圖的呈現

4.1 SpringMVC概述

在使用Spring MVC的時候,需要在web.xml中配置DispatcherServlet,這個DispatcherServlet可以看成是一個前端控制器的具體實現,還需要在Bean定義中配置Web請求和Controller(控制器)的對應關係,以及各種視圖的展現方式。在具體使用Controller的時候,會看到ModelAndView數據的生成,還會看到把ModelAndView數據交給相應的View來進行呈現。

4.2 Web環境中的Spring MVC

Spring MVC 是建立在IoC容器基礎上的,要了解SpringMVC,首先要了解Spring IoC是如何在Web環境中發揮作用的。

Spring IoC是個獨立的模塊,並不是直接在Web環境中發揮作用,在web容器啓動過程中,將IoC導入,並在web容器中建立起來並初始化,這樣才能建立起SpringMVC運行機制,從而響應web容器傳遞的HTTP請求。

Tomcat 的web.xml對Spring MVC的配置:

在這個配置描述中,首先定義一個Servlet對象,它是Spring MVC的DispatcherServlet。這個DispatcherServlet是MVC中最重要的一個類,起着分發請求的作用。然後,爲這個DispatcherServlet定義了對應的URL映射,這些URL映射爲這個Servlet指定了需要處理的HTTP請求。context-param參數的配置用來指定Spring IOC容器讀取Bean定義的XML文件的路徑,在這裏,這個配置文件被定義爲/WEBINF/applicationContext.xml。DispatcherServlet和ContextLoaderListener提供了在Web容器中對Spring的接口,這些接口與web容器耦合是通過ServletContext來實現的。

4.3 上下文在Web容器中的啓動

4.3.1 IoC容器啓動的基本過程

IoC容器啓動過程就是建立上下文的過程,該上下文與ServletContext相伴而生,由ContextLoaderListener啓動的上下文爲根上下文。在根上下文的基礎上,還有一個與Web MVC相關的上下文用來保存控制器(DispatcherServlet)需要的MVC對象,作爲跟上下文的子上下文。

Web容器中啓動Spring應用程序的過程如下圖:

在web.xml中,已經配置了由Spring提供的實現了ServletContextListener接口的ContextLoaderListener,該監聽器類爲在Web容器中建立IoC容器提供服務。在Web容器中,建立WebApplicationContext的過程,是在contextInitialized的接口實現中完成的。具體的載入IOC容器的過程是由ContextLoaderListenser交由ContextLoader來完成的,而ContextLoader本身就是ContextLoaderListener的基類,它們之間的類關係爲:

 

在ContextLoader中,完成兩個IoC容器建立的基本過程,一個是在Web容器中建立起雙親IoC容器,另一個是生成相應的WebApplicationContext並將其初始化。

4.3.2 Web容器中的上下文設計

WebApplicationContext接口的類繼承關係圖:

 

 

在啓動過程中,Spring使用默認的XmlWebApplicationContext 實現作爲IoC容器。

 

 

4.3.3 ContextLoader的設計與實現

對於Spring承載的Web應用而言,可以指定在Web應用程序啓動時載人IoC容器(或者稱爲WebAppl icationCon text)。這個功能是由ContextLoaderListener這樣的類來完成的,它是在Web容器中配置的監聽器。這個ContextLoaderListener通過使用ContextLoader來完成實際的WebApplicationContext,也就是IoC容器的初始化工作。這個ContextLoader就像Spring應用程序在Web容器中的啓動器。這個啓動過程是在Web容器中發生的,所以需要根據Web容器部署的要求來定義ContextLoader。 

下面分析具體的根上下文的載入過程。在ContextLoaderListener中,實現的是ServletContextListener接口,這個接口裏的函數會結合Web容器的生命週期被調用。因爲ServletContextListener是ServletContext的監聽者,如果ServletContext發生變化.會觸發出相應的事件,而監聽器一直在對這些事件進行監聽,如果接收到了監聽的事件,就會做出預先設計好的響應動作。由於ServletContext的變化而觸發的監聽器的響應具體包括:在服務器啓動時,ServletContext被創建。服務器關閉時,ServletContext將被銷燬等。對應這些事件及Web容器狀態的變化,在監聽器中定義了對應的事件響應的回調方法。比如在服務器啓動時,ServletContextListener的contextlnitialized()方法被調用,服務器將要關閉時,ServletContextListener的contextDestroyed()方法被調用。

 

 

在初始化這個上下文以後,該上下文會被存儲到SevletContext中,這樣就建立了一個全局的關於整個應用的上下文。同時,在啓動Spring MVC時.我們還會看到這個上下文被以後的DispatcherServlet在進行自己持有的上下文的初始化時,設置爲DispatcherServlet自帶的上下文的雙親上下文。

4.4 Spring MVC的設計與實現

4.4.1 Spring MVC的應用場景

在前文的分析過程中,瞭解了 Spring的上下文體系通過ContextLoader和DispatcherServiet建立並初始化的過程。在完成對ContextLoaderListener的初始化以後,Web容器開始初始化DispatcherServlet,這個初始化的啓動與在web.xml中對載入次序的定義有關。

DispatcherServiet會建立自己的上下文來持有Spring MVC的Bean對象,在建立這個自己持有的Ioc容器時,會從ServletContext中得到根上下文作爲DispatcherServlet持有上下文的雙親上下文。有了這個根上下文,再對自己持有的上下文進行初始化,最後把自己持有的這個上下文保存到ServletContext(Web容器的上下文)中,供以後檢索和使用。

4.4.2 Spring MVC設計概念

DispatcherServlet的處理過程,如下圖所示:

 

DispatcherServiet的工作大致可以分爲兩個部分:一個是初始化部分,由initServletBean()啓動,通過initWebAppIicationContext()方法最終調用DispatcherServlet的initStrategies方法。在這個方法裏,DispatcherServlet對MVC模塊的其他部分進行了初始化,比如handlerMapping, ViewResolver等。另一個是對HTTP請求進行響應,作爲一個Serviet,Web容器會調用Servlet的doGet()和doPost()方法,在經過FrameworkServlet的processRequest()簡單處理後,會調用DispatcherServlet的doService()方法,在這個方法調用中封裝了doDispatch(),這個doDispatch()是Dispatcher實現MVC模式的主要部分。

4.4.3 DispatcherServlet的啓動和初始化

作爲Servlet,DispatcherServlet的啓動與Servlet的啓動過程是相聯繫的。在Servlet的初始化過程中,Servlet的init方法會被調用。在HttpServletBean中進行初始化:

FrameworkServlet中的初始化方法:

 

 

 

由於這個根上下文是DispatcherServlet建立的上下文的雙親上下文,所以根上下文中粉理的Bean也是可以被DispatcherServlet的上下文使用的。通過getBean向IoC容器獲取Bean時.容器會先到它的雙親IoC容器中獲取getBean。除了上面的SpringMVC上下文的的創建之外,還需要啓動SpringMVC中的其他一些配置初始化,通過上面的onRefresh調用來完成,這個方法在子類DispatcherServlet中被覆蓋了,實際調用了initStrategies進行配置,這裏就不細說這些配置了。 源碼如下:

對於具體的初始化過程,根據上面的方法名稱,很容易理解。以HanderMapping爲例來說明這個initHanderMappings過程。這裏的Mapping關係的作用是,爲HTTP請求找到相應 的Controller控制器,從而利用這些控制器Controller去完成設計好的數據處理工作。HandlerMappings完成對MVC中Controller的定義和配置,只不過在Web這個特定的應用環境中,這些控制器是與具體的HTTP請求相對應的。DispatcherServlet中HandlerMappings初始化過程的具體實現如下。在HandlerMapping初始化的過程中,把在Bean配置文件中配置好的handlerMapping從IoC容器中取得。

經過以上的讀取過程,handlerMappings變量就已經獲取了在BeanDefinition中配置好的映射關係。其他的初始化過程和handlerMappings比較類似,都是直接從IoC容器中讀入配置,所以這裏的MVC初始化過程是建立在IoC容器已經初始化完成的基礎上的。至於上下文是如何獲得的,可以參見前面對IoC容器在Web環境中加載的實現原理的分析。

4.4.4 MVC處理HTTP分發請求

1.HandlerMapping的配置和設計原理

在初始化完成時,在上下文環境中已定義的所有HandlerMapping都已經被加載了,這些加載的handlerMappings被放在一個List中並排序,存儲着HTTP請求對應的映射數據。這個List中的每一個元素都對應着一個具體handlerMapping的配置,一般每一個handlerMapping可以持有一系列從URL請求到Controller的映射,而Spring MVC 提供了一系列的HandlerMapping實現。

 

這個HandlerExecutionChain的實現看起來比較簡潔,它持有一個攔截器鏈和一個handler對象,這個handler對象實際上就是HTTP請求對應的Controller,在持有這個handler對象的同時,還在HandlerExecutionChain中設置了攔截器鏈,通過這個攔截器鏈中的攔截器,可以爲handler對象提供功能的增強。要完成這些工作,需要對攔截器鏈和handler都進行配置,這些配置都是在這個類的初始化函數中完成的。爲了維護這個攔截器和handler, HandlerExecutionChain還提供了一系列與攔截器鏈維護相關一些操作,比如可以爲攔截器鏈增加攔截器的addInterceptor方法等。

 

 

 

 

HandlerExecutionChain中定義的Handler和Interceptor需要在定義HandlerMapping時配置好,例如對具體的SimpleURLHandlerMapping, 要做的就是根據URL映射的方式,註冊Handler和Interceptor,從而維護一個反映這種映射關係的handlerMap。當需要匹配HTTP請求時,需要查詢這個handlerMap中信息來得到對應的HandlerExecutionChain。這些信息是什麼時候配置好的呢?這裏有一個註冊過程,這個註冊過程在容器對Bean進行依賴注入時發生,它實際上是通過一個Bean的postProcessor來完成的。如果想了解這個過程,可以從依賴注入那裏去看看,doCreateBean->initializeBean -> postProcessBeforeInitialization -> setApplicationContext -> initApplicationContext.

以SimpleUrlHandlerMapping爲例,需要注意的是,這裏用到了對容器的回調,只有SimpleUrlHandlerMapping是AppicationContextAware的子類才能啓動這個註冊過程。這個註冊過程完成的是反映URL和Controller之間的映射關係的handlerMap的建立。具體分析如下:

這個SimpleUrlHandlerMapping註冊過程的完成,很大一部分需要它的基類來配合,這個基類就是AbstractUrlHandlerMapping.

 

 

這個配置好URL請求和handler映射數據的handlerMap,爲Spring MVC響應HTTP請求準備好了基本的映射數據,根據這個handlerMap以及設置於其中的映射數據,可以方便地由URL請求得到它所對應的handler。有了這些準備工作,Spring MVC就可以等待HTTP請求的到來了。

2  使用HandlerMapping完成請求的映射處理

繼續通過SimpleUrlHandlerMapping的實現來分析HandlerMapping的接口方法getHandler,該方法會根據初始化時得到的映射關係來生成DispatcherServlet需要的HandlerExecutionChain,也就是說,這個getHandler方法是實際使用HandlerMapping完成請求的映射處理的地方。

 

取得handler的具體過程在getHandlerInternal方法中實現,這個方法接受HTTP請求作爲參數,它的實現在AbstractHandlerMapping的子類AbstractUrlHandlerMapping中,這個實現過程包括從HTTP請求中得到URL,並根據URL到urlMapping中獲得handler。代碼實現如下:

 

 

 

 

經過這一系列對HTTP請求進行解析和匹配handler的過程,得到了與請求對應的handler處理器。在返回的handler中,已經完成了在HandlerExecutionChain中的封裝工作,爲handler對HTTP請求的響應做好了準備。然後,在MVC中,還有一個重要的問題:請求是怎樣實現分發,從而到達對應的handler的呢?

3.Spring MVC對HTTP請求的分發處理

重新回到DispatcherServlet,這個類不但建立了自己持有的IoC容器,還肩負着請求分發處理的重任。在MVC框架初始化完成以後,對HTTP請求的處理是在doService()方法中完成的,DispatcherServlet也是通過這個方法來響應HTTP的請求。然後,依據Spring MVC的使用,業務邏輯的調用入口是在handler的handler函數中實現的,這裏是連接Spring MVC和應用業務邏輯實現的地方。

 

 

經過上面一系列的處理,得到了handler對象,接着就可以開始調用handler對象中的HTTP響應動作了。在handler中封裝了應用業務邏輯,由這些邏輯對HTTP請求進行相應的處理,生成各種需要的數據,並把這些數據封裝到ModelAndView對象中去,這個ModelAndView的數據封裝是Spring MVC框架的要求。對handler來說,這些都是通過調用handler的handlerRequest方法來觸發完成的。在得到ModelAndView對象以後,這個ModelAndView對象會被交給MVC模式中的視圖類,由視圖類對ModelAndView對象中的數據進行呈現。視圖呈現的調用入口在DispatcherServlet的doDispatch方法中實現,它的調用入口是render方法。這個方法在下面介紹。

4.5 Spring MVC視圖的呈現

4.5.1 DispatcherServlet視圖呈現的設計

前面分析了Spring MVC中的M(Model)和 C(Controller)相關的實現,其中的M大致對應成ModelAndView的生成,而C大致對應到DispatcherServlet和用戶業務邏輯有關的handler實現。在Spring MVC框架中,DispatcherServlet起到了非常杧的作用,是整個MVC框架的調用樞紐。對於下面關心的視圖呈現功能,它的調用入口同樣在

DispatcherServlet中的doDispatch方法中實現。具體來說,在DispatcherServlet中,對視圖呈現的處理是在render方法調用中完成的,代碼如下。爲了完成視圖的呈現工作,需要從ModelAndViewc對象中取得視圖對象,然後調用視圖對象的render方法,由這個視圖對象來完成特定的視圖呈現工作。同時,由於是在Web的環境中,因此視圖對象的呈現往往需要完成與HTTP請求和響應相關的處理,這些對象會作爲參數傳到視圖對象的render方法中,供render方法使用

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章