1. 程式人生 > >SpringMVC原始碼分析3:DispatcherServlet的初始化與請求轉發

SpringMVC原始碼分析3:DispatcherServlet的初始化與請求轉發

在我們第一次學Servlet程式設計,學java web的時候,還沒有那麼多框架。我們開發一個簡單的功能要做的事情很簡單,就是繼承HttpServlet,根據需要重寫一下doGet,doPost方法,跳轉到我們定義好的jsp頁面。Servlet類編寫完之後在web.xml裡註冊這個Servlet類。

除此之外,沒有其他了。我們啟動web伺服器,在瀏覽器中輸入地址,就可以看到瀏覽器上輸出我們寫好的頁面。為了更好的理解上面這個過程,你需要學習關於Servlet生命週期的三個階段,就是所謂的“init-service-destroy”。

以上的知識,我覺得對於你理解SpringMVC的設計思想,已經足夠了。SpringMVC當然可以稱得上是一個複雜的框架,但是同時它又遵循Servlet世界裡最簡單的法則,那就是“init-service-destroy”。我們要分析SpringMVC的初始化流程,其實就是分析DispatcherServlet類的init()方法,讓我們帶著這種單純的觀點,開啟DispatcherServlet的原始碼一窺究竟吧。

用Eclipse IDE開啟DispatcherServlet類的原始碼,ctrl+T看一下。

DispatcherServlet類的初始化入口方法init()定義在HttpServletBean這個父類中,HttpServletBean類作為一個直接繼承於HttpServlet類的類,覆寫了HttpServlet類的init()方法,實現了自己的初始化行為。

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        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) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

這裡的initServletBean()方法在HttpServletBean類中是一個沒有任何實現的空方法,它的目的就是留待子類實現自己的初始化邏輯,也就是我們常說的模板方法設計模式。SpringMVC在此生動的運用了這個模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化過程,由子類FrameworkServlet中覆寫的initServletBean()方法觸發。

再看一下init()方法內被try,catch塊包裹的程式碼,裡面涉及到BeanWrapper,PropertyValues,ResourceEditor這些Spring內部非常底層的類。要深究具體程式碼實現上面的細節,需要對Spring框架原始碼具有相當深入的瞭解。我們這裡先避繁就簡,從程式碼效果和設計思想上面來分析這段try,catch塊內的程式碼所做的事情:

  • 註冊一個字串到資原始檔的編輯器,讓Servlet下面的
  • 將web.xml中在DispatcherServlet這個Servlet下面的

這兩點,我想通過下面一個例子來說明一下。

我在web.xml中註冊的DispatcherServlet配置如下:

<!-- springMVC配置開始 -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- springMVC配置結束 -->

可以看到,我註冊了一個名為contextConfigLocation的

另外一個作用,就是將contextConfigLocation的值讀取出來,然後通過setContextConfigLocation()方法設定到DispatcherServlet中,這個setContextConfigLocation()方法是在FrameworkServlet類中定義的,也就是上面繼承類圖中DispatcherServlet的直接父類。

我們在setContextConfigLocation()方法上面打上一個斷點,啟動web工程,可以看到下面的除錯結果。

HttpServletBean類的作者是大名鼎鼎的Spring之父Rod Johnson。作為POJO程式設計哲學的大師,他在HttpServletBean這個類的設計中,運用了依賴注入思想完成了

明白了HttpServletBean類的設計思想,我們也就知道可以如何從中獲益。具體來說,我們繼承HttpServletBean類(就像DispatcherServlet做的那樣),在類中定義一個屬性,為這個屬性加上setter方法後,我們就可以在

注意,雖然SpringMVC本身為了後面初始化上下文的方便,使用了字串來宣告和設定contextConfigLocation引數,但是將其宣告為Resource型別,同樣能夠成功獲取。鼓勵讀者們自己繼承HttpServletBean寫一個測試用的Servlet類,並設定一個引數來除錯一下,這樣能夠幫助你更好的理解獲取配置引數的過程。

容器上下文的建立

上一篇文章中提到過,SpringMVC使用了Spring容器來容納自己的配置元素,擁有自己的bean容器上下文。在SpringMVC初始化的過程中,非常關鍵的一步就是要建立起這個容器上下文,而這個建立上下文的過程,發生在FrameworkServlet類中,由上面init()方法中的initServletBean()方法觸發。

@Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

initFrameworkServlet()方法是一個沒有任何實現的空方法,除去一些樣板式的程式碼,那麼這個initServletBean()方法所做的事情已經非常明白:

this.webApplicationContext = initWebApplicationContext();

這一句簡單直白的程式碼,道破了FrameworkServlet這個類,在SpringMVC類體系中的設計目的,它是 用來抽離出建立 WebApplicationContext 上下文這個過程的。

initWebApplicationContext()方法,封裝了建立Spring容器上下文的整個過程,方法內的邏輯如下:

  1. 獲取由ContextLoaderListener初始化並註冊在ServletContext中的根上下文,記為rootContext
  2. 如果webApplicationContext已經不為空,表示這個Servlet類是通過程式設計式註冊到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由程式設計式傳入。若這個傳入的上下文還沒被初始化,將rootContext上下文設定為它的父上下文,然後將其初始化,否則直接使用。
  3. 通過wac變數的引用是否為null,判斷第2步中是否已經完成上下文的設定(即上下文是否已經用程式設計式方式傳入),如果wac==null成立,說明該Servlet不是由程式設計式註冊到容器中的。此時以contextAttribute屬性的值為鍵,在ServletContext中查詢上下文,查詢得到,說明上下文已經以別的方式初始化並註冊在contextAttribute下,直接使用。
  4. 檢查wac變數的引用是否為null,如果wac==null成立,說明2、3兩步中的上下文初始化策略都沒成功,此時呼叫createWebApplicationContext(rootContext),建立一個全新的以rootContext為父上下文的上下文,作為SpringMVC配置元素的容器上下文。大多數情況下我們所使用的上下文,就是這個新建的上下文。
  5. 以上三種初始化上下文的策略,都會回撥onRefresh(ApplicationContext context)方法(回撥的方式根據不同策略有不同),onRefresh方法在DispatcherServlet類中被覆寫,以上面得到的上下文為依託,完成SpringMVC中預設實現類的初始化。
  6. 最後,將這個上下文釋出到ServletContext中,也就是將上下文以一個和Servlet類在web.xml中註冊名字有關的值為鍵,設定為ServletContext的一個屬性。你可以通過改變publishContext的值來決定是否釋出到ServletContext中,預設為true。

以上面6點跟蹤FrameworkServlet類中的程式碼,可以比較清晰的瞭解到整個容器上下文的建立過程,也就能夠領會到FrameworkServlet類的設計目的,它是用來建立一個和Servlet關聯的Spring容器上下文,並將其註冊到ServletContext中的。跳脫開SpringMVC體系,我們也能通過繼承FrameworkServlet類,得到與Spring容器整合的好處,FrameworkServlet和HttpServletBean一樣,是一個可以獨立使用的類。整個SpringMVC設計中,處處體現開閉原則,這裡顯然也是其中一點。

初始化SpringMVC預設實現類

初始化流程在FrameworkServlet類中流轉,建立了上下文後,通過onRefresh(ApplicationContext context)方法的回撥,進入到DispatcherServlet類中。

@Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

DispatcherServlet類覆寫了父類FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各種程式設計元素的初始化。當然這些程式設計元素,都是作為容器上下文中一個個bean而存在的。具體的初始化策略,在initStrategies()方法中封裝。

protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        initHandlerMappings(context);
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        initViewResolvers(context);
        initFlashMapManager(context);
    }

我們以其中initHandlerMappings(context)方法為例,分析一下這些SpringMVC程式設計元素的初始化策略,其他的方法,都是以類似的策略初始化的。

private void initHandlerMappings(ApplicationContext context) {
        this.handlerMappings = null;

        if (this.detectAllHandlerMappings) {
            // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
            Map<String, HandlerMapping> matchingBeans =
                    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
            if (!matchingBeans.isEmpty()) {
                this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
                // We keep HandlerMappings in sorted order.
                OrderComparator.sort(this.handlerMappings);
            }
        }
        else {
            try {
                HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
                this.handlerMappings = Collections.singletonList(hm);
            }
            catch (NoSuchBeanDefinitionException ex) {
                // Ignore, we'll add a default HandlerMapping later.
            }
        }

        // Ensure we have at least one HandlerMapping, by registering
        // a default HandlerMapping if no other mappings are found.
        if (this.handlerMappings == null) {
            this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
            if (logger.isDebugEnabled()) {
                logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            }
        }
    }

detectAllHandlerMappings變數預設為true,所以在初始化HandlerMapping介面預設實現類的時候,會把上下文中所有HandlerMapping型別的Bean都註冊在handlerMappings這個List變數中。如果你手工將其設定為false,那麼將嘗試獲取名為handlerMapping的Bean,新建一個只有一個元素的List,將其賦給handlerMappings。如果經過上面的過程,handlerMappings變數仍為空,那麼說明你沒有在上下文中提供自己HandlerMapping型別的Bean定義。此時,SpringMVC將採用預設初始化策略來初始化handlerMappings。

點進去getDefaultStrategies看一下。

@SuppressWarnings("unchecked")
    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
        String key = strategyInterface.getName();
        String value = defaultStrategies.getProperty(key);
        if (value != null) {
            String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
            List<T> strategies = new ArrayList<T>(classNames.length);
            for (String className : classNames) {
                try {
                    Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                    Object strategy = createDefaultStrategy(context, clazz);
                    strategies.add((T) strategy);
                }
                catch (ClassNotFoundException ex) {
                    throw new BeanInitializationException(
                            "Could not find DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]", ex);
                }
                catch (LinkageError err) {
                    throw new BeanInitializationException(
                            "Error loading DispatcherServlet's default strategy class [" + className +
                                    "] for interface [" + key + "]: problem with class file or dependent class", err);
                }
            }
            return strategies;
        }
        else {
            return new LinkedList<T>();
        }
    }

它是一個範型的方法,承擔所有SpringMVC程式設計元素的預設初始化策略。方法的內容比較直白,就是以傳遞類的名稱為鍵,從defaultStrategies這個Properties變數中獲取實現類,然後反射初始化。

需要說明一下的是defaultStrategies變數的初始化,它是在DispatcherServlet的靜態初始化程式碼塊中載入的。

private static final Properties defaultStrategies;

    static {
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch (IOException ex) {
            throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
        }
    }
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

這個DispatcherServlet.properties裡面,以鍵值對的方式,記錄了SpringMVC預設實現類,它在spring-webmvc-3.1.3.RELEASE.jar這個jar包內,在org.springframework.web.servlet包裡面。

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

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

至此,我們分析完了initHandlerMappings(context)方法的執行過程,其他的初始化過程與這個方法非常類似。所有初始化方法執行完後,SpringMVC正式完成初始化,靜靜等待Web請求的到來。

總結

回顧整個SpringMVC的初始化流程,我們看到,通過HttpServletBean、FrameworkServlet、DispatcherServlet三個不同的類層次,SpringMVC的設計者將三種不同的職責分別抽象,運用模版方法設計模式分別固定在三個類層次中。其中HttpServletBean完成的是

相關推薦

SpringMVC原始碼分析3DispatcherServlet初始請求轉發

在我們第一次學Servlet程式設計,學java web的時候,還沒有那麼多框架。我們開發一個簡單的功能要做的事情很簡單,就是繼承HttpServlet,根據需要重寫一下doGet,doPost方法,跳轉到我們定義好的jsp頁面。Servlet類編寫完之後在web.xml裡註冊這個Servlet類。 除此之外

ABP原始碼分析ABP初始全過程

ABP在初始化階段做了哪些操作,前面的四篇文章大致描述了一下。 為個更清楚的描述其脈絡,做了張流程圖以輔助說明。其中每一步都涉及很多細節,難以在一張圖中全部表現出來。每一步的細節(會涉及到較多介面,類,呼叫關係,步驟流程什麼的)會在後面的文章中通過其他圖和文字詳細描述。其實如果仔細分析Abp原始碼的話,會發現

SpringMVC原始碼分析4DispatcherServlet如何找到正確的Controller

SpringMVC是目前主流的Web MVC框架之一。  我們使用瀏覽器通過地址 http://ip:port/contextPath/path進行訪問,SpringMVC是如何得知使用者到底是訪問哪個Controller中的方法,這期間到底發生了什麼。 本文將分析SpringMVC是如何處理請求與

《深入理解SPARK核心思想原始碼分析》——SparkContext的初始(仲篇)——SparkUI、環境變數及排程

《深入理解Spark:核心思想與原始碼分析》一書第一章的內容請看連結《第1章 環境準備》 《深入理解Spark:核心思想與原始碼分析》一書第二章的內容請看連結《第2章 SPARK設計理念與基本架構》 由於本書的第3章內容較多,所以打算分別開闢四篇隨筆分別展現。 本文展現第3章第二部分的內容:

《深入理解Spark核心思想原始碼分析》——SparkContext的初始(伯篇)——執行環境元資料清理器

《深入理解Spark:核心思想與原始碼分析》一書第一章的內容請看連結《第1章 環境準備》 《深入理解Spark:核心思想與原始碼分析》一書第二章的內容請看連結《第2章 SPARK設計理念與基本架構》 由於本書的第3章內容較多,所以打算分別開闢四篇隨筆分別展現。本文展現第3章第一部分的內容: 第3章

suricata 3.1 原始碼分析13 (流初始

簡介 Suricata中用於管理和維護流的模組稱為Flow Engine,主要由兩部分實現,第一部分的入口點是FlowHandlePacket函式,用於為新資料包進行流查詢/分配,另一部分是FlowManagerThread執行緒,用於對超時的流進行刪除。

SpringMVC原始碼分析2SpringMVC設計理念DispatcherServlet

轉自:https://my.oschina.net/lichhao/blog SpringMVC簡介 SpringMVC作為Struts2之後異軍突起的一個表現層框架,正越來越流行,相信javaee的開發者們就算沒使用過SpringMVC,也應該對其略有耳聞。我試圖通過對SpringMVC的設計思想和原始碼實

NSQ原始碼分析(一)——nsqd的初始及啟動流程

nsq原始碼地址:https://github.com/nsqio/nsq 版本1.1.0  NSQ原始碼分析系列是我通過閱讀nsq的原始碼及結合網上的相關文章整理而成,由於在網上沒有找到很詳細和完整的文章,故自己親自整理了一份。如果有錯誤的地方,還請指正,希望這系列的文章給您帶來

【kubernetes/k8s原始碼分析】kubelet原始碼分析之容器網路初始原始碼分析

一. 網路基礎   1.1 網路名稱空間的操作 建立網路名稱空間: ip netns add 名稱空間內執行命令: ip netns exec 進入名稱空間: ip netns exec bash   1.2 bridge-nf-c

dubbo原始碼分析-消費端啟動初始過程-筆記

消費端的程式碼解析是從下面這段程式碼開始的 <dubbo:reference id="xxxService" interface="xxx.xxx.Service"/> ReferenceBean(afterPropertiesSet) ->getObject() ->ge

【spring原始碼分析】IOC容器初始(二)

前言:在【spring原始碼分析】IOC容器初始化(一)中已經分析了匯入bean階段,本篇接著分析bean解析階段。 1.解析bean程式呼叫鏈 同樣,先給出解析bean的程式呼叫鏈: 根據程式呼叫鏈,整理出在解析bean過程中主要涉及的類和相關方法。 2.解析bean原始碼分

Android wpa_supplicant原始碼分析--啟動之全域性初始

1. wpa_supplicant簡介 wpa_supplicant是用來用來支援無線中各種加密方式的,包括WEP、WPA/WPA2和WAPI(中國特有)、EAP(8021x)。wpa_s通過socket與上層(framework)和底層(driver)通訊,向上接收命令和傳

SpringMVC原始碼分析1SpringMVC概述

轉載自:https://blog.csdn.net/a724888/article/details/76014532 Web MVC簡介 1.1、Web開發中的請求-響應模型: 在Web世界裡,具體步驟如下: 1、  Web瀏覽器(如IE)發起請求,如訪問http:/

LAV Filter 原始碼分析 3 LAV Video (1)

LAV Video 是使用很廣泛的DirectShow Filter。它封裝了FFMPEG中的libavcodec,支援十分廣泛的視訊格式的解碼。在這裡對其原始碼進行詳細的分析。LAV Video 工程程式碼的結構如下圖所示直接看LAV Video最主要的類CLAVVideo

dpdk 程式碼分析 記憶體初始

一  前言 http://www.dpdk.org/  dpdk 是 intel 開發的x86晶片上用於高效能網路處理的基礎庫,業內比較常用的模式是linux-app模式,即 利用該基礎庫,在使用者層空間做資料包處理,有了這個基礎庫,可以方便地在寫應用層的網路包處理高

RPC框架(六)dubbo原始碼分析--dubbo服務提供者初始

一、概述 dubbo服務提供者由dubbo:service來定義,從前面可以看到,Spring把dubbo:service解析成一個ServiceBean,ServiceBean實現了ApplicationListener和InitializingB

Android 5.0 Camera系統原始碼分析(3)Camera預覽流程控制流

1. 前言 本文分析的是Android系統原始碼,從frameworks層到hal層,記錄了Camera進入預覽模式的重點程式碼,主要為控制流程的程式碼,有關影象buffer的傳遞暫不涉及,硬體平臺基於mt6735。由於某些函式比較複雜,在貼出程式碼時會適當對

Lighttpd1.4.20原始碼分析 筆記 fdevent系統-初始

C程式在進行真正的編譯之前都要進行預編譯。 我們看看fdevent系統中的一些巨集: #if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H) # if defined HAVE_STD

jquery原始碼分析之jQuery物件初始

在jquery實際應用中可通過$或jQuery生成jquery物件,如$("#hr_three")可生成jquery物件,jquery是如何做到的呢?jquery載入時,入口為如下匿名方法,(function( global, factory ) {...} ([color=

rocketmq之原始碼分析broker入口BrokerController初始過程(十六)

接著上一章的BrokerController的基礎功能講,本章主要介紹的是BrokerController的初始化操作,在初始化的