Tomcat 7 中 web 應用加載原理(一)Context 構建

爲何關心 Tomcat 中一個 web 應用的加載過程?在前面的文章中看過屢次 Tomcat 的組件結構圖,這裏再貼出來回顧一下: web

以前的 Tomcat 7 啓動分析系列文章中看到 Tomcat 啓動的時候將會解析 server.xml,根據裏面所配置的各個節點信息逐一初始化和啓動相應組件(即分別調用它們的 init 和 start 方法),但瀏覽一下 Tomcat 7 源碼中的 server.xml 的內容,裏面對應上圖中的已經默認配置的各級組件包括 Server、Service、Engine、Connector、Host、Valve 。上圖中的 Context 組件實際就是咱們一般所說的一個 web 應用,有趣的是在 server.xml 中並無配置該組件,而咱們默認啓動的時候實際上已經有好幾個 web 應用能夠訪問了: 這個究竟是怎麼回事?

看過前面的Tomcat 7 的一次請求分析系列文章的人應該知道,瀏覽器一次請求送到 Tomcat 服務器以後,最終會根據瀏覽器中的 url 路徑找到相應的實際要訪問的 web 應用的 context 對象(默認即org.apache.catalina.core.StandardContext類的實例)。好比訪問的 url 爲http://localhost:8080/,那麼將會送到上圖 ROOT 文件夾表示的 web 應用中,訪問的 url 爲http://localhost:8080/docs,那麼將訪問 docs 文件夾表示的 web 應用。因此可以猜到的是在 Tomcat 啓動完成後,一定容器內部已經構造好了表示相應web應用的各個 context 對象。apache

本文就對這個問題一探究竟。在Tomcat 7 服務器關閉原理的開頭提到,默認的配置下 Tomcat 啓動完以後會看到後臺實際上總共有 6 個線程在運行: 瀏覽器

前面的幾篇文章中涉及了 mainhttp-bio-8080-Acceptor-0http-bio-8080-AsyncTimeoutajp-bio-8009-Acceptor-0ajp-bio-8009-AsyncTimeout,已經談到了這些線程的做用,它們是如何產生並響應請求的。但有一個線程沒有說,即 ContainerBackgroundProcessor[StandardEngine[Catalina]],而本文要解答的問題奧祕就在這個線程之中。

先看看這個線程是如何產生的,其實從命名就能夠看出一些端倪,它叫作容器後臺處理器,而且跟 StandardEngine 關聯起來,它的產生於做用也一樣如此。bash

Tomcat 7 中全部的默認容器組件( StandardEngine、StandardHost、StandardContext、StandardWrapper )都會繼承父類org.apache.catalina.core.ContainerBase,在這些容器組件啓動時將會調用本身內部的 startInternal 方法,在該方法內部通常會調用父類的 startInternal 方法( StandardContext 類的實現除外),好比org.apache.catalina.core.StandardEngine類中的 startInternal 方法: 服務器

最後的 super.startInternal()即調用父類 org.apache.catalina.core.ContainerBase的startInternal方法,在該方法最後:
第 6 行設置了 LifecycleState.STARTING狀態(這樣將向容器發佈一個 Lifecycle.START_EVENT事件),這一行的做用本文後面會提到,暫且按下不表。第 9 行調用 threadStart 方法,看看 threadStart 方法的代碼:
這裏能夠看到若是兩個前置校驗條件經過的話將會啓動一個線程,而且線程的名字即以 ContainerBackgroundProcessor[開頭,線程名字後面取的是對象的 toString 方法,以 StandardEngine 爲例,看看 org.apache.catalina.core.StandardEngine的 toString 方法實現:
以上解釋了這個後臺線程的來歷。

但這裏有一個問題,既然 StandardEngine、StandardHost 都會調用super.startInternal()方法,按默認配置,後臺理應產生兩個後臺線程,實際爲何只有一個?app

回到org.apache.catalina.core.ContainerBase的 threadStart 方法,在啓動線程代碼以前有兩個校驗條件: 函數

容器組件對象初始化時 thread 爲 null,backgroundProcessorDelay 是 -1
org.apache.catalina.core.StandardEngine在其自身構造函數中作了一點修改:
構造函數最後將父類的 backgroundProcessorDelay 的值由 -1改爲了 10,因此 Tomcat 啓動解析 xml 時碰到一個 Engine 節點就會對應產生一個後臺處理線程。

講完了這個後臺處理線程的產生,看看這個線程所做的事情,再看下這個線程的啓動代碼: post

因此這個線程將會執行 ContainerBase 的內部類 ContainerBackgroundProcessor 的 run 方法,看下 ContainerBackgroundProcessor 的所有實現代碼:
在它的 run 方法暫停一段時間以後會調用 processChildren 方法,而 processChildren 方法作了兩件事,一是調用容器組件自身的 backgroundProcess 方法,而是取出該容器組件的全部子容器組件並調用它們的 processChildren 方法。歸結起來這個線程的實現就是按期經過遞歸的方式調用當前容器及其全部子容器的 backgroundProcess 方法。

而這個 backgroundProcess 方法在 ContainerBase 內部已經給出了實現: url

這段代碼就不一一解釋了,歸納起來講就是逐個調用與容器相關其它內部組件的 backgroundProcess 方法。最後註冊一個 Lifecycle.PERIODIC_EVENT事件。

上面就是 Tomcat 7 的後臺處理線程所做的事情的概述,在 Tomcat 的早期版本中有一些後臺處理的事情原來是在各個組件內部分別自定義一個線程並啓動,在 Tomcat 5 中改爲了全部後臺處理共享同一線程的方式。spa

回到本文要解答的問題,web 應用如何加載到容器中的?在 ContainerBase 類的 backgroundProcess 方法的最後:

fireLifecycleEvent(Lifecycle.PERIODIC_EVENT, null);  
複製代碼

向容器註冊了一個PERIODIC_EVENT事件。前面說道默認的ContainerBackgroundProcessor[StandardEngine[Catalina]]線程會按期(默認爲 10 秒)執行 Engine、Host、Context、Wrapper 各容器組件及與它們相關的其它組件的 backgroundProcess 方法,因此也會按期向 Host 組件發佈一個PERIODIC_EVENT事件,這裏看下 StandardHost 都會關聯的一個監聽器org.apache.catalina.startup.HostConfig

在 Tomcat 啓動解析 xml 時org.apache.catalina.startup.Catalina類的 386 行:digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"))

在 HostRuleSet 類的 addRuleInstances 方法中:

第 9 到 12 行看到,全部 Host 節點都會添加一個org.apache.catalina.startup.HostConfig對象做爲org.apache.catalina.core.StandardHost對象的監聽器

在 HostConfig 的 lifecycleEvent 方法中能夠看到若是 Host 組件收到了 Lifecycle.PERIODIC_EVENT 事件的發佈所做出的響應(若是對 Tomcat 7 的 Lifecycle 機制不清楚能夠看下Tomcat 7 啓動分析(五)Lifecycle 機制和實現原理

第 17 行,若是發佈的事件是 PERIODIC_EVENT將會執行 check 方法。第 19 行,若是發佈的事件是 START_EVENT則執行 start 方法。check 方法和 start 方法最後都會調用 deployApps() 方法,看下這方法的實現:
這裏即各類不一樣方式發佈 web 應用的代碼。

本文前面提到默認狀況下組件啓動的時候會發佈一個Lifecycle.START_EVENT事件(在org.apache.catalina.core.ContainerBase類的 startInternal 方法倒數第二行),回到 HostConfig 的 lifecycleEvent 方法中,因此默認啓動時將會執行 HostConfig 的 start 方法,在該方法的最後:

if (host.getDeployOnStartup())  
    deployApps();
複製代碼

由於默認配置 host.getDeployOnStartup() 返回 true ,這樣容器就會在啓動的時候直接加載相應的 web 應用。

固然,若是在 server.xml 中 Host 節點的 deployOnStartup 屬性設置爲 false ,則容器啓動時不會加載應用,啓動完以後不能當即提供 web 應用的服務。但由於有上面提到的後臺處理線程在運行,會按期執行 HostConfig 的 check 方法:

若是 Host 節點的 autoDeploy 屬性是 true(默認設置即爲 true ),能夠看到 check 方法最後一樣會加載 web 應用。
相關文章
相關標籤/搜索