SpringMVC:DispatcherServlet代碼分析及運行過程

1          首先該類有一靜態語塊,用以加載缺省策略。

    static {

            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATHDispatcherServlet.class);

            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

}

缺省的策略文件爲當前包下的DispatcherServlet.properties,主要包括

LocaleResolver(本地化解析器,AcceptHeaderLocaleResolver)

ThemeResolver(主題解析器,FixedThemeResolver)

HandlerMapping(處理器映射,BeanNameUrlHandlerMapping)

HandlerAdapter(控制適配器,多個)

ViewResolver(視圖解析器,InternalResourceViewResolver)

RequestToViewNameTranslator(請求到視圖名的翻譯器,DefaultRequestToViewNameTranslator)


2          通過繼承關係調用HttpServletBean類的init()初始化方法


    在該方法體內,首先將這個servlet視爲一個bean對其包裝並將配置文件中當前servlet下的初始參數值賦倒這個servlet中。然後調用子類(FrameworkServlet)initServletBean(),而子類通過模板模式再調用其子類(DispatcherServlet)的initFrameworkServlet()方法。在initServletBean()方法中主要代碼片段如下:

            this.webApplicationContext = initWebApplicationContext();

            initFrameworkServlet();

而在initWebApplicationContext(),它首先是從ServletContext中獲得由ContextLoader類設置進的keyWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性的值(即Spring容器的實例),然後以實例爲parent創建一個新的容器。這也是爲什麼說一個DispatcherServlet就對應一個WebApplicationContext(每個DispatcherServlet都有各自的上下文)。最後將該WebApplicationContext綁定到到ServletContext,如果允許發佈話

由此可以看出:

WebApplicationContextUtils.getWebApplicationContext(servletContext)

servletContext.getAttribute(SERVLET_CONTEXT_PREFIX+ getServletName())之間的主要區別在於:後者包括了該servlet的所有配置信息而前者是全局性的沒有這部分信息


3          DispatcherServletinitFrameworkServlet()方法初始化工作

        initMultipartResolver();

        initLocaleResolver();

        initThemeResolver();

        initHandlerMappings();

        initHandlerAdapters();

        initHandlerExceptionResolvers();

        initRequestToViewNameTranslator();

        initViewResolvers();

    如果在配置文件沒有設置將缺省的策略文件DispatcherServlet.properties。由此可見,因爲DispatcherServlet是單例的,所以設置的信息是全局的也就是所有的解析器不能動態的變化。


4          當調用類FrameworkServlet中的doGet()/doPost()/doPut()/doDelete()方法時

以上方法均調用相同方法processRequest(request, response)。注意:Spring放棄了對HttpServletservice()方法的覆蓋而用具體的操作方法取代。

5          調用類FrameworkServlet中的processRequest(request, response)方法

        try {

        doService(request, response);

     }

        finally {

            if (isPublishEvents()) {

                this.webApplicationContext.publishEvent(

                    new ServletRequestHandledEvent(this,

                    request.getRequestURI(), request.getRemoteAddr(),

                    request.getMethod(), getServletConfig().getServletName(),

                    WebUtils.getSessionId(request), getUsernameForRequest(request),

                                processingTime, failureCause));

            }

     }

通過代碼摘要可以看出該類有一個webApplicationContext引用,除了執行doService()這個核心方法外,每次都會發佈一個ServletRequestHandledEvent事件如果publishEventstrue,其值可以在web.xml文件中增加添加context參數,或servlet初始化參數(

<init-param>

<param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/application.xml</param-value>

</init-param>)

DispatcherServlet初始化參數

參數

描述

contextClass

實現WebApplicationContext接口的類,當前的servlet用它來創建上下文。如果這個參數沒有指定,默認使用XmlWebApplicationContext

contextConfigLocation

傳給上下文實例(由contextClass指定)的字符串,用來指定上下文的位置。這個字符串可以被分成多個字符串(使用逗號作爲分隔符)來支持多個上下文(在多上下文的情況下,如果同一個bean被定義兩次,後面一個優先)。

namespace

WebApplicationContext命名空間。默認值是[server-name]-servlet

publishContext

我們發佈的context是作爲ServletContext的一個屬性嗎?默認值爲true,屬性名爲SERVLET_CONTEXT_PREFIX+ getServletName()

publishEvents

在執行每個請求後是否發佈一個ServletRequestHandledEvent事件?默認值爲true

    以上參數的初始加載是在HttpServletBean類的init()中,而所有DispatcherServlet的解析器是在initFrameworkServlet()方法中。

由此可見,DispatcherServlet的初始參數加載來源可分爲兩部分,

5.1       web.xml文件對於該servlet配置的<init-param>初始參數中

5.2       通過SpringIOC容器實例化後,通過該類的初始化方法,將容器內的實例一一引用

6          調用類DispatcherServlet中的doService(request, response)方法

暴露在request屬性中DispatchServlet的一些特性,並且調用doDispatch(request, response)方法執行真正的分發動作。

         request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());

request.setAttribute(LOCALE_RESOLVER_ATTRIBUTEthis.localeResolver);

    request.setAttribute(THEME_RESOLVER_ATTRIBUTEthis.themeResolver);

    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    try {

        doDispatch(request, response);

    }

7          調用類DispatcherServlet中的doDispatch(request, response)方法

7.1         在區域上下文持有者(LocaleContextHolder中當前線程下的值做一次替換。暴露當前localeResolverrequest中的Locale

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

        LocaleContextHolder.setLocaleContext(new LocaleContext() {

            public Locale getLocale() {

                returnlocaleResolver.resolveLocale(request);

            }});

由源碼可見,將當前線程下的LocaleContext取出,再從當前區域解析器(LocaleResolver)中得到Locale前創建爲LocaleContext後再賦到LocaleContextHolder

7.2       在請求上下文持有者(RequestContextHolder中當前線程下的值做一次替換。暴露當前RequestAttributes到當前線程中

RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();

ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);

RequestContextHolder.setRequestAttributes(requestAttributes);

    在此注意,ServletRequestAttributes類只能獲得sessionrequest有屬性,而無法獲得request的參數(parameter)

7.3       轉換requestMultipartHttpServletRequest類型的實例,如果不是就簡單的返回

        protected HttpServletRequest checkMultipart(HttpServletRequest request) {

if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {

            if (request instanceof MultipartHttpServletRequest) {

            }

            else

                returnthis.multipartResolver.resolveMultipart(request);

            }

        return request;

    }

由代碼可以看出,如果要支持文件上傳要有兩個前提

    1.在應用的配置文件中添加MultipartResolver(分段文件解析器),目前Spring支持兩種上傳文件的開源項目Commons FileUpload(http://jakarta.apache.org/commons/fileupload)COSFileUpload(http://www.servlets.com/cos)

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver"/>

注意:Beanid必須爲multipartResolver’, DispatcherServlet中的成員屬性this.multipartResolver是在初始化時與容器中的實例引用在一起的。

    2. 通過multipartResolver.isMultipart(request)判斷requestContextType是否是multipart/form-data。所以表單格式應爲

        <form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/><input type="submit"/>
        這個元素的名字(“file”)和服務器端處理這個表單的bean(在下面將會提到)中類型爲byte[]的屬性名相同。在這個表單裏我們也聲明瞭編碼參數(enctype="multipart/form-data")以便讓瀏覽器知道如何對這個文件上傳表單進行編碼

最後由相應的MultipartResolver(分段文件解析器)request封裝爲與解析器對應的MultipartHttpServletRequest實例並返回

      

7.4       根據當前請求URL爲其找到指定的處理器(handler)與欄截器(HandlerInterceptor),並將它們封裝爲HandlerExecutionChain實例。

HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);

在此過程中分爲如下幾個步驟

1.    循環所有已註冊的HandlerMapping(處理器映射),對於不同的處理器映射對其映射的處理方式不同。BeanNameUrlHandlerMapping只要在配置文件中bean元素的name屬性以/開頭它都認爲是一個映射如<bean name=”/editaccount”>。而SimpleUrlHandlerMapping只關注自身定義的<property name=”mappings”>下的那些映射,而且支持Ant風格的定義

2.    通過客戶請求的URL與處理器映映射的鍵值做匹配(也可以使用Ant風格匹配,參見AbstractUrlHandlerMapping.lookupHandler()方法),如果滿足匹配條件,就返回與其對應的處理器(Handler),一般情況下,都是Controller(控制器)接口子孫類的實例。

3.    根據匹配上的處理器映射取得攔截器數組,並與找到的處理器一同作爲構建函數的參數創建出HandlerExecutionChain實例

7.5       執行所有攔截器的前置方法

HandlerInterceptor接口提供瞭如下三個方法:

1.    preHandle(…)前置方法,在處理器執行前調用,當返回false時,DispatcherServlet認爲該攔截器已經處理完了請求,而不再繼續執行執行鏈中的其它攔截器和處理器

2.    postHandle(…)後置方法,在處理器執行後調用

3.    afterCompletion(…)完成前置方法,在整個請求完後調用。注意如果第n個攔截器的preHandle方法返回的是false則只會調用第n個攔截器之前所有該方法,而n++不會被調用

另:Spring還提供了一個adapterHandlerInterceptorAdapter讓用戶更方便的擴展HandlerInterceptor接口

7.6       獲得與當前處理器相匹配的HandlerAdapter(處理器適配器)

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {

        Iterator it = this.handlerAdapters.iterator();

        while (it.hasNext()) {

            HandlerAdapter ha = (HandlerAdapter) it.next();

            if (ha.supports(handler)) {

                return ha;

            }

    }

循環所有已註冊的HandlerAdapter(處理器適配器),如果該適配器支持ha.supports(handler)當前處理器,就返回。由此可見,Spring通過HandlerAdapter使處理器解耦,實處理器(Handler)不只是僅僅侷限於控制器(Controller)這一種形式。目前Spring可以支持,ServletHttpRequestHandlerThrowawayControllerController,從這一點上看Spring已爲客戶留出了很大空間作爲擴展的口子。

7.7       根據處理器適配器調用處理器的指定方法,執行真正的處理操作。如Controller(控制器)接口調用handleRequest() ThrowawayController接口調用execute()Servlet接口調用service()….但無論調用那種自理器,HandlerAdapter中的handle()方法都要求返回ModelAndView的一個實例

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

Spring中最常用的處理器是以Controller接口下的所有子孫類控制器,繼承結構如圖


在這些控制器中又以SimpleFormController控制器最爲常用,下圖爲該類的完整執行過程


 

7.8       執行所有攔截器的後置方法

7.9       根據處理器(Handle)返回的ModelAndView對象,找到或創建View對象,並調用View對象的render()方法

render(mv, processedRequest, response)

在此有幾個關鍵性的概念:

ModelAndView:它是模形(Model,實際上只是一個Map,最終會將模形存放到request的屬性中,以Mapkeyrequest屬性的鍵,Mapvalue爲值)與視圖(View)的持有者。而View又提供了兩種形態,即以View接口爲祖先的實例對象,以字符串的視圖的名字。該類通過isReference()方法判斷是一個字符串的引用還是一個真正的View對象。注意:該類有些奇怪:它將有狀態的Model與無狀態的View合併到一個類中,這就導致每次請求都到對該類型的實例進行創建,而不無緩存。View對象Spring提供了緩存功能。

ViewResolver(視圖解析器):主要提供的功能是從視圖名稱到實際視圖的映射。也就是說只有ModelAndViewisReference()返回true時,纔會對視圖的名字做解析工作。這使視圖解析與真正的視圖相分離,這樣我們就可以通過不同的方式配置視圖,如可以採用propertiesxml或是提供URI的前、後綴等形式配置視圖。但要注意視圖解析器與視圖之間還是緊密的關係,Sping在此沒實現全部的完全解耦

ViewResolver

描述

AbstractCachingViewResolver

抽象視圖解析器實現了對視圖的緩存。在視圖被投入使用之前,通常需要進行一些準備工作。從它繼承的視圖解析器將對要解析的視圖進行緩存。

InternalResourceViewResolver

作爲UrlBasedViewResolver的子類,它支持InternalResourceView(對ServletJSP的包裝),以及其子類JstlViewTilesView。通過setViewClass方法,可以指定用於該解析器生成視圖使用的視圖類。

UrlBasedViewResolver

UrlBasedViewResolver實現ViewResolver,將視圖名直接解析成對應的URL,不需要顯式的映射定義。如果你的視圖名和視圖資源的名字是一致的,就可使用該解析器,而無需進行映射。

ResourceBundleViewResolver

ResourceBundleViewResolver實現ViewResolver,在一個ResourceBundle中尋找所需bean的定義。這個bundle通常定義在一個位於classpath中的屬性文件中。默認的屬性文件是views.properties

XmlViewResolver

XmlViewResolver實現ViewResolver,支持XML格式的配置文件。該配置文件必須採用與Spring XML Bean Factory相同的DTD。默認的配置文件是/WEB-INF/views.xml

VelocityViewResolver /FreeMarkerViewResolver

作爲UrlBasedViewResolver的子類,它能支持VelocityView(對Velocity模版的包裝)和FreeMarkerView以及它們的子類。

View(視圖):處理請求的準備工作,並將該請求提交給某種具體的視圖技術。

如,InternalResourceView會通過RequestDispatcher類調用include()forward()方法

  RedirectView會通過HttpServletResponse類調用sendRedirect()方法

AbstractExcelView會產生一個輸出流

區別:

1forwardinclude區別在於forward本意是讓第一個頁面處理request,第二個頁面處理response.調用第二個頁面後,程序還會返回第一個頁面繼續執行,但是此時再使用response輸出已經沒有作用了;include服務器端的動態加載,執行完第二個頁面的程序後可以回到第一個頁面繼續輸出,即將第二個頁面的輸出拉到第一個頁面中。

2forwardinclude共享Request範圍內的對象,redirect則不行,即:如果一個javabean被聲明爲request範圍的話,則被forward到的資源也可以訪問這個javabean,redriect則不行。

3forwardinclude基本上都是轉發到context內部的資源,而redirect可以重定向到外部的資源,如: req.sendRedriect("http://www.mocuai.com");


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