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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...