爲何關心 Tomcat 中一個 web 應用的加載過程?在前面的文章中看過屢次 Tomcat 的組件結構圖,這裏再貼出來回顧一下: 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 個線程在運行: 瀏覽器
main
、
http-bio-8080-Acceptor-0
、
http-bio-8080-AsyncTimeout
、
ajp-bio-8009-Acceptor-0
、
ajp-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
方法,在該方法最後:
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 方法,在啓動線程代碼以前有兩個校驗條件: 函數
null
,backgroundProcessorDelay 是
-1
org.apache.catalina.core.StandardEngine
在其自身構造函數中作了一點修改:
-1
改爲了
10
,因此 Tomcat 啓動解析 xml 時碰到一個 Engine 節點就會對應產生一個後臺處理線程。
講完了這個後臺處理線程的產生,看看這個線程所做的事情,再看下這個線程的啓動代碼: post
而這個 backgroundProcess 方法在 ContainerBase 內部已經給出了實現: url
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 機制和實現原理:
PERIODIC_EVENT
將會執行 check 方法。第 19 行,若是發佈的事件是
START_EVENT
則執行 start 方法。check 方法和 start 方法最後都會調用 deployApps() 方法,看下這方法的實現:
本文前面提到默認狀況下組件啓動的時候會發佈一個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 方法: