【Spring】DispatcherServlet的啟動和初始化

来源:http://www.cnblogs.com/weknow619/archive/2017/08/16/7376125.html
-Advertisement-
Play Games

使用過SpringMVC的都知道DispatcherServlet,下麵介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯繫的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。Dis ...


使用過SpringMVC的都知道DispatcherServlet,下麵介紹下該Servlet的啟動與初始化。作為Servlet,DispatcherServlet的啟動與Serlvet的啟動過程是相聯繫的。在Serlvet的初始化過程程中,Serlvet的init方法會被調用,以進行初始化。DispatcherServlet的基類HttpServletBean中的這個初始化過程源碼如下:

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

    // 獲取Servlet的初始化參數,對Bean屬性進行配置
    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, getEnvironment()));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // 調用子類的initServletBean方法進行具體的初始化工作
    initServletBean();

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

// initServletBean這個初始化工作位於FrameworkServlet類中
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");
    }
}

protected WebApplicationContext initWebApplicationContext() {
    // 調用WebApplicationContextUtils來得到根上下文,它保存在ServletContext中
    // 使用它作為當前MVC上下文的雙親上下文
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        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) {
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        onRefresh(wac);
    }

    // 把當前建立的上下文存到ServletContext中,使用的屬性名是跟當前Servlet名相關的
    if (this.publishContext) {
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

在初始化開始時,需要讀取配置在ServletContext中的Bean屬性參數,這些屬性參數設置在web.xml的Web容器初始化參數中。

接著會執行DispatcherServlet持有的IOC容器的初始化過程,在這個過程中,一個新的上下文會被建立起來,這個DispatcherServlet持有的上下文被設置為根上下文的子上下文。可以這麼理解,根上下文是和web應用相對應的一個上下文,而DispatcherServlet持有的上下文是和Servlet對應的一個上下文。在一個web應用中可以容納多個Servlet存在;對應的,對於應用在web容器中的上下文體系,一個根上下文可以作為許多Serlvet上下文的雙親上下文。對這點的理解有助於在web環境中IOC容器的Bean設置和檢索有所幫助,因為在向IOC容器getBean時,IOC容器會先向其雙親上下文去getBean,換句話說就是在根上下文中定義的Bean是可以被各個Servlet持有的上下文得到和共用的。DispatcherServlet持有的上下文被建立後,也需要和其他 IOC容器一樣完成初始化操作,這個初始化操作就是通過refresh方法完成的,最後DispatcherServlet再給自己持有的上下文命名並設置到web視窗的上下文中(即ServletContext),這個名稱和在web.xml中設置的DispatcherServlet的Servlet名稱有關,進而保證這個上下文在web環境上下文體系中的唯一性。

上面代碼執行後,這個Servlet的上下文就建立起來了,具體取得根上下文的過程是在WebApplicationContextUtils中實現的,源碼如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE這個屬性代表的根上下文
    // 在ContextLoaderListener初始化的過程中被建立,並設置到ServletContext中
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

這個根上下文是ContextLoader設置到ServletContext中的,使用屬性ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同時對這個IOC容器的Bean配置文件,ContextLoader也進行了設置,預設的位置是在/WEB-INF/applicationContext.xml文件中,由於這個根上下文是DispatcherServlet建立的上下文的雙親上下文,所以根上下文中管理的bean是可以被DispatcherServlet的上下文使用的,反過來則不行,通過getBean向IOC容器獲取bean時,會先到其雙親IOC容器嘗試獲取。

回到FrameworkServlet繼續看DispatcherServlet的上下文是怎樣建立的,源碼如下:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    
    // 實例化需要的具體上下文對象,併為這個上下文對象設置屬性
    // 這裡使用的是DEFAULT_CONTEXT_CLASS,這個DEFAULT_CONTEXT_CLASS被設置為XmlWebApplicationContext.class,
    // 所以在DispatcherServlet中使用的IOC容器是XmlWebApplicationContext
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    // 設置雙親上下文(也就是根上下文)
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

建立DispatcherServlet的上下文,需要把根上下文作為參數傳遞給它,再使用反射實例化上下文對象,併為它設置參數,按預設配置的話這個上下文對象就是XmlWebApplicationContext對象,這個類型是在DEFAULT_CONTEXT_CLASS參數中設置好並允許BeanUtils使用的,實例化結束之後,還需要為這個上下文對象設置好一些基本的配置,這些配置包括它的雙親上下文、Bean定義配置的文件位置等,完成配置後就通過調用IOC容器的refresh方法來完成IOC容器的最終初始化。

通過上面web容器一系列的操作後,在這個上下文體系建立和初始化完畢的基礎上,Spring MVC就可以發揮作用了。此時DispatcherServlet就持有一個以自己的Servlet名稱命名的IOC容器,這個IOC容器建立後,意味著DispatcherServlet擁有自己的Bean定義空間,這為使用各個獨立的XML文件來配置MVC中各個Bean創建了條件,初始化完成後,Spring MVC的具體實現和普通的Spring應用程式的實現並沒有太多差別,在DispatcherServlet的初始化過程中,以對HandlerMapping的初始化調用作為觸發點,可以看下圖Spring MVC模塊初始化的方法調用關係圖,

這個調用關係最初由HttpServletBean的init方法觸發,這個HttpServletBean是HttpServlet的子類,接著會在HttpServletBean的子類FrameworkServlet中對IOC容器完成初始化,在這個初始化方法中會調用DispatcherServlet的initStrategies方法,在這個initStrategies方法中,啟動整個Spring MVC框架的初始化工作。

從上面的方法調用關係圖也可以看出對MVC的初始化是在DispatcherServlet的initStrategies中完成的,包括對各種MVC框架的實現元素,比如國際化支持LocalResolver、視圖生成的ViewResolver等的初始化工作,源碼如下:

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

上面各個初始化方法的名稱應該比較好理解,這裡以常見的HandlerMapping為例來說明initHandlerMappings()實現,Mapping映射的作用就是為HTTP請求找到相應的Controller控制器,HandlerMappings完成對MVC中Controller的定義和配置,DispatcherServlet中HandlerMappings初始化過程源碼如下:

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

    // 這裡導入所有的HandlerMapping Bean,這些Bean可以在當前的DispatcherServlet的IOC
    // 容器中,也可能在其雙親上下文中,這個detectAllHandlerMappings的預設值設為true,
    // 即預設地從所有的IOC容器中獲取
    if (this.detectAllHandlerMappings) {
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            OrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            // 可以根據名稱從當前的IOC容器中通過getBean獲取HandlerMapping
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
        }
    }

    // 如果沒找到HandlerMappings,那麼需要為Servlet設置預設的HandlerMappings,
    // 這些預設的值可以設置在DispatcherServlet.properties中
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

在HandlerMapping初始化的過程中,把在Bean配置文件中配置好的handlerMapping從IOC容器中取得。經過上面的讀取過程,handlerMappings變數就已經獲取了在BeanDefinition中配置好的映射關係。其他的初始化過程和handlerMappings比較類似,都是從IOC容器中讀入配置,所以說MVC初始化過程是建立在IOC容器已經初始化完成的基礎之上的。執行完其他的各個初始化操作後,整個初始化過程就基本完成了。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 安裝好環境後,開始了第一個Hello word 例子,如何讀取圖片,保存圖品 每天學習一點點,睡覺去了。 ...
  • 以前學過點 面向對象的知識,我感覺這之間是有關聯的,比如說裝飾器的第一個要素是對被裝飾的函數的封閉性,不允許更改;第二個就是對裝飾器本oj身的可擴展性。 裝飾器要點:高階函數+嵌套函數=裝飾器 需要掌握的知識點:1、函數即變數 2、高階函數(函數的參數也是函數) 3、嵌套函數 裝飾器一:裝飾器不帶參 ...
  • 今天寫這個是為了 提醒自己 編程過程 不僅要有邏輯 思想 還有要規範 代碼 這樣可讀性 感謝我牛神提供的文檔 1、PHP 編程規範與編碼習慣最主要的有以下幾點: 1 文件說明 2 function 函數體說明 3 代碼縮進 4 if省略 5 變數規範 6 命名規範 7 十行一註釋 8 註釋風格 9 ...
  • 虛擬機類載入機制 一、類載入的階段和時機 1.階段 整個生命周期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。 其中驗證、準 ...
  • 通過set()獲取兩個數組的交/並/差集: ...
  • 線程實現和線程安全性 參考博客:(http://www.importnew.com/20672.html),參考書籍:《Java併發編程實戰》 一、線程Java代碼實現 1.繼承Thread + 聲明Thread的子類 + 運行thread子類的方法 2.創建Thread的匿名子類 3.實現Runn ...
  • IP的獲取與轉換 1、前言 IP轉換成整型存儲是資料庫優化一大趨勢,字元串索引比整型索引消耗資源很多,特別是表中數據量大的時候,以及求查詢某一個ip段的數據。本文所指的IP是ip4,ip6暫不再討論範圍 2、ip4轉化為整形 這裡將介紹: php自帶函數 ip2long php原生模擬ip2long ...
  • 在php中有一個 serialize() 函數 可以把數組序列化成字元串進行存儲和傳輸 如果想反序列化這種字元串,在php中只需要一個簡單的unserialize() 函數就可以完成了.但是在golang中可就沒有這麼容易了,非得費個九牛二虎之力,寫上不少代碼才行。 這時候只想感嘆一下,php真的是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...