interface21 - web - DispatcherServlet(DispatcherServlet初始化流程)

来源:https://www.cnblogs.com/chenpi/archive/2018/08/26/9539451.html
-Advertisement-
Play Games

前言 最近打算花點時間好好看看spring的源碼,然而現在Spring的源碼經過迭代的版本太多了,比較龐大,看起來比較累,所以準備從最初的版本(interface21)開始入手,僅用於學習,理解其設計思想,後續慢慢研究其每次版本變更的內容。。。 先從interface21的一個典型web工程例子看起 ...


前言 

最近打算花點時間好好看看spring的源碼,然而現在Spring的源碼經過迭代的版本太多了,比較龐大,看起來比較累,所以準備從最初的版本(interface21)開始入手,僅用於學習,理解其設計思想,後續慢慢研究其每次版本變更的內容。。。

先從interface21的一個典型web工程例子看起,寵物診所 - petclinic,因為該工程基本涵蓋了Spring的APO、IOC、JDBC、Web MVC、事務、國際化、主題切換、參數校驗等主要功能。。。

繼上一篇,瞭解完ContextLoaderListener(載入Spring Web Application Context)的流程後,看看Spring mvc的關鍵控制器 - DispatcherServlet初始化流程是如何的~~~~~~~

對應的web.xml配置

    <servlet>
        <servlet-name>petclinic</servlet-name>
        <servlet-class>com.interface21.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>petclinic</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>

這裡有一個地方需要註意下,load-on-startup的配置,關於該Servlet參數的含義,可以先看下web-app_2_3.dtd文件中的解釋,如下,大致意思就是,如果沒配置load-on-startup或該值為負數,則servlet容器可以自由選擇什麼時候載入該servlet(實例化並執行Servlet的init方法),如果為0或正數,則必須在容器啟動時載入Servlet並執行其init方法,且數值越小的Servlet優先載入,回過頭看我們的DispatcherServlet,由於load-on-startup配置成1,所以會在啟動的時候,執行init方法,即本文要關註的DispatcherServlet初始化流程:

<!--
The load-on-startup element indicates that this servlet should be
loaded (instantiated and have its init() called) on the startup
of the web application. The optional contents of
these element must be an integer indicating the order in which
the servlet should be loaded. If the value is a negative integer,
or the element is not present, the container is free to load the
servlet whenever it chooses. If the value is a positive integer
or 0, the container must load and initialize the servlet as the
application is deployed. The container must guarantee that
servlets marked with lower integers are loaded before servlets
marked with higher integers. The container may choose the order
of loading of servlets with the same load-on-start-up value.

Used in: servlet
-->

執行時序圖(看不清的話可以點擊查看原圖)

時序圖中的各個步驟簡要分析

執行的入口在HttpServletBean類的init方法,由於DispatcherServlet的load-on-startup參數配置成1,所以在Servlet容器(tomcat)啟動時,會自動調用該Servlet的init方法。

步驟描述:

  1. 首先,執行DispatcherServlet的父類HttpServletBean的init方法;
    public final void init() throws ServletException {
            this.identifier = "Servlet with name '" + getServletConfig().getServletName() + "' ";
    
            logger.info(getIdentifier() + "entering init...");
    
            // Set bean properties
            try {
                PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), requiredProperties);
                BeanWrapper bw = new BeanWrapperImpl(this);
                bw.setPropertyValues(pvs);
                logger.debug(getIdentifier() + "properties bound OK");
    
                // Let subclasses do whatever initialization they like
                initServletBean();
                logger.info(getIdentifier() + "configured successfully");
            } catch (BeansException ex) {
                String mesg = getIdentifier() + ": error setting properties from ServletConfig";
                logger.error(mesg, ex);
                throw new ServletException(mesg, ex);
            } catch (Throwable t) {
                // Let subclasses throw unchecked exceptions
                String mesg = getIdentifier() + ": initialization error";
                logger.error(mesg, t);
                throw new ServletException(mesg, t);
            }
        }
  2. 獲取Servlet的初始化參數,創建BeanWrapperImpl 實例,設置屬性值;
  3. 執行HttpServletBean的子類FrameworkServlet的initServletBean方法;
        protected final void initServletBean() throws ServletException {
            long startTime = System.currentTimeMillis();
            logger.info("Framework servlet '" + getServletName() + "' init");
            this.webApplicationContext = createWebApplicationContext();
            initFrameworkServlet();
            long elapsedTime = System.currentTimeMillis() - startTime;
            logger.info("Framework servlet '" + getServletName() + "' init completed in " + elapsedTime + " ms");
        }
  4. 調用FrameworkServlet的createWebApplicationContext方法。
        private WebApplicationContext createWebApplicationContext() throws ServletException {
            getServletContext().log("Loading WebApplicationContext for servlet '" + getServletName() + "'");
            ServletContext sc = getServletConfig().getServletContext();
            WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(sc);
            String namespace = getNamespace();
    
            WebApplicationContext waca = (this.contextClass != null) ?
                    instantiateCustomWebApplicationContext(this.contextClass, parent, namespace) :
                    new XmlWebApplicationContext(parent, namespace);
            logger.info("Loading WebApplicationContext for servlet '" + getServletName() + "': using context class '" + waca.getClass().getName() + "'");
            waca.setServletContext(sc);
    
            if (this.publishContext) {
                // Publish the context as a servlet context attribute
                String attName = getServletContextAttributeName();
                sc.setAttribute(attName, waca);
                logger.info("Bound context of servlet '" + getServletName() + "' in global ServletContext with name '" + attName + "'");
            }
            return waca;
        }
  5. 進入createWebApplicationContext方法,從ServletContext的屬性中獲取WebApplicationContext,該上下文是由ContextLoaderListener載入的
  6. 創建子上下文XmlWebApplicationContext,其父上下文為之前ContextLoaderListener載入的WebApplicationContext,關於這兩個上下文的關係,及負責載入的內容,可參考這張圖,圖片來源(http://jinnianshilongnian.iteye.com/blog/1602617/
  7. 執行子上下文XmlWebApplicationContext的waca.setServletContext方法,去載入petclinic-servlet.xml配置文件(國際化,主題,HandlerMapping、HandlerAdapter、視圖解析等相關配置),關於WebApplicationContext上下文的載入流程,可參考上一篇(Spring Web Application Context載入流程),流程都是相同的;
  8. 將該子上下文設置到ServletContext屬性中
  9. 進入DispatcherServlet類的initFrameworkServlet方法,主要執行一些初始化工作
        protected void initFrameworkServlet() throws ServletException {
            initLocaleResolver();
            initThemeResolver();
            initHandlerMappings();
            initHandlerAdapters();
            initViewResolver();
        }
  10. 國際化相關:執行initLocaleResolver方法,從上下文中獲取localeResolver bean,沒有則使用預設AcceptHeaderLocaleResolver
        private void initLocaleResolver() throws ServletException {
            try {
                this.localeResolver = (LocaleResolver) getWebApplicationContext().getBean(LOCALE_RESOLVER_BEAN_NAME);
                logger.info("Loaded locale resolver [" + this.localeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.localeResolver = new AcceptHeaderLocaleResolver();
                logger.info("Unable to locate locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the LocaleResolver specified by a bean
                throw new ServletException("Fatal error loading locale resolver with name '" + LOCALE_RESOLVER_BEAN_NAME + "': using default", ex);
            }
        }
  11. 主題相關:執行initThemeResolver方法,從上下文中獲取themeResolver bean,沒有則使用預設FixedThemeResolver
        private void initThemeResolver() throws ServletException {
            try {
                this.themeResolver = (ThemeResolver) getWebApplicationContext().getBean(THEME_RESOLVER_BEAN_NAME);
                logger.info("Loaded theme resolver [" + this.themeResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.themeResolver = new FixedThemeResolver();
                logger.info("Unable to locate theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default [" + this.themeResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ThemeResolver specified by a bean
                throw new ServletException("Fatal error loading theme resolver with name '" + THEME_RESOLVER_BEAN_NAME + "': using default", ex);
            }
        }
  12. 執行initHandlerMappings方法,從上下文中獲取HandlerMapping類型的bean,沒有則使用預設BeanNameUrlHandlerMapping
        private void initHandlerMappings() throws ServletException {
            this.handlerMappings = new ArrayList();
    
            // Find all HandlerMappings in the ApplicationContext
            String[] hms = getWebApplicationContext().getBeanDefinitionNames(HandlerMapping.class);
            for (int i = 0; i < hms.length; i++) {
                initHandlerMapping(hms[i]);
                logger.info("Loaded handler mapping [" + hms[i] + "]");
            }
    
            // Ensure we have at least one HandlerMapping, by registering
            // a default HandlerMapping if no other mappings are found.
            if (this.handlerMappings.isEmpty()) {
                initDefaultHandlerMapping();
                logger.info("No HandlerMappings found in servlet '" + getServletName() + "': using default");
            } else {
                // We keep HandlerMappings in sorted order
                Collections.sort(this.handlerMappings, new OrderComparator());
            }
        }
        private void initDefaultHandlerMapping() throws ServletException {
            try {
                HandlerMapping hm = new BeanNameUrlHandlerMapping();
                hm.setApplicationContext(getWebApplicationContext());
                this.handlerMappings.add(hm);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerMapping: " + ex.getMessage(), ex);
            }
        }
  13. 執行initHandlerAdapters方法,從上下文中獲取HandlerAdapter類型的bean,沒有則使用預設SimpleControllerHandlerAdapter
        private void initHandlerAdapters() throws ServletException {
            this.handlerAdapters = new ArrayList();
    
            String[] has = getWebApplicationContext().getBeanDefinitionNames(HandlerAdapter.class);
            for (int i = 0; i < has.length; i++) {
                initHandlerAdapter(has[i]);
                logger.info("Loaded handler adapter [" + has[i] + "]");
            }
    
            // Ensure we have at least one HandlerAdapter, by registering
            // a default HandlerAdapter if no other adapters are found.
            if (this.handlerAdapters.isEmpty()) {
                initDefaultHandlerAdapter();
                logger.info("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
            } else {
                // We keep HandlerAdapters in sorted order
                Collections.sort(this.handlerAdapters, new OrderComparator());
            }
        }
        private void initDefaultHandlerAdapter() throws ServletException {
            try {
                HandlerAdapter ha = new SimpleControllerHandlerAdapter();
                ha.setApplicationContext(getWebApplicationContext());
                this.handlerAdapters.add(ha);
            } catch (ApplicationContextException ex) {
                throw new ServletException("Error initializing default HandlerAdapter: " + ex.getMessage(), ex);
            }
        }
  14. 執行initViewResolver方法,從上下文中獲取viewResolver bean,沒有則使用預設InternalResourceViewResolver
        private void initViewResolver() throws ServletException {
            try {
                this.viewResolver = (ViewResolver) getWebApplicationContext().getBean(VIEW_RESOLVER_BEAN_NAME);
                logger.info("Loaded view resolver [" + viewResolver + "]");
            } catch (NoSuchBeanDefinitionException ex) {
                // We need to use the default
                this.viewResolver = new InternalResourceViewResolver();
                try {
                    this.viewResolver.setApplicationContext(getWebApplicationContext());
                } catch (ApplicationContextException ex2) {
                    throw new ServletException("Fatal error initializing default ViewResolver");
                }
                logger.info("Unable to locate view resolver with name '" + VIEW_RESOLVER_BEAN_NAME + "': using default [" + this.viewResolver + "]");
            } catch (BeansException ex) {
                // We tried and failed to load the ViewResolver specified by a bean
                throw new ServletException("Fatal error loading view resolver: bean with name '" + VIEW_RESOLVER_BEAN_NAME + "' is required in servlet '" + getServletName() + "': using default", ex);
            }
        }
  15. 返回到FrameworkServlet類
  16. 返回到HttpServletBean類
  17. Servlet的init方法執行完畢

另外可以關註下該Servlet的銷毀方法,類似的,也是執行一些資源銷毀等操作,銷毀工廠創建的單例bean對象,發佈ContextClosedEvent事件等;

    public void destroy() {
        getServletContext().log("Closing WebApplicationContext for servlet '" + getServletName() + "'");
        getWebApplicationContext().close();
    }
    public void close() {
        logger.info("Closing application context [" + getDisplayName() + "]");

        // destroy all cached singletons in this context,
        // invoking DisposableBean.destroy and/or "destroy-method"
        getBeanFactory().destroySingletons();

        // publish respective event
        publishEvent(new ContextClosedEvent(this));
    }

interface21代碼參考

 https://github.com/peterchenhdu/interface21


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

-Advertisement-
Play Games
更多相關文章
  • items.py setting.py pipelines.py 爬蟲文件 tuchong.py ...
  • 一.概述 相比起C和C++的自己回收記憶體,JAVA要方便得多,因為JVM會為我們自動分配記憶體以及回收記憶體。 在之前的 "JVM 之記憶體管理" 中,我們介紹了JVM記憶體管理的幾個區域,其中程式計數器以及虛擬機棧是線程私有的,隨線程而滅,故而它是不用考慮垃圾回收的,因為線程結束其記憶體空間即釋放。 而JA ...
  • 如何安裝Python的操作步驟: 1.第一步先去python的官方網站下載python的安裝包 地址:https://www.python.org/downloads/ 根據自己的系統選擇對應的安裝包 2.下載完成之後,點擊進行安裝 直接雙擊進行運行即可,會彈出如下界面,點擊運行就行 然後出現如下界 ...
  • 本文主要分三個部分,首先簡單介紹csrf,接著對照源碼重點分析一下yii框架的驗證原理,最後針對頁面緩存導致的token被緩存提出一種可行的方案。涉及的知識點會作為附錄附於文末。 1.CSRF描述 CSRF全稱為“Cross-Site Request Forgery”,是在用戶合法的SESSION內 ...
  • 一.python2 python2中預設以ASCII編碼 二.python3 python3文本預設是以Unicode編碼 感覺此處內容有些複雜,不敢確認上述內容是否正確,還請路過的前輩補充指點。 ...
  • 1. 列表和普通變數有什麼區別 列表是數據類型,普通變數是用來存儲數據的 可以把列表賦值給普通變數 2.存在列表 a = [11, 22, 33], 如何向列表中添加(增)新元素 44 a.append(44) 或者 a.insert(3,44) #索引號為3 3.對列表排序 a = [11,22, ...
  • 題目描述 寫出一個程式,接受一個由字母和數字組成的字元串,和一個字元,然後輸出輸入字元串中含有該字元的個數。不區分大小寫。 輸入描述: 輸入一個有字母和數字以及空格組成的字元串,和一個字元。 輸出描述: 輸出輸入字元串中含有該字元的個數。 輸入 ABCDEF A 輸出 1 #include<iost ...
  • 題目描述 計算字元串最後一個單詞的長度,單詞以空格隔開。 輸入描述: 一行字元串,非空,長度小於5000。 輸出描述: 整數N,最後一個單詞的長度。 示例1 輸入 hello world 輸出 5 #include<iostream> #include<string.h> using namespa ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...