Web容器初始化過程

来源:https://www.cnblogs.com/xujian2014/archive/2018/09/17/9649822.html
-Advertisement-
Play Games

一、SpringMVC啟動過程 Spring的MVC是基於Servlet功能實現的,每個web工程中都有一個web.xml文件,web容器在啟動的時候會載入這個配置文件,當一個web應用載入到web容器中後,在web應用開始響應客戶端的請求之前,要按照順序執行下麵幾個步驟: 1、實例化部署描述符中的 ...


一、SpringMVC啟動過程

Spring的MVC是基於Servlet功能實現的,每個web工程中都有一個web.xml文件,web容器在啟動的時候會載入這個配置文件,當一個web應用載入到web容器中後,在web應用開始響應客戶端的請求之前,要按照順序執行下麵幾個步驟:

1、實例化部署描述符中的<listener>元素標識的時間監聽器的實例;

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

2、在實現了ServletContextListener介面的監聽器實例中,調用contextInitialized()方法。ContextLoaderListener實現了ServletContextListener介面,ServletContextListener作為Servlet的監聽者,會在ServletContext創建、銷毀過程中監聽ServletContextEvent事件,然後進行響應的處理。換句話說,就是在啟動web容器時,ContextLoaderListener類會自動裝配ApplicationContext的配置信息。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }
  // 這裡調用其父類ContextLoader的initWebApplicationContext進行初始化工作   
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
  
    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

  ContextLoaderListener類繼承了ContextLoader,後者是實際的ServletContextListener介面實現者,這裡用到了代理模式,利用ContextLoader類創建Spring application context,並將其註冊到ServletContext中去。ContextLoaderListener就是調用ContextLoader類的initWebApplicationContext()方法和closeWebApplicationContext()方法來實現ServletContext的創建和銷毀工作的。

  

   其中,initWebApplicationContext()方法主要完成了兩個部分的工作:一是查看是否已經指定父容器,如果不存在則設置其父容器。二是將Spring WebApplicationContext放置到線程Map中。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            Log logger = LogFactory.getLog(ContextLoader.class);
            servletContext.log("Initializing Spring root WebApplicationContext");
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }

            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
              //查看是否指定父容器
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }

                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }

                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
                //將WebApplicationContext放置到線程Map中
                if (ccl == ContextLoader.class.getClassLoader()) {
                    currentContext = this.context;
                } else if (ccl != null) {
                    currentContextPerThread.put(ccl, this.context);
                }

                if (logger.isDebugEnabled()) {
                    logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
                }

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

                return this.context;
            } catch (RuntimeException var8) {
                logger.error("Context initialization failed", var8);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
                throw var8;
            } catch (Error var9) {
                logger.error("Context initialization failed", var9);
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
                throw var9;
            }
        }
    }    

在初始化方法執行後,將WebApplicationContext存到了兩個地方,分別是ServletContext和currentContextPerThread中,我們可以通過這兩個地方來獲取WebApplicationContext:1.從servletContext中去拿;2.從當前的線程Map中去拿。

3、實例化部署符中<filter>元素標識的過濾器實例,並調用每個過濾器實例的init()方法。

4、實例化部署符中<servlet>元素標識的Servlet實例,按照該元素包含的load-on-startup順序,調用每個Servlet實例的init()方法。

  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

在SpringMVC的架構中,DispatcherServlet主要負責客戶端請求的分發,起到控制器的作用。DispatcherServlet是實現Servlet介面的實現類,其結構如下圖所示。Servlet的生命周期由Servlet的容器來控制,主要分為初始化、運行和銷毀三個階段,分別對應三個方法:init()、service()和destory()。

  在DispatcherServlet的父類HTTPServletBean中,重寫了init()方法,主要實現將當前的Servlet類實例轉換為BeanWrapper類型實例,以便使用Spring中提供的註入功能進行相應屬性的註入。

  在DispatcherServlet的父類FameworkServelet中,重寫了service()方法,對於不同的方法,Spring統一將程式引導至processRequest(request,response)中,processRequest方法再對處理請求進行了一些準備工作後,又將細節實現轉移到了doService方法中。

  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String method = request.getMethod();
        if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
            this.processRequest(request, response);
        } else {
            super.service(request, response);
        }

   }
  protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
        this.initContextHolders(request, localeContext, requestAttributes);
        try {
            //調用DispatcherServlet的doService()方法
            this.doService(request, response);
        } catch (ServletException var17) {
            failureCause = var17;
            throw var17;
        } catch (IOException var18) {
            failureCause = var18;
            throw var18;
        } catch (Throwable var19) {
            failureCause = var19;
            throw new NestedServletException("Request processing failed", var19);
        } finally {
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
            if (this.logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", (Throwable)failureCause);
                } else if (asyncManager.isConcurrentHandlingStarted()) {
                    this.logger.debug("Leaving response open for concurrent processing");
                } else {
                    this.logger.debug("Successfully completed request");
                }
            }
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
        }
    }        
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }

        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();
            label108:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label108;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }

        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

        try {
           // 最終的請求處理函數
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }

  經過層層的準備工作,最終在doDispatch()方法中實現了完整的請求處理過程:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                //根據request信息尋找對應的處理器
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null || mappedHandler.getHandler() == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                //根據處理器尋找對應的處理器適配器
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }

                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }

                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                   //返回視圖
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(request, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var19) {
                    dispatchException = var19;
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            } catch (Exception var20) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var20);
            } catch (Error var21) {
                this.triggerAfterCompletionWithError(processedRequest, response, mappedHandler, var21);
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }    

二、ServletContext、ApplicationContext和WebApplicationContext區別?

  1、ServletContext:是一個Web應用的上下文,是一個全局信息的存儲空間,代表當前的web應用。Servlet容器(如Tomcat)在啟動一個Webapp時,會為它創建一個ServletContext對象,即servlet上下文環境。每個webapp都有唯一的ServletContext對象。同一個webapp的所有servlet對象共用一個ServeltContext,servlet對象可以通過ServletContext來訪問容器中的各種資源。ServletContext在web應用啟動時創建,在web應用服務關閉時釋放。

   2、ApplicationContext:它是建立在BeanFactory基礎之上的,除了包含BeanFactory的所有功能之外,在國際化支持、資源訪問、事件傳播等方面進行了良好的支持。其體繫結構如下圖。

  ApplicationContext介面有三個常用的實現類,如下:

類名稱 功能描述
ClassPathXmlApplicationContext 從類路徑ClassPath中尋找指定的XML配置文件,找到並裝載 完成ApplicationContext的實例化工作
FileSystemXmlApplicationContext 從指定的文件系統路徑中尋找指定的XML配置文件,找到並裝載 完成ApplicationContext的實例化工作
XmlWebApplicationContext 從Web應用中尋找指定的XML配置文件,找到並裝載完成ApplicationContext的實例化工作

  與BeanFactory不同的是,ApplicationContext容器實例化後會自動對所有的單實例Bean進行實例化與依賴關係的裝配,使之處於待用狀態。而BeanFactory容器實例化後並不會自動實例化Bean,只有當Bean被使用時BeanFactory容器才會對該Bean進行實例化與依賴關係的裝配。

  3、WebApplicationContext:它是專門為web應用提供的,它允許從相對於web根目錄路徑中裝載配置文件完成初始化;從WebApplicationContext中可以獲得ServletContext的引用,同時為了方便web應用訪問Spring應用上下文,WebApplicationContext也將作為一個屬性放到ServletContext中,可以通過WebApplicationContextUtils的getWebApplicationContext(ServletContext sc)方法獲取。

三、總結

  ApplicationContext是Spring的核心,Context為上下文環境,在Web應用中,會用到WebApplicationContext,它繼承自ApplicationContext。在SpringMVC啟動過程中,ContextLoaderListener類起著至關重要的作用。它讀取web.xml中配置的context-param中的配置文件,提前在web容器初始化前準備業務對應的Application context,將創建好的WebApplicationContext放置於ServletContext屬性中,這樣我們只要得到Servlet就可以得到WebApplicationContext對象,並利用這個對象訪問spring容器管理的bean。

四、參考資料

  1、https://www.cnblogs.com/RunForLove/p/5688731.html

  2、spring源碼深度解析(郝佳)


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

-Advertisement-
Play Games
更多相關文章
  • 首發鏈接:https://bbs.huaweicloud.com/blogs/70f69ca4953111e89fc57ca23e93a89f 《一統江湖的大前端》系列是自己的前端學習筆記,旨在介紹javascript在非網頁開發領域的應用案例和發現各類好玩的js庫,不定期更新。如果你對前端的理解還 ...
  • 很精彩的一次內部分享,介紹了大部分的GC演算法理論知識,JVM博大精深,本篇文章只是結合本次內部分享總結的一些理論知識,如果有大佬有疑問,歡迎留言指出! Concurrent:併發,程式一邊運行一邊做GC Parallel:並行,一塊區域,一個人做清掃,需要100s,但是把區域分成兩塊,用兩個人掃,時 ...
  • 電腦端登錄公眾號管理後臺,【添加功能插件】開通客服功能,輸入"人工客服"接入客服熱線 底部有我的微信二維碼,如有問題,可加好友進行技術交流! ​ ​ ​ ​ ​ ​ ​ weixin-java-mp集成微信公眾號自帶客服功能代碼 增加TextBuilder.java文件 內容如下: public c ...
  • 博客園首頁是需要分享乾貨的地方,今天早上寫的《HRMS(人力資源管理系統)-從單機應用到SaaS應用-系統介紹》內容下架了,所以我就按照相關規定,只分享乾貨,我把之前寫完的架構設計相關知識的內容整理髮布上來。這次主要分享一下在架構設計過程中涉及的基礎知識,主要是涵蓋系統架構方法、架構模式及設計模式,... ...
  • 上周發佈的《2018,全新出發(全力推動實現住有所居》文章,其中記錄了個人在這5年過程中的成長和收穫,有幸認識了不少博客園的朋友,大家一起學習交流,在這個過程當中好多朋友提出SaaS系統如何設計,架構方面如何下手,在這5年的過程中我參與規劃設計了很多的SaaS系統其中有不少的坑和痛苦的經驗,特別是在... ...
  • RabbitMQ實戰教程(一) : 安裝及相關概念介紹 由於本人只在 安裝 服務 ,其他系統安裝暫時沒有涉及,如果有需要請自行搜索安裝教程. . . 1 . Windows 安裝 安裝需要先安裝 ,再安裝 第一步:安裝 ,由於 是用 編寫的,所以在安裝 之前要先安裝 下載地址: 下載最新版本即可,例 ...
  • 前言 此系列是針對springboot的啟動,旨在於和大家一起來看看springboot啟動的過程中到底做了一些什麼事。如果大家對springboot的源碼有所研究,可以挑些自己感興趣或者對自己有幫助的看;但是如果大家沒有研究過springboot的源碼,不知道springboot在啟動過程中做了些 ...
  • 元素在順序容器中的順序與其加入容器時的位置相對應。關聯容器中元素的位置由元素相關聯的關鍵字值決定。所有容器類都共用公共的介面,不同容器按不同方式對其進行擴展。 一個容器就是一些特定類型對象的集合。順序容器為程式員提供了控制元素存儲和訪問順序的能力。 1. 順序容器概述 容器的兩種性能: 向容器中添加 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...