1 Tomcat核心功能
我們知道如果要設(shè)計(jì)一個(gè)系統(tǒng),首先是要了解需求。 Tomcat 要實(shí)現(xiàn)本質(zhì)是2個(gè)核心功能
- 處理 Socket 連接,負(fù)責(zé)網(wǎng)絡(luò)字節(jié)流與 Request 和 Response 對(duì)象相互轉(zhuǎn)化。
- 加載和管理 Servlet,以及具體處理 Request 請(qǐng)求。
2 Tomcat架構(gòu)設(shè)計(jì)
通過(guò)tomcat功能可以將Tomcat設(shè)計(jì)了兩個(gè)核心組件連接器(Connector)和容器(Container)來(lái)分別來(lái)處理對(duì)外交流,內(nèi)部處理。

Tomcat中一個(gè)容器可能對(duì)接多個(gè)連接器,每一個(gè)連接器都對(duì)應(yīng)某種協(xié)議某種IO模型,tomcat將單個(gè)容器和多個(gè)連接器組成一個(gè)service組件,一個(gè)tomcat中可能存在多個(gè)Service組件
Connector:將不同協(xié)議不同IO模型的請(qǐng)求轉(zhuǎn)換為標(biāo)準(zhǔn)的標(biāo)準(zhǔn)的 ServletRequest 對(duì)象交給容器處理。
Container:Container本質(zhì)上是一個(gè)Servlet容器,負(fù)責(zé)servelt的加載和管理,處理請(qǐng)求ServletRequest,并返回標(biāo)準(zhǔn)的 ServletResponse 對(duì)象給連接器。
3 Connector架構(gòu)設(shè)計(jì)
3.1 核心功能
1 監(jiān)聽(tīng)網(wǎng)絡(luò)端口,等待客戶端網(wǎng)絡(luò)連接請(qǐng)求。
2 接受客戶端網(wǎng)絡(luò)連接請(qǐng)求,于客戶端建立Socket連接,并監(jiān)聽(tīng)客戶端發(fā)送的請(qǐng)求。
3 從Socket中讀取客戶端請(qǐng)求網(wǎng)絡(luò)字節(jié)流。
4 根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)解析字節(jié)流,生成統(tǒng)一的 Tomcat Request 對(duì)象。
5 將 Tomcat Request 對(duì)象轉(zhuǎn)成標(biāo)準(zhǔn)的 ServletRequest。
6 查找并調(diào)用當(dāng)前請(qǐng)求適配的Servlet 容器,得到 ServletResponse
7 Servlet 容器處理完成后,將 ServletResponse 轉(zhuǎn)成 Tomcat Response 對(duì)象。
8 根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)將 Tomcat Response 轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)流,寫入Socket中,。
9 將Socket中響應(yīng)字節(jié)流發(fā)送給客戶端。
3.2 支持多個(gè)協(xié)議
Tomcat 支持的應(yīng)用層協(xié)議
HTTP/1.1:這是大部分 Web 應(yīng)用采用的訪問(wèn)協(xié)議。
AJP:用于和 Web 服務(wù)器集成(如 Apache)。
HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。
3.3 支持多個(gè)I/O模型
NIO:非阻塞 I/O,采用 Java NIO 類庫(kù)實(shí)現(xiàn)
NIO2:異步 I/O,采用 JDK 7 最新的 NIO2 類庫(kù)實(shí)現(xiàn)。
APR:采用 Apache 可移植運(yùn)行庫(kù)實(shí)現(xiàn),是 C/C++ 編寫的本地庫(kù)。
3.4 通用架構(gòu)設(shè)計(jì)
優(yōu)秀的模塊化設(shè)計(jì)應(yīng)該考慮高內(nèi)聚、低耦合。
- 高內(nèi)聚是指相關(guān)度比較高的功能要盡可能集中,不要分散。
- 低耦合是指兩個(gè)相關(guān)的模塊要盡可能減少依賴的部分和降低依賴的程度,不要讓兩個(gè)模塊產(chǎn)生強(qiáng)依賴。
3.4.1 Connector高內(nèi)聚的功能
通過(guò)分析連接器的詳細(xì)功能列表,我們發(fā)現(xiàn)連接器需要完成 3 個(gè)高內(nèi)聚的功能,*
-
網(wǎng)絡(luò)通信。
- 1 監(jiān)聽(tīng)網(wǎng)絡(luò)端口,等待客戶端網(wǎng)絡(luò)連接請(qǐng)求。
- 2 接受客戶端網(wǎng)絡(luò)連接請(qǐng)求,于客戶端建立Socket連接,并監(jiān)聽(tīng)客戶端發(fā)送的請(qǐng)求。
- 3 從Socket中讀取客戶端請(qǐng)求網(wǎng)絡(luò)字節(jié)流。
- 9 將Socket中響應(yīng)字節(jié)流發(fā)送給客戶端。
-
應(yīng)用層協(xié)議解析。
- 4 根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)解析字節(jié)流,生成統(tǒng)一的 Tomcat Request 對(duì)象。
- 8 根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)將 Tomcat Response 轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)流,寫入Socket中。
-
Tomcat Request/Response 與 ServletRequest/ServletResponse 的轉(zhuǎn)化。
- 5 將 Tomcat Request 對(duì)象轉(zhuǎn)成標(biāo)準(zhǔn)的 ServletRequest。
- 6 查找并調(diào)用當(dāng)前請(qǐng)求適配的Servlet 容器,得到 ServletResponse。
- 7 Servlet 容器處理完成后,將 ServletResponse 轉(zhuǎn)成 Tomcat Response 對(duì)象。
3.5 Connector中核心組件
3個(gè)高內(nèi)聚的功能并分別對(duì)應(yīng)Connector中三個(gè)核心組件:EndPoint,Processor,Adapter。

3.5.1 EndPoint組件
Endpoint 翻譯過(guò)來(lái)是"通信端點(diǎn)",主要負(fù)責(zé)網(wǎng)絡(luò)通信,這其中就包括,監(jiān)聽(tīng)客戶端連接創(chuàng)建于客戶端連接的Socket,并負(fù)責(zé)連接Socket 接收和發(fā)送處理器。因此Endpoint是對(duì)傳輸層的抽象,是用來(lái)實(shí)現(xiàn) TCP/IP 協(xié)議的。
EndPoint類結(jié)構(gòu)圖
EndPoint用基類用抽象類AbstractEndpoint來(lái)表示,對(duì)于不同的Linux IO模型通過(guò)使用不同子類來(lái)實(shí)現(xiàn)。

3.5.2 Processor組件
Processor:翻譯過(guò)來(lái)是"處理器",主要負(fù)責(zé)根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)讀取字節(jié)流解析成 Tomcat Request 和 Response,因此Processor是對(duì)應(yīng)用層的抽象,是用來(lái)實(shí)現(xiàn) HTTP/AJP 協(xié)議的。
Processor類結(jié)構(gòu)圖

3.5.3 Adapter組件
由于協(xié)議不同,客戶端發(fā)過(guò)來(lái)的請(qǐng)求信息也不盡相同,Tomcat 定義了自己的 Request 類來(lái)“存放”這些請(qǐng)求信息。ProtocolHandler 接口負(fù)責(zé)解析請(qǐng)求并生成 Tomcat Request/Response類。但是這個(gè) Request/Response 對(duì)象不是標(biāo)準(zhǔn)的 ServletRequest/ServletResponse,也就意味著,不能用Tomcat Request/Response 作為參數(shù)來(lái)調(diào)用容器。
Tomcat 設(shè)計(jì)者的解決方案是引入 CoyoteAdapter,這是適配器模式的經(jīng)典運(yùn)用,負(fù)責(zé)將Tomcat Request/Response 與 ServletRequest/ServletResponse 的相互轉(zhuǎn)化,實(shí)現(xiàn)連接器(Connector)和容器(Container)的解耦。
3.5.4 ProtocolHandler組件
ProtocolHandler組件EndPoint組件,Processor組件合并在一起表示協(xié)議處理器。用來(lái)處理tomcat支持多種IO模型和多種協(xié)議的組件。
ProtocolHandler類圖

3.5 Connector處理流程

Endpoint內(nèi)部Acceptor組件用于監(jiān)聽(tīng)Socket 連接請(qǐng)求,當(dāng)發(fā)送客戶端連接到服務(wù)端Acceptor組件負(fù)責(zé)于客戶端建立連接創(chuàng)建Socket,每當(dāng)連接客戶端發(fā)起請(qǐng)求,Endpoint會(huì)創(chuàng)建一個(gè)SocketProcessor對(duì)象SocketProcessor 用于處理接收到的 Socket 請(qǐng)求,它實(shí)現(xiàn) Runnable 接口,在 run 方法里調(diào)用協(xié)議處理組件 Processor 進(jìn)行處理。為了提高處理能力,SocketProcessor 被提交到線程池來(lái)執(zhí)行。而這個(gè)線程池叫作執(zhí)行器(Executor)
Processor 接收來(lái)自 Endpoint 的 Socket,讀取字節(jié)流解析成 Tomcat Request 和 Response 對(duì)象,接著會(huì)調(diào)用 Adapter 的 Service 方法。并通過(guò) Adapter 將其提交到容器處理
連接器調(diào)用 CoyoteAdapter 的 sevice 方法,傳入的是 Tomcat Request 對(duì)象,CoyoteAdapter 負(fù)責(zé)將 Tomcat Request 轉(zhuǎn)成 ServletRequest,再調(diào)用容器的 service 方法。
4 Container架構(gòu)設(shè)計(jì)
4.1 Servlet容器
Container本質(zhì)上是一個(gè)Servlet容器,負(fù)責(zé)servelt的加載和管理,處理請(qǐng)求ServletRequest,并返回標(biāo)準(zhǔn)的 ServletResponse 對(duì)象給連接器。
工作流程
當(dāng)客戶請(qǐng)求某個(gè)資源時(shí),HTTP 服務(wù)器會(huì)用一個(gè) ServletRequest 對(duì)象把客戶的請(qǐng)求信息封裝起來(lái),然后調(diào)用 Servlet 容器的 service 方法,Servlet 容器拿到請(qǐng)求后,根據(jù)請(qǐng)求的 URL 和 Servlet 的映射關(guān)系,找到相應(yīng)的 Servlet,如果 Servlet 還沒(méi)有被加載,就用反射機(jī)制創(chuàng)建這個(gè) Servlet,并調(diào)用 Servlet 的 init 方法來(lái)完成初始化,接著調(diào)用 Servlet 的 service 方法來(lái)處理請(qǐng)求,把 ServletResponse 對(duì)象返回給 HTTP 服務(wù)器,HTTP 服務(wù)器會(huì)把響應(yīng)發(fā)送給客戶端

4.2 核心功能
按照servlet規(guī)范我們可以將多個(gè)servelt打包一個(gè)war包,而這個(gè)war包就表示一個(gè)web應(yīng)用程序,war名稱就是應(yīng)用程序的名稱。一個(gè)一個(gè)網(wǎng)站來(lái)說(shuō)會(huì)存在多個(gè)域名,每個(gè)域名則會(huì)對(duì)應(yīng)多個(gè)web應(yīng)用程序。
例如有一個(gè)網(wǎng)購(gòu)系統(tǒng),有面向網(wǎng)站管理人員的后臺(tái)管理系統(tǒng),還有面向終端客戶的在線購(gòu)物系統(tǒng)。這兩個(gè)系統(tǒng)跑在同一個(gè) Tomcat 上,為了隔離它們的訪問(wèn)域名,配置了兩個(gè)虛擬域名:manage.shopping.com和user.shopping.com,網(wǎng)站管理人員通過(guò)manage.shopping.com域名訪問(wèn) Tomcat 去管理用戶和商品,而用戶管理和商品管理是兩個(gè)單獨(dú)的 Web 應(yīng)用。終端客戶通過(guò)user.shopping.com域名去搜索商品和下訂單,搜索功能和訂單管理也是兩個(gè)獨(dú)立的 Web 應(yīng)用。

4.3 Container中核心組件
tomcat 將Container容器按功能分為4個(gè)組件,分別是 Engine、Host、Context 和 Wrapper。這 4 種容器不是平行關(guān)系,而是父子關(guān)系。
Wrapper:表示一個(gè) Servlet
Context:表示一個(gè) Web 應(yīng)用程序,一個(gè) Web 應(yīng)用程序中可能會(huì)有多個(gè) Servlet
Host:表示的是一個(gè)虛擬主機(jī),或者說(shuō)一個(gè)站點(diǎn),可以給 Tomcat 配置多個(gè)虛擬主機(jī)地址,而一個(gè)虛擬主機(jī)下可以部署多個(gè) Web 應(yīng)用程序
Engine:表示引擎,用來(lái)管理多個(gè)虛擬站點(diǎn),一個(gè) Service 最多只能有一個(gè) Engine。

可以再通過(guò) Tomcat 的server.xml配置文件來(lái)加深對(duì) Tomcat 容器的理解。Tomcat 采用了組件化的設(shè)計(jì),它的構(gòu)成組件都是可配置的,其中最外層的是 Server,其他組件按照一定的格式要求配置在這個(gè)頂層容器中。

4.4 組件類圖
Container容器中定義了Container 接口用來(lái)描述Container容器中所有的組件,不同的子組件分別定義了不同子接口做描述。容器組件之間具有父子關(guān)系。
public interface Container extends Lifecycle {
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}

4.5 請(qǐng)求定位 Servlet 的過(guò)程
你可能好奇,設(shè)計(jì)了這么多層次的容器,Tomcat 是怎么確定請(qǐng)求是由哪個(gè) Wrapper 容器里的 Servlet 來(lái)處理的呢?答案是,Tomcat 是用 Mapper 組件來(lái)完成這個(gè)任務(wù)的。
Mapper 組件的功能就是將用戶請(qǐng)求的 URL 定位到一個(gè) Servlet,它的工作原理是:Mapper 組件里保存了 Web 應(yīng)用的配置信息,其實(shí)就是容器組件與訪問(wèn)路徑的映射關(guān)系,比如 Host 容器里配置的域名、Context 容器里的 Web 應(yīng)用路徑,以及 Wrapper 容器里 Servlet 映射的路徑,你可以想象這些配置信息就是一個(gè)多層次的 Map。
當(dāng)一個(gè)請(qǐng)求到來(lái)時(shí),Mapper 組件通過(guò)解析請(qǐng)求 URL 里的域名和路徑,再到自己保存的 Map 里去查找,就能定位到一個(gè) Servlet。請(qǐng)你注意,一個(gè)請(qǐng)求 URL 最后只會(huì)定位到一個(gè) Wrapper 容器,也就是一個(gè) Servlet。
針對(duì)上面網(wǎng)購(gòu)系統(tǒng)我可以這樣的部署到tomcat,Tomcat 會(huì)創(chuàng)建一個(gè) Service 組件和一個(gè) Engine 容器組件,在 Engine 容器下創(chuàng)建兩個(gè) Host 子容器,在每個(gè) Host 容器下創(chuàng)建兩個(gè) Context 子容器。由于一個(gè) Web 應(yīng)用通常有多個(gè) Servlet,Tomcat 還會(huì)在每個(gè) Context 容器里創(chuàng)建多個(gè) Wrapper 子容器。每個(gè)容器都有對(duì)應(yīng)的訪問(wèn)路徑,你可以通過(guò)下面這張圖來(lái)幫助你理解。

假如有用戶訪問(wèn)一個(gè) URL,比如圖中的http://user.shopping.com:8080/order/buy,Tomcat 如何將這個(gè) URL 定位到一個(gè) Servlet 呢?
首先,根據(jù)協(xié)議和端口號(hào)選定 Service 和 Engine。
我們知道 Tomcat 的每個(gè)連接器都監(jiān)聽(tīng)不同的端口,比如 Tomcat 默認(rèn)的 HTTP 連接器監(jiān)聽(tīng) 8080 端口、默認(rèn)的 AJP 連接器監(jiān)聽(tīng) 8009 端口。上面例子中的 URL 訪問(wèn)的是 8080 端口,因此這個(gè)請(qǐng)求會(huì)被 HTTP 連接器接收,而一個(gè)連接器是屬于一個(gè) Service 組件的,這樣 Service 組件就確定了。我們還知道一個(gè) Service 組件里除了有多個(gè)連接器,還有一個(gè)容器組件,具體來(lái)說(shuō)就是一個(gè) Engine 容器,因此 Service 確定了也就意味著 Engine 也確定了。
然后,根據(jù)域名選定 Host。
Service 和 Engine 確定后,Mapper 組件通過(guò) URL 中的域名去查找相應(yīng)的 Host 容器,比如例子中的 URL 訪問(wèn)的域名是user.shopping.com,因此 Mapper 會(huì)找到 Host2 這個(gè)容器。
之后,根據(jù) URL 路徑找到 Context 組件。
Host 確定以后,Mapper 根據(jù) URL 的路徑來(lái)匹配相應(yīng)的 Web 應(yīng)用的路徑,比如例子中訪問(wèn)的是/order,因此找到了 Context4 這個(gè) Context 容器。
最后,根據(jù) URL 路徑找到 Wrapper(Servlet)。
Context 確定后,Mapper 再根據(jù)web.xml中配置的 Servlet 映射路徑來(lái)找到具體的 Wrapper 和 Servlet。
看到這里,我想你應(yīng)該已經(jīng)了解了什么是容器,以及 Tomcat 如何通過(guò)一層一層的父子容器找到某個(gè) Servlet 來(lái)處理請(qǐng)求。需要注意的是,并不是說(shuō)只有 Servlet 才會(huì)去處理請(qǐng)求,實(shí)際上這個(gè)查找路徑上的父子容器都會(huì)對(duì)請(qǐng)求做一些處理。我在上一期說(shuō)過(guò),連接器中的 Adapter 會(huì)調(diào)用容器的 Service 方法來(lái)執(zhí)行 Servlet,最先拿到請(qǐng)求的是 Engine 容器,Engine 容器對(duì)請(qǐng)求做一些處理后,會(huì)把請(qǐng)求傳給自己子容器 Host 繼續(xù)處理,依次類推,最后這個(gè)請(qǐng)求會(huì)傳給 Wrapper 容器,Wrapper 會(huì)調(diào)用最終的 Servlet 來(lái)處理。那么這個(gè)調(diào)用過(guò)程具體是怎么實(shí)現(xiàn)的呢?答案是使用 Pipeline-Valve 管道。
4.6 Tomcat中責(zé)任鏈模式
責(zé)任鏈模式
Pipeline-Valve 是責(zé)任鏈模式,責(zé)任鏈模式是指在一個(gè)請(qǐng)求處理的過(guò)程中有很多處理者依次對(duì)請(qǐng)求進(jìn)行處理,每個(gè)處理者負(fù)責(zé)做自己相應(yīng)的處理,處理完之后將再調(diào)用下一個(gè)處理者繼續(xù)處理。
類圖

每一個(gè)容器組件都有一個(gè) Pipeline 對(duì)象,Pipeline 中維護(hù)了 Valve 鏈表,默認(rèn)時(shí)每一個(gè)Pipeline存放了一個(gè)默認(rèn)的BasicValue,
這里每一個(gè)Value表示一個(gè)處理點(diǎn),當(dāng)調(diào)用addValve 方法時(shí)會(huì)將添加Vaule添加到鏈表頭部,Pipeline 中沒(méi)有 invoke
方法,請(qǐng)求處理時(shí)Pipeline只需要獲取鏈表中第一個(gè)Valve調(diào)用incoke去執(zhí)行,執(zhí)行完畢后當(dāng)前Value會(huì)調(diào)用
getNext.invoke() 來(lái)觸發(fā)下一個(gè) Valve 調(diào)用
tomcat中責(zé)任鏈
每一個(gè)容器在執(zhí)行到最后一個(gè)默認(rèn)BasicValue時(shí),會(huì)負(fù)責(zé)調(diào)用下層容器的 Pipeline 里的第一個(gè) Valve

5 總結(jié)流程
