tomcat60源碼
『壹』 Tomcat篇02-整體架構和I/O模型
本文主要包括tomcat伺服器的目錄結構、工作模式、整體架構、I/O模型以及NIO、NIO2、APR三者的對比介紹。
我們先來看一下tomcat8.5和tomcat9中的home目錄中的文件:
可以看到除掉一些說明文件之後,還有7個目錄:
實際上除了主目錄里有lib目錄,在webapps目錄下的web應用中的WEB-INF目錄下也存在一個lib目錄:
兩者的區別在於:
●Tomcat主目錄下的lib目錄:存放的JAR文件 不僅能被Tomcat訪問,還能被所有在Tomcat中發布的java Web應用訪問
●webapps目錄下的Java Web應用的lib目錄:存放的JAR文件 只能被當前Java Web應用訪問
既然有多個lib目錄,那麼肯定就有使用的優先順序,Tomcat類載入器的目錄載入優先順序如下:
Tomcat的類載入器負責為Tomcat本身以及Java Web應用載入相關的類。假如Tomcat的類載入器要為一個Java Web應用載入一個類,類載入器會按照以下優先順序到各個目錄中去查找該類的.class文件,直到找到為止,如果所有目錄中都不存在該類的.class文件,則會拋出異常:
Tomcat不僅可以單獨運行,還可以與其他的Web伺服器集成,作為其他Web伺服器的進程內或進程外的servlet容器。集成的意義在於:對於不支持運行Java Servlet的其他Web伺服器,可通過集成Tomcat來提供運行Servlet的功能。
Tomcat有三種工作模式:
我們先從tomcat的源碼目錄來分析一下tomcat的整體架構,前面我們配置jsvc運行tomcat的時候,我們知道tomcat中啟動運行的最主要的類是 org.apache.catalina.startup.Bootstrap ,那麼我們在tomcat的源碼中的java目錄下的org目錄的apache目錄可以找到主要的源碼的相對應的類。
圖中的目錄如果畫成架構圖,可以這樣表示:
Tomcat 本質上就是一款Servlet 容器,因此 catalina 才是Tomcat的核心 ,其他模塊都是為 catalina 提供支撐的。
單線程阻塞I/O模型是最簡單的一種伺服器I/O模型,單線程即同時只能處理一個客戶端的請求,阻塞即該線程會一直等待,直到處理完成為止。對於多個客戶端訪問,必須要等到前一個客戶端訪問結束才能進行下一個訪問的處理,請求一個一個排隊,只提供一問一答服務。
如上圖所示:這是一個同步阻塞伺服器響應客戶端訪問的時間節點圖。
這種模型的特點在於單線程和阻塞I/O。 單線程即伺服器端只有一個線程處理客戶端的所有請求,客戶端連接與伺服器端的處理線程比是 n:1 ,它無法同時處理多個連接,只能串列處理連接。而阻塞I/O是指伺服器在讀寫數據時是阻塞的,讀取客戶端數據時要等待客戶端發送數據並且把操作系統內核復制到用戶進程中,這時才解除阻塞狀態。寫數據回客戶端時要等待用戶進程將數據寫入內核並發送到客戶端後才解除阻塞狀態。 這種阻塞帶來了一個問題,伺服器必須要等到客戶端成功接收才能繼續往下處理另外一個客戶端的請求,在此期間線程將無法響應任何客戶端請求。
該模型的特點:它是最簡單的伺服器模型,整個運行過程都只有一個線程,只能支持同時處理一個客戶端的請求(如果有多個客戶端訪問,就必須排隊等待), 伺服器系統資源消耗較小,但並發能力低,容錯能力差。
多線程阻塞I/O模型在單線程阻塞I/O模型的基礎上對其進行改進,加入多線程,提高並發能力,使其能夠同時對多個客戶端進行響應,多線程的核心就是利用多線程機制為每個客戶端分配一個線程。
如上圖所示,伺服器端開始監聽客戶端的訪問,假如有兩個客戶端同時發送請求過來,伺服器端在接收到客戶端請求後分別創建兩個線程對它們進行處理,每條線程負責一個客戶端連接,直到響應完成。 期間兩個線程並發地為各自對應的客戶端處理請求 ,包括讀取客戶端數據、處理客戶端數據、寫數據回客戶端等操作。
這種模型的I/O操作也是阻塞的 ,因為每個線程執行到讀取或寫入操作時都將進入阻塞狀態,直到讀取到客戶端的數據或數據成功寫入客戶端後才解除阻塞狀態。盡管I/O操作阻塞,但這種模式比單線程處理的性能明顯高了,它不用等到第一個請求處理完才處理第二個,而是並發地處理客戶端請求,客戶端連接與伺服器端處理線程的比例是 1:1 。
多線程阻塞I/O模型的特點:支持對多個客戶端並發響應,處理能力得到大幅提高,有較大的並發量,但伺服器系統資源消耗量較大,而且如果線程數過多,多線程之間會產生較大的線程切換成本,同時擁有較復雜的結構。
在探討單線程非阻塞I/O模型前必須要先了解非阻塞情況下套接字事件的檢測機制,因為對於單線程非阻塞模型最重要的事情是檢測哪些連接有感興趣的事件發生。一般會有如下三種檢測方式。
當多個客戶端向伺服器請求時,伺服器端會保存一個套接字連接列表中,應用層線程對套接字列表輪詢嘗試讀取或寫入。如果成功則進行處理,如果失敗則下次繼續。這樣不管有多少個套接字連接,它們都可以被一個線程管理,這很好地利用了阻塞的時間,處理能力得到提升。
但這種模型需要在應用程序中遍歷所有的套接字列表,同時需要處理數據的拼接,連接空閑時可能也會佔用較多CPU資源,不適合實際使用。
這種方式將套接字的遍歷工作交給了操作系統內核,把對套接字遍歷的結果組織成一系列的事件列表並返回應用層處理。對於應用層,它們需要處理的對象就是這些事件,這是一種事件驅動的非阻塞方式。
伺服器端有多個客戶端連接,應用層向內核請求讀寫事件列表。內核遍歷所有套接字並生成對應的可讀列表readList和可寫列表writeList。readList和writeList則標明了每個套接字是否可讀/可寫。應用層遍歷讀寫事件列表readList和writeList,做相應的讀寫操作。
內核遍歷套接字時已經不用在應用層對所有套接字進行遍歷,將遍歷工作下移到內核層,這種方式有助於提高檢測效率。 然而,它需要將所有連接的可讀事件列表和可寫事件列表傳到應用層,假如套接字連接數量變大,列表從內核復制到應用層也是不小的開銷。 另外,當活躍連接較少時, 內核與應用層之間存在很多無效的數據副本 ,因為它將活躍和不活躍的連接狀態都復制到應用層中。
通過遍歷的方式檢測套接字是否可讀可寫是一種效率比較低的方式,不管是在應用層中遍歷還是在內核中遍歷。所以需要另外一種機制來優化遍歷的方式,那就是 回調函數 。內核中的套接字都對應一個回調函數,當客戶端往套接字發送數據時,內核從網卡接收數據後就會調用回調函數,在回調函數中維護事件列表,應用層獲取此事件列表即可得到所有感興趣的事件。
內核基於回調的事件檢測方式有兩種
第一種是用 可讀列表readList 和 可寫列表writeList 標記讀寫事件, 套接字的數量與 readList 和 writeList 兩個列表的長度一樣 。
上面兩種方式由操作系統內核維護客戶端的所有連接並通過回調函數不斷更新事件列表,而應用層線程只要遍歷這些事件列表即可知道可讀取或可寫入的連接,進而對這些連接進行讀寫操作,極大提高了檢測效率,自然處理能力也更強。
單線程非阻塞I/O模型最重要的一個特點是,在調用讀取或寫入介面後立即返回,而不會進入阻塞狀態。雖然只有一個線程,但是它通過把非阻塞讀寫操作與上面幾種檢測機制配合就可以實現對多個連接的及時處理,而不會因為某個連接的阻塞操作導致其他連接無法處理。在客戶端連接大多數都保持活躍的情況下,這個線程會一直循環處理這些連接,它很好地利用了阻塞的時間,大大提高了這個線程的執行效率。
單線程非阻塞I/O模型的主要優勢體現在對多個連接的管理,一般在同時需要處理多個連接的發場景中會使用非阻塞NIO模式,此模型下只通過一個線程去維護和處理連接,這樣大大提高了機器的效率。一般伺服器端才會使用NIO模式,而對於客戶端,出於方便及習慣,可使用阻塞模式的套接字進行通信。
在多核的機器上可以通過多線程繼續提高機器效率。最朴實、最自然的做法就是將客戶端連接按組分配給若干線程,每個線程負責處理對應組內的連接。比如有4個客戶端訪問伺服器,伺服器將套接字1和套接字2交由線程1管理,而線程2則管理套接字3和套接字4,通過事件檢測及非阻塞讀寫就可以讓每個線程都能高效處理。
多線程非阻塞I/O模式讓伺服器端處理能力得到很大提高,它充分利用機器的CPU,適合用於處理高並發的場景,但它也讓程序更復雜,更容易出現問題(死鎖、數據不一致等經典並發問題)。
最經典的多線程非阻塞I/O模型方式是Reactor模式。首先看單線程下的Reactor,Reactor將伺服器端的整個處理過程分成若干個事件,例如分為接收事件、讀事件、寫事件、執行事件等。Reactor通過事件檢測機制將這些事件分發給不同處理器去處理。在整個過程中只要有待處理的事件存在,即可以讓Reactor線程不斷往下執行,而不會阻塞在某處,所以處理效率很高。
基於單線程Reactor模型,根據實際使用場景,把它改進成多線程模式。常見的有兩種方式:一種是在耗時的process處理器中引入多線程,如使用線程池;另一種是直接使用多個Reactor實例,每個Reactor實例對應一個線程。
Reactor模式的一種改進方式如下圖所示。其整體結構基本上與單線程的Reactor類似,只是引入了一個線程池。由於對連接的接收、對數據的讀取和對數據的寫入等操作基本上都耗時較少,因此把它們都放到Reactor線程中處理。然而,對於邏輯處理可能比較耗時的工作,可以在process處理器中引入線程池,process處理器自己不執行任務,而是交給線程池,從而在Reactor線程中避免了耗時的操作。將耗時的操作轉移到線程池中後,盡管Reactor只有一個線程,它也能保證Reactor的高效。
Reactor模式的另一種改進方式如下圖所示。其中有多個Reactor實例,每個Reactor實例對應一個線程。因為接收事件是相對於伺服器端而言的,所以客戶端的連接接收工作統一由一個accept處理器負責,accept處理器會將接收的客戶端連接均勻分配給所有Reactor實例,每個Reactor實例負責處理分配到該Reactor上的客戶端連接,包括連接的讀數據、寫數據和邏輯處理。這就是多Reactor實例的原理。
Tomcat支持的I/O模型如下表(自8.5/9.0 版本起,Tomcat移除了對BIO的支持),在 8.0 之前 , Tomcat 默認採用的I/O方式為 BIO , 之後改為 NIO。 無論 NIO、NIO2 還是 APR, 在性能方面均優於以往的BIO。
Tomcat中的NIO模型是使用的JAVA的NIO類庫,其內部的IO實現是同步的(也就是在用戶態和內核態之間的數據交換上是同步機制),採用基於selector實現的非同步事件驅動機制(這里的非同步指的是selector這個實現模型是使用的非同步機制)。 而對於Java來說,非阻塞I/O的實現完全是基於操作系統內核的非阻塞I/O,它將操作系統的非阻塞I/O的差異屏蔽並提供統一的API,讓我們不必關心操作系統。JDK會幫我們選擇非阻塞I/O的實現方式。
NIO2和前者相比的最大不同就在於引入了非同步通道來實現非同步IO操作,因此也叫AIO(Asynchronous I/O)。NIO.2 的非同步通道 APIs 提供方便的、平台獨立的執行非同步操作的標准方法。這使得應用程序開發人員能夠以更清晰的方式來編寫程序,而不必定義自己的 Java 線程,此外,還可通過使用底層 OS 所支持的非同步功能來提高性能。如同其他 Java API 一樣,API 可利用的 OS 自有非同步功能的數量取決於其對該平台的支持程度。
非同步通道提供支持連接、讀取、以及寫入之類非鎖定操作的連接,並提供對已啟動操作的控制機制。Java 7 中用於 Java Platform(NIO.2)的 More New I/O APIs,通過在 java.nio.channels 包中增加四個非同步通道類,從而增強了 Java 1.4 中的 New I/O APIs(NIO),這些類在風格上與 NIO 通道 API 很相似。他們共享相同的方法與參數結構體,並且大多數對於 NIO 通道類可用的參數,對於新的非同步版本仍然可用。主要區別在於新通道可使一些操作非同步執行。
非同步通道 API 提供兩種對已啟動非同步操作的監測與控制機制。第一種是通過返回一個 java.util.concurrent.Future 對象來實現,它將會建模一個掛起操作,並可用於查詢其狀態以及獲取結果。第二種是通過傳遞給操作一個新類的對象, java.nio.channels.CompletionHandler ,來完成,它會定義在操作完畢後所執行的處理程序方法。每個非同步通道類為每個操作定義 API 副本,這樣可採用任一機制。
Apache可移植運行時(Apache Portable Runtime,APR) 是Apache HTTP伺服器的支持庫,最初,APR是作為Apache HTTP伺服器的一部分而存在的,後來成為一個單獨的項目。其他的應用程序可以使用APR來實現平台無關性(跨平台)。APR提供了一組映射到下層操作系統的API,如果操作系統不支持某個特定的功能,APR將提供一個模擬的實現。這樣程序員使用APR編寫真正可在不同平台上移植的程序。
順利安裝完成後會顯示apr的lib庫路徑,一般都是 /usr/local/apr/lib
安裝完成之後我們還需要修改環境變數和配置參數
這里我們使用的是systemd調用jsvc來啟動tomcat,所以我們直接在systemd對應的tomcat的unit文件中的 ExecStart 中添加一個路徑參數 -Djava.library.path=/usr/local/apr/lib 指向apr庫的路徑:
然後我們在tomcat的home目錄下的conf子目錄中對server.xml文件進行修改
把8080埠對應的配置修改成apr:(其他埠配置也類似)
重啟tomcat服務我們從tomcat的日誌中就可以看到協議已經從默認的NIO變成了apr。
NIO性能是最差的這是毋庸置疑的,如果是考慮到高並發的情況,顯然非同步非阻塞I/O模式的NIO2和APR庫在性能上更有優勢,實際上NIO2的性能表現也和APR不相上下,但是NIO2要求Tomcat的版本要在8.0以上,而APR只需要5.5以上即可,但是APR需要額外配置庫環境,相對於內置集成的NIO2來說APR這個操作比較麻煩,兩者各有優劣。具體使用哪個還是需要結合實際業務需求和環境進行測試才能決定。
『貳』 如何學習Tomcat的源碼
方法/步驟
第一步 下載安裝fiddler,fiddler2和4的版本對電腦裡面net版本有要求。
第二步 打開fiddler 2,fiddler會及時抓取正在進行網路通訊的所以信息
第三步 因為網頁後台會有自動刷新的進程,所以再抓取特頂網頁COOKIE時候,先按住CTRL+X清楚現在記錄的網頁
第四步 以小米網站為例,刷新該網站。然後就會看見fiddler已經在記錄一系列的網頁,選定小米官網
第五步 然後在右側的屬性詳情欄,即可以找到【cookie】
『叄』 Tomcat源碼解析-容器組件之StandardHost
Container容器用來表示tomcat中servlet容器,負責servelt的載入和管理,處理請求ServletRequest,並返回標準的 ServletResponse 對象給連接器。
Container容器組件
tomcat 將Container容器按功能分為4個組件,分別是 Engine、Host、Context 和 Wrapper。這 4 種容器不是平行關系,而是父子關系。
Lifecycle介面定義tomcat中所有組件的生命周期相關介面方法。Tomcat 定義一個基類LifecycleBase 來實現 Lifecycle 介面,把一些公共的邏輯放到基類中實現。而子類就負責實現自己的初始化、啟動和停止等模板方法。
詳見 Tomcat架構設計-組件生命周期 Lifecycle
Container介面定義tomcat中所有容器組件的通用介面方法。Tomcat 定義一個基類ContainerBase 來實現Container 介面,把一些公共的邏輯放到基類中實現。
詳見 Tomcat架構設計-容器組件基類 ContainerBase
在tomcat中最核心功能就是將一個靜態資源目錄或一個應用程序部署到容器中。而這個容器就是指得Host容器組件。而靜態資源或一個應用程序通過Context容器組件來表示。所謂部署就是載入到Host容器的子組件中。當然虛擬主機除了部署外還又其他功能,包括熱部署,懶載入,別名等。
如果想要將一個靜態資源目錄部署到Tomcat伺服器上,tomcat提供了多種部署方式
在server.xml中配置
path表示Context根路徑,docBase表示映射靜態資源目錄
在xmlBase路徑下配置xml文件
在$CATALINA_BASE/xmlBase 路徑下創建 JavaWebApp.xml,xmlBase配置在Host標簽屬性中
文件名稱表示Context根路徑,docBase表示映射靜態資源目錄
將資源文件拷貝到appBase路徑下
appBase路徑在Host標簽屬性中定義,文件名稱表示Context根路徑。
部署應用程序到appBase目錄
appBase是在server.xml文件Host標簽appBase屬性來定義,appBase可以填寫相對路徑或者絕對路徑,如果是相對路徑那麼完整路徑為 CATALINA_BASE表示tomcat的工作目錄
虛擬主機Host可以在設置在使用時在部署靜態資源或應用程序。
虛擬主機Host會定期檢查appBase和xmlBase目錄下新Web應用程序或靜態資源,如果發生更新則會觸發對應context組件的重新載入
虛擬主機Host可以定義別名。
StandardHost並管理子容器Context組件,以及從父類ContainerBase,LifecycleBase 繼承的通用組件。
StandardHost實現了Host介面,在了解StandardHost功能之前我們需要了解
Host介面.
Host介面
StandardHost實現Host介面,Host介面用來對Tomcat中虛擬主機功能配置提供了訪問方法。
StandardHost只對虛擬機功能配置做了定義 ,其具體實現由HostConfig來實現。同時負責管理子容器Context組件(下圖藍色),以及從父類ContainerBase(下圖紅色),LifecycleBase(下圖黃色) 繼承的通用組件。
tomcat中所有組件都需要經歷如下流程。
Tomcat使用Digester解析server.xml,Digester是一款用於將xml轉換為Java對象的事件驅動型工具,是對SAX的高層次的封裝。相對於SAX,Digester可以針對每一個xml標簽設置對應的解析規則。詳見 Tomcat相關技術-Digester(二)
Tomcat在Catalina組件初始化階段調用createStartDigester()創建Digester對象,Digester對象內包含解析server.xml規則,接著通過Digester對象解析server.xml實例化StandardHost,並對部分屬性設置值.
server.xml配置
解析<Host>標簽及子標簽tomcat使用規則組HostRuleSet,其中定義了解析規則。
CopyParentClassLoaderRule規則
CopyParentClassLoaderRule規則,負責調用次棧頂對象getParentClassLoader獲取父類載入,設置到棧頂對象parentClassLoader屬性上
LifecycleListenerRule規則
LifecycleListenerRule 規則負責給棧頂對象添加一個生命周期監聽器.
接下來初始化開始則進入tomcat組件的生命周期,對於tomcat中所有組件都必須實現Lifecycle,Tomcat 定義一個基類LifecycleBase 來實現 Lifecycle 介面,把一些公共的邏輯放到基類中實現,比如生命狀態的轉變與維護、生命事件的觸發以及監聽器的添加和刪除等,而子類就負責實現自己的初始化、啟動和停止等模板方法。為了避免跟基類中的方法同名,我們把具體子類的實現方法改個名字,在後面加上 Internal,叫 initInternal、startInternal 等。
StandardHost父類對容器的初始化、啟動和停止等模板方法進行的了默認實現。子類容器只需要重寫父類實現即可實現擴展。
StandardEngine其他生命周期實現均從父類ContainerBase繼承。
為添加的子容器設置生命周期監聽器MemoryLeakTrackingListener
每一個容器組件都有一個 Pipeline 對象,Pipeline 中維護了 Valve 鏈表,默認時每一個Pipeline存放了一個默認的BasicValue,
這里每一個Value表示一個處理點,當調用addValve 方法時會將添加Vaule添加到鏈表頭部,Pipeline 中沒有 invoke
方法,請求處理時Pipeline只需要獲取鏈表中第一個Valve調用incoke去執行,執行完畢後當前Value會調用
getNext.invoke() 來觸發下一個 Valve 調用
每一個容器在執行到最後一個默認BasicValue時,會負責調用下層容器的 Pipeline 里的第一個 Valve
對於StandardHost容器來說默認情況存在三個Value(閥門),分別是 AccessLogValve (構建時讀取server.xml時), StandardHostValve (構建實例化時), ErrorReportValve (啟動時)。
記錄訪問日誌,這里是一個通用組件,後續會由專題講解
『肆』 怎麼看J2EE的源碼
sun的jdk中並沒有包含servlet源代碼,也就是你關聯了jdk中的src後還是不能夠查看servlet的源代碼的。servlet是在tomcat有實現的,所以我們只要下載tomcat的源碼就可以查看servlet的源碼了。
下面是步驟:
1. 進入tomcat官方的下載tomcat源碼,一般對應你的版本,例如我的版本是tomcat 6.X,那麼我現在tomcat 6.x的src包下載.
2. 進入官網下載自己配套的src源碼
3. 進入到你自己寫的servlet的程序代碼中
4. 按住crtl, 然後用滑鼠點擊servlet的類的方法,關聯下那個源代碼就可以了。那個源代碼就是你下的那個tomcat源碼
『伍』 Tomcat之NioEndpoint源碼分析
NioEndpoint涉及tomcat服務端是如何處理客戶端的連接及數據處理的模型
endpoint#start方法啟動調用鏈
org.apache.catalina.startup.Bootstrap#main
org.apache.catalina.startup.Bootstrap#start
org.apache.catalina.startup.Catalina#start
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.core.StandardServer#startInternal
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.core.StandardService#startInternal
org.apache.catalina.util.LifecycleBase#start
org.apache.catalina.connector.Connector#startInternal
org.apache.coyote.AbstractProtocol#start
org.apache.tomcat.util.net.AbstractEndpoint#start
開始啟動NIO的endpoint
①、創建executor執行器線程池
corePoolSize核心大小是10,最大maximumPoolSize是200的ThreadPoolExecutor
while (endpoint.isRunning()) {}
一直循環直到接收到一個shutdown 命令(Loop until we receive a shutdown command)
1、獲取客戶端連接的socket對象
2、把socket對象設置到endpoint中
根據NioChannel創建newWrapper對象
重置NioChannel中的SocketChannel sc和socketWrapper 參數
把當前連接緩存到connections集合中
socketWrapper賦值為newWrapper對象
設置當前socket為非阻塞
③、為socketWrapper設置參數
socketWrapper就是對當前連接的socket的一個封裝
3、把socketWrapper注冊到Poller中
org.apache.tomcat.util.net.NioEndpoint.Poller#register
創建Poller對象,會創建選擇器selector =WindowsSelectorImpl
這個線程啟動也是一個死循環
1、一直循環遍歷events隊列中是否有連接
2、有新連接便會調用processKey處理新連接
創建SocketProcessorBase對象,這個對象也是一個實現了Runnable的線程。
sc任務放到線程池execute執行,調用到SocketProcessorBase的run方法
NioEndpoint的啟動,啟動了三個線程和一個執行器線程池。
Acceptor線程處理客戶端的連接,並把連接封裝添加到Poller線程中的events中。
Poller線程把連接socket添加到events中,在處理socket時,會啟動一個處理連接的ConnectionHandler線程。
ConnectionHandler線程把處理的socket交給Http11Processor進行協議的處理。
ConnectionHandler線程是在線程池中執行。
『陸』 在TOMCAT中怎麼找到源代碼的位置
在server.xml下面有段上下文配置,也就是<context></context>標簽,用來指定你的web應用程序放的位置及相關屬性配置
『柒』 Tomcat源碼解析-組件之StandardServer
StandardServer是tomcat容器的最高層的組件,職責如下:
實現Tomcat一鍵啟動關閉,管理全局 JDNI資源,管理子組件,阻塞tomcat主線程。
StandardServer子組件
Bootstarp作為tomcat啟動類,JVM會調用main函數完成tomcat啟動。在其內部流程如下:
核心方法功能
Tomcat使用Digester解析server.xml,Digester是一款用於將xml轉換為Java對象的事件驅動型工具,是對SAX的高層次的封裝。相對於SAX可以為xml中每一個標簽設置對應的解析規則。詳見 Tomcat相關技術-Digester(二)
這里通過解析server.xml實例化StandardServer,並設置server.xml文件中定義的屬性初始化。
server.xml配置
<Server>標簽用來表示當前StandardServer組件
StandardServer構造函數
將<server>標簽屬性映射到StandardServer對象屬性中
<GlobalNamingResources>標簽中定義了全局JNDI資源,
<Listener>標簽中定義StandardServer組件中需要的LifecycleListener監聽器。<Server>標簽內可以設置多個<Listener>。
Server中定義的Listener
添加LifecycleListener監聽器
<Service>標簽中定義StandardServer組件中子組件Service。<Server>標簽內可以設置多個<Service>。
添加Service子組件
StandardServer作為tomcat最上層的組件,和其他所有組件一樣都實現了Lifecycle 介面。
Tomcat 定義一個基類LifecycleBase 來實現 Lifecycle 介面,把一些公共的邏輯放到基類中去,比如生命狀態的轉變與維護、生命事件的觸發以及監聽器的添加和刪除等,而子類就負責實現自己的初始化、啟動和停止等模板方法。為了避免跟基類中的方法同名,我們把具體子類的實現方法改個名字,在後面加上 Internal,叫 initInternal、startInternal 等。
其他模板方法
詳見 Tomcat架構設計-組件生命周期
StandardServer組件初始化的核心是調用所有Service子組件初始化方法init。
詳細流程如下:
啟動start的核心是調用所有Service子組件初始化方法start。
詳細流程如下:
啟動stop的核心是調用所有Service子組件初始化方法stop。
詳細流程如下:
銷毀destroy的核心是調用所有Service子組件初始化方法destroy。
1 調用所有Service子組件啟動方法destroy
2 銷毀JND全局資源
3 jmx bean注銷MBeanFactory
4 jmx bean注銷StringCache
5 調用LifecycleMBeanBase.destroyInternal 將當前組件對象從jmx 注銷
在這樣的設計中,在父組件的 init 方法里需要創建子組件並調用子組件的 init 方法。同樣,在父組件的 start 方法里也需要調用子組件的 start 方法。只要調用最頂層組件StandardServer的 init 和 start 方法,整個 Tomcat 就被啟動起來了。只要調用最頂層組件StandardServer的 destroy 和 stop 方法,整個 Tomcat 就被關閉。
添加已在解析server.xml初始化設置調用
阻塞tomcat主線程,只要stopAwait不為true, tomcat主線程在此無限循環.監聽到客戶端發起SHUTDOWN命令後退出
『捌』 tomcat的classloader的源碼解析
相關源碼如下:
common,catalina,shared三個classloader沒有違背雙親載入原則
WebappClassLoader違背了雙親委派機制 因為重寫了loadclass
在context啟動的時候其會啟動一個webappLoader
而webappLoader會創建一個WebappClassLoader,設置一個source(對應咱們webapps下面的一個項目)
設置delegate=false 代表不需要遵循雙親委派機制,然後啟動WebappClassLoader,
然後這個context的載入的會使用自己的context裡面含有的WebappClassLoader去載入bean
當webapps下面有多個項目就建立多個standardContext
每個standardContext 設有一個對應的webappLoader和webappClassLoader
『玖』 如何調試tomcat源碼實現一次http請求
上文我們說到endpoint是底層處理I/O具體實現類,那麼一次HTTP首先也要從這個類中開始。還是以NIOEndPoint實現類為例子。在NIOEndPoint類中有一個名為Acceptor內部類。該內部類負責接收即將到來的TCP/IP連接,並將它們分配給合適的processor處理。
HTTP底層是TCP協議,Java實現TCP協議的具體的方式就是Socket連接。我相信只要了解一點Java網路編程方面的知識都會了解的,不在累述。另一個比較重要的方法是setSocketOptions(),該方法是將socket連接添加到一個緩存隊列裡面。這里使用的是生產者和消費者模式,如果對該模式有不了解,請看我的另一個博文,Java並發之生產者和消費者模型
『拾』 eclipse怎麼導入tomcat源碼
在你要看代碼的地方,ctrl+方法名(或者類名),會彈出一個頁面,在這個頁面上,你關聯一下你下載的Tomcat源碼包。
關閉之後,在ctrl+方法名(或者類名)就可以看到源碼了。