您好,登錄后才能下訂單哦!
這篇文章主要講解了“Tomcat架構及啟動過程是怎樣的”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Tomcat架構及啟動過程是怎樣的”吧!
Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未發布,它實現了 Servlet4.0 及 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,詳情請查閱 Tomcat-9.0-doc
Tomcat-9.0-dochttps://tomcat.apache.org/tomcat-9.0-doc/index.html
Overview
Bootstrap 作為 Tomcat 對外界的啟動類,在 $CATALINA_BASE/bin 目錄下,它通過反射創建 Catalina 的實例并對其進行初始化及啟動。
Catalina 解析 $CATALINA_BASE/conf/server.xml 文件并創建 StandardServer、StandardService、StandardEngine、StandardHost 等
StandardServer 代表的是整個 Servlet 容器,他包含一個或多個 StandardService
StandardService 包含一個或多個 Connector,和一個 Engine,Connector 和 Engine 都是在解析 conf/server.xml 文件時創建的,Engine 在 Tomcat 的標準實現是 StandardEngine
MapperListener 實現了 LifecycleListener 和 ContainerListener 接口用于監聽容器事件和生命周期事件。該監聽器實例監聽所有的容器,包括 StandardEngine、StandardHost、StandardContext、StandardWrapper,當容器有變動時,注冊容器到 Mapper。
Mapper 維護了 URL 到容器的映射關系。當請求到來時會根據 Mapper 中的映射信息決定將請求映射到哪一個 Host、Context、Wrapper。
Http11NioProtocol 用于處理 HTTP/1.1 的請求
NioEndpoint 是連接的端點,在請求處理流程中該類是核心類,會重點介紹。
CoyoteAdapter 用于將請求從 Connctor 交給 Container 處理。使 Connctor 和 Container 解耦。
StandardEngine 代表的是 Servlet 引擎,用于處理 Connector 接受的 Request。包含一個或多個 Host(虛擬主機), Host 的標準實現是 StandardHost。
StandardHost 代表的是虛擬主機,用于部署該虛擬主機上的應用程序。通常包含多個 Context (Context 在 Tomcat 中代表應用程序)。Context 在 Tomcat 中的標準實現是 StandardContext。
StandardContext 代表一個獨立的應用程序,通常包含多個 Wrapper,一個 Wrapper 容器封裝了一個 Servlet,Wrapper的標準實現是 StandardWrapper。
StandardPipeline 組件代表一個流水線,與 Valve(閥)結合,用于處理請求。 StandardPipeline 中含有多個 Valve, 當需要處理請求時,會逐一調用 Valve 的 invoke 方法對 Request 和 Response 進行處理。特別的,其中有一個特殊的 Valve 叫 basicValve,每一個標準容器都有一個指定的 BasicValve,他們做的是最核心的工作。
StandardEngine 的是 StandardEngineValve,他用來將 Request 映射到指定的 Host;
StandardHost 的是 StandardHostValve, 他用來將 Request 映射到指定的 Context;
StandardContext 的是 StandardContextValve,它用來將 Request 映射到指定的 Wrapper;
StandardWrapper 的是 StandardWrapperValve,他用來加載 Rquest 所指定的 Servlet,并調用 Servlet 的 Service 方法。
Tomcat init
當通過 ./startup.sh 腳本或直接通過 java 命令來啟動 Bootstrap 時,Tomcat 的啟動過程就正式開始了,啟動的入口點就是 Bootstrap 類的 main 方法。
啟動的過程分為兩步,分別是 init 和 start,本節主要介紹 init;
初始化類加載器。[關于 Tomcat 類加載機制,可以參考我之前寫的一片文章:談談Java類加載機制]
通過從 CatalinaProperties 類中獲取 common.loader 等屬性,獲得類加載器的掃描倉庫。CatalinaProperties 類在的靜態塊中調用了 loadProperties() 方法,從 conf/catalina.properties 文件中加載了屬性.(即在類創建的時候屬性就已經加載好了)。
通過 ClassLoaderFactory 創建 URLClassLoader 的實例
通過反射創建 Catalina 的實例并設置 parentClassLoader
setAwait(true)。設置 Catalina 的 await 屬性為 true。在 Start 階段尾部,若該屬性為 true,Tomcat 會在 main 線程中監聽 SHUTDOWN 命令,默認端口是 8005.當收到該命令后執行 Catalina 的 stop() 方法關閉 Tomcat 服務器。
createStartDigester()。Catalina 的該方法用于創建一個 Digester 實例,并添加解析 conf/server.xml 的 RuleSet。Digester 原本是 Apache 的一個開源項目,專門解析 XML 文件的,但我看 Tomcat-9.0.0.M22 中直接將這些類整合到 Tomcat 內部了,而不是引入 jar 文件。Digester 工具的原理不在本文的介紹范圍,有興趣的話可以參考 The Digester Component – Apache 或 《How Tomcat works》- Digester [推薦] 一章
parse() 方法就是 Digester 處理 conf/server.xml 創建各個組件的過程。值的一提的是這些組件都是使用反射的方式來創建的。特別的,在創建 Digester 的時候,添加了一些特別的 rule Set,用于創建一些十分核心的組件,這些組件在 conf/server.xml 中沒有但是其作用都比較大,這里做下簡單介紹,當 Start 時用到了再詳細說明:
EngineConfig。LifecycleListener 的實現類,觸發 Engine 的生命周期事件后調用,這個監聽器沒有特別大的作用,就是打印一下日志
HostConfig。LifecycleListener 的實現類,觸發 Host 的生命周期事件后調用。這個監聽器的作用就是部署應用程序,這包括 conf/
ContextConfig。LifecycleListener 的實現類,觸發 Context 的生命周期事件時調用。這個監聽器的作用是配置應用程序,它會讀取并合并 conf/web.xml 和 應用程序的 web.xml,分析 /WEB-INF/classes/ 和 /WEB-INF/lib/*.jar中的 Class 文件的注解,將其中所有的 Servlet、ServletMapping、Filter、FilterMapping、Listener 都配置到 StandardContext 中,以備后期使用。當然了 web.xml 中還有一些其他的應用程序參數,最后都會一并配置到 StandardContext 中。
reconfigureStartStopExecutor() 用于重新配置啟動和停止子容器的 Executor。默認是 1 個線程。我們可以配置 conf/server.xml 中 Engine 的 startStopThreads,來指定用于啟動和停止子容器的線程數量,如果配置 0 的話會使用 Runtime.getRuntime().availableProcessors() 作為線程數,若配置為負數的話會使用 Runtime.getRuntime().availableProcessors() + 配置值,若和小與 1 的話,使用 1 作為線程數。當線程數是 1 時,使用 InlineExecutorService 它直接使用當前線程來執行啟動停止操作,否則使用 ThreadPoolExecutor 來執行,其最大線程數為我們配置的值。
需要注意的是 Host 的 init 操作是在 Start 階段來做的, StardardHost 創建好后其 state 屬性的默認值是 LifecycleState.NEW,所以在其調用 startInternal() 之前會進行一次初始化。
Tomcat Start[Deployment]
圖中從 StandardHost Start StandardContext 的這步其實在真正的執行流程中會直接跳過,因為 conf/server.xml 文件中并沒有配置任何的 Context,所以在 findChildren() 查找子容器時會返回空數組,所以之后遍歷子容器來啟動子容器的 for 循環就直接跳過了。
觸發 Host 的 BEFORE_START_EVENT 生命周期事件,HostConfig 調用其 beforeStart() 方法創建 $CATALINA_BASE/webapps& $CATALINA_BASE/conf/
觸發 Host 的 START_EVENT 生命周期事件,HostConfig 調用其 start() 方法開始部署已在 $CATALINA_BASE/webapps & $CATALINA_BASE/conf/
解析 $CATALINA_BASE/conf/
部署 $CATALINA_BASE/webapps 下所有的 WAR 文件,并添加到 StandardHost。
部署 $CATALINA_BASE/webapps 下所有已解壓的目錄,并添加到 StandardHost。
特別的,添加到 StandardHost 時,會直接調用 StandardContext 的 start() 方法來啟動應用程序。啟動應用程序步驟請看 Context Start 一節。
在 StandardEngine 和 StandardContext 啟動時都會調用各自的 threadStart() 方法,該方法會創建一個新的后臺線程來處理該該容器和子容器及容器內各組件的后臺事件。StandardEngine 會直接創建一個后臺線程,StandardContext 默認是不創建的,和 StandardEngine 共用同一個。后臺線程處理機制是周期調用組件的 backgroundProcess() 方法。詳情請看 Background process 一節。
MapperListener
addListeners(engine) 方法會將該監聽器添加到 StandardEngine 和它的所有子容器中
registerHost() 會注冊所有的 Host 和他們的子容器到 Mapper 中,方便后期請求處理時使用。
當有新的應用(StandardContext)添加進來后,會觸發 Host 的容器事件,然后通過 MapperListener 將新應用的映射注冊到 Mapper 中。
Start 工作都做完以后 Catalina 會創建一個 CatalinaShutdownHook 并注冊到 JVM。CatalinaShutdownHook 繼承了 Thread,是 Catalina 的內部類。其 run 方法中直接調用了 Catalina 的 stop() 方法來關閉整個服務器。注冊該 Thread 到 JVM 的原因是防止用戶非正常終止 Tomcat,比如直接關閉命令窗口之類的。當直接關閉命令窗口時,操作系統會向 JVM 發送一個終止信號,然后 JVM 在退出前會逐一啟動已注冊的 ShutdownHook 來關閉相應資源。
Context Start
StandRoot 類實現了 WebResourceRoot 接口,它容納了一個應用程序的所有資源,通俗的來說就是部署到 webapps 目錄下對應 Context 的目錄里的所有資源。因為我對 Tomcat 的資源管理部分暫時不是很感興趣,所以資源管理相關類只是做了簡單了解,并沒有深入研究源代碼。
resourceStart() 方法會對 StandardRoot 進行初始配置
postWorkDirectory() 用于創建對應的工作目錄 $CATALINA_BASE/work/
StardardContext 只是一個容器,而 ApplicationContext 則是一個應用程序真正的運行環境,相關類及操作會在請求處理流程看完以后進行補充。
StardardContext 觸發 CONFIGURE_START_EVENT 生命周期事件,ContextConfig 開始調用 configureStart() 對應用程序進行配置。
這個過程會解析并合并 conf/web.xml & conf/
配置配置文件中的參數到 StandardContext, 其中主要的包括 Servlet、Filter、Listener。
因為從 Servlet3.0 以后是直接支持注解的,所以服務器必須能夠處理加了注解的類。Tomcat 通過分析 WEB-INF/classes/ 中的 Class 文件和 WEB-INF/lib/ 下的 jar 包將掃描到的 Servlet、Filter、Listerner 注冊到 StandardContext。
setConfigured(true),是非常關鍵的一個操作,它標識了 Context 的成功配置,若未設置該值為 true 的話,Context 會啟動失敗。
Background process
后臺進程的作用就是處理一下 Servlet 引擎中的周期性事件,處理周期默認是 10s。
特別的 StandardHost 的 backgroundProcess() 方法會觸發 Host 的 PERIODIC_EVENT 生命周期事件。然后 HostConfig 會調用其 check() 方法對已加載并進行過重新部署的應用程序進行 reload 或對新部署的應用程序進行熱部署。熱部署跟之前介紹的部署步驟一致, reload() 過程只是簡單的順序調用 setPause(true)、stop()、start()、setPause(false),其中 setPause(true) 的作用是暫時停止接受請求。
How to read excellent open source projects
真正的第一次閱讀開源項目源代碼,收獲還是很大的。讓我在架構設計、面向對象思想、設計模式、Clean Code等等各個方面都有了進步。閱讀優秀的開源項目其實是一件很爽的事,因為時不時的會發現一個新的設計思路,然后不由自主的感嘆一聲居然還可以這樣!當然了,讀的時候還是會有一些痛點的,比如說碰到一個變量,但是死活就是找不到初始化的位置,有時通過 Find Usage 工具可以找到,但有些找不到的只能從頭開始再過一邊源碼。有時碰到一個設計思路死活都想不明白為什么這樣設計等等,這種情況就只能通過分析更高一層的架構來解決了等等。
下面我簡單分享一下我是如何閱讀開源項目源碼的。
先找一些介紹該項目架構的書籍來看,項目架構是項目核心中的核心,讀架構讀的是高層次的設計思路,讀源碼讀的是低層次的實現細節。有了高層次的設計思路做指導,源碼讀起來才會得心應手,因為讀的時候心里很清楚現在在讀的源碼在整個項目架構中處于什么位置。我在讀 Tomcat 源碼之前先把 《How Tomcat works》 一書過了一邊,然后又看了一下 《Tomcat 架構解析》 的第二章,對 Tomcat 的架構有了初步了解。(PS:《How Tomcat works》一書是全英文的,但讀起來非常流暢,雖然它是基于 Tomcat 4 和 5 的,但 Tomcat 架構沒有非常大的變化,新版的 Tomcat 只是增加了一些組件,如果你要學習 Tomcat 的話,首推這本書!)
如果實在找不到講架構的書,那就自己動手畫類圖吧!一般來說,開源項目都是為了提供服務的,我們把提供服務的流程作為主線來分析源代碼,這樣目的性會更強一些,將該流程中涉及到的類畫到類圖中,最后得到的類圖就是架構!不過分析之前你要先找到流程的入口點,否則分析就無從開始。以 Tomcat 為例,他的主線流程大致可以分為 3 個:啟動、部署、請求處理。他們的入口點就是 Bootstrap 類和 接受請求的 Acceptor 類!
有了閱讀思路我們下面來說說工具吧。我使用的閱讀工具是 IntelliJ IDEA,一款十分強大的 IDE,可能比較重量級,如果你有其他更加輕量級的 Linux 平臺源碼閱讀工具,可以推薦給我~
Structure 欄目可以自定義列出類中的域、方法,然后還可以按照繼承結構對域和方法進行分組,這樣就可以直接看出來域和方法是在繼承結構中哪個類里定義的。當你點擊方法和域時,還可以自動滾動到源代碼等等。
在源代碼中 點擊右鍵 -> Diagrams -> show Diagram 可以顯示類的繼承結構,圖中包含了該類所有的祖先和所有的接口。在該圖中選擇指定的父類和接口,點擊右鍵 -> show Implementations, IDEA 會列出接口的實現類或該類的子類。
FindUsage、Go To Declaration 等等就不再多說了。
感謝各位的閱讀,以上就是“Tomcat架構及啟動過程是怎樣的”的內容了,經過本文的學習后,相信大家對Tomcat架構及啟動過程是怎樣的這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。