tomcat源碼分析(1)

1.源碼下載與構(gòu)建

1.1 主要流程

#1.下載源代碼
http://archive.apache.org/dist/tomcat/tomcat-8/v8.5.55/bin/

#2.解壓源代碼, 并進(jìn)入該目錄
/Programming/apache-tomcat-8.5.55-src

#3.在源代碼目錄的bin同級(jí)目錄下, 創(chuàng)建 resources目錄

#4.將與bin目錄同級(jí)的conf和webapps目錄移動(dòng)到新創(chuàng)建的 resources 目錄下

#5.在bin同級(jí)目錄下, 創(chuàng)建pom.xml文件, 注意artifactId, 見下文圖示

#6.IDEA中導(dǎo)入源代碼, 見下文圖示

#7.修改源碼手動(dòng)將JSP解析器初始化
找到ContextConfig中的configureStart方法,在 webConfig(); 后面加上:
context.addServletContainerInitializer(new JasperInitializer(), null);

#9.啟動(dòng)源代碼, 見下文圖示
apache-tomcat-8.5.55-src\java\org\apache\catalina\startup\Bootstrap.java

#啟動(dòng)參數(shù)配置, 見下文圖示
-Dcatalina.home=/Programming/apache-tomcat-8.5.55-src/resources
-Dcatalina.base=/Programming/apache-tomcat-8.5.55-src/resources
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Programming/apache-tomcat-8.5.55-src/resources/conf/logging.properties
-Duser.language=en
-Duser.region=US
-Dfile.encoding=UTF-8

#10.啟動(dòng)后, 訪問即可
http://localhost:8080

1.2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.apache.tomcat</groupId>
 <artifactId>apache-tomcat-8.5.55-src</artifactId>
 <name>Tomcat8.5</name>
 <version>8.5</version>
 <build>
 <!--指定源?錄-->
 <finalName>Tomcat8.5</finalName>
 <sourceDirectory>java</sourceDirectory>
 <resources>
 <resource>
 <directory>java</directory>
 </resource>
 </resources>
 <plugins>
 <!--引?編譯插件-->
 <plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>3.1</version>
 <configuration>
 <encoding>UTF-8</encoding>
 <source>8</source>
 <target>8</target>
 </configuration>
 </plugin>
 </plugins>
 </build>
 <!--tomcat 依賴的基礎(chǔ)包-->
 <dependencies>
 <dependency>
 <groupId>org.easymock</groupId>
 <artifactId>easymock</artifactId>
 <version>3.4</version>
 </dependency>
 <dependency>
 <groupId>ant</groupId>
 <artifactId>ant</artifactId>
 <version>1.7.0</version>
 </dependency>
 <dependency>
 <groupId>wsdl4j</groupId>
 <artifactId>wsdl4j</artifactId>
 <version>1.6.2</version>
 </dependency>
 <dependency>
 <groupId>javax.xml</groupId>
 <artifactId>jaxrpc</artifactId>
 <version>1.1</version>
 </dependency>
 <dependency>
 <groupId>org.eclipse.jdt.core.compiler</groupId>
 <artifactId>ecj</artifactId>
 <version>4.5.1</version>
 </dependency>
 <dependency>
 <groupId>javax.xml.soap</groupId>
 <artifactId>javax.xml.soap-api</artifactId>
 <version>1.4.0</version>
 </dependency>
 </dependencies>
</project>

1.3 源碼導(dǎo)入

tomcat源碼導(dǎo)入.png

1.4 啟動(dòng)類&&啟動(dòng)參數(shù)設(shè)置

tomcat源碼啟動(dòng)類參數(shù)設(shè)置.png

2.tomcat 基本介紹

2.1 tomcat 目錄結(jié)構(gòu)

bin                         用于存放Tomcat的啟動(dòng)、停止等批處理腳本和Shell腳本
conf                        用于存放Tomcat的相關(guān)配置文件
conf/Catalina               用于存儲(chǔ)針對(duì)每個(gè)虛擬機(jī)的Context配置
conf/context.xml            用于定義所有Web應(yīng)用均需要加載的Context配置样傍,如果Web應(yīng)用指定了自己的context.xml殖氏,那么該文件的配置將被覆蓋
conf/catalina.properties    Tomcat環(huán)境變量配置
conf/catalina.policy        當(dāng)Tomcat在安全模式下運(yùn)行時(shí),此文件為默認(rèn)的安全策略配置
conf/logging.properties     Tomcat日志配置文件闸迷,可通過該文件修改Tomcat日志級(jí)別以及日志路徑等
conf/server.xml             Tomcat服務(wù)器核心配置文件吨铸,用于配置Tomcat的鏈接器夕冲、監(jiān)聽端口杠纵、處理請(qǐng)求的虛擬主機(jī)等训裆。可以說武福,Tomcat主要根據(jù)該文件的配置信息創(chuàng)建服務(wù)器實(shí)例
conf/tomcat-users.xml       用于定義Tomcat默認(rèn)用戶及角色映射信息议双,Tomcat的Manager模塊即用該文件中定義的用戶進(jìn)行安全認(rèn)證
conf/web.xml                Tomcat中所有應(yīng)用默認(rèn)的部署描述文件,主要定義了基礎(chǔ)Servlet和MIME映射捉片。如果應(yīng)用中不包含Web.xml平痰,那么Tomcat將使用此文件初始化部署描述,反之伍纫,Tomcat會(huì)在啟動(dòng)時(shí)將默認(rèn)部署描述與自定義配置進(jìn)行合并
lib                         Tomcat服務(wù)器依賴庫目錄,包含Tomcat服務(wù)器運(yùn)行環(huán)境依賴Jar包
logs                        Tomcat默認(rèn)的日志存放路徑
webapps                     Tomcat默認(rèn)的Web應(yīng)用部署目錄
sork                        Web應(yīng)用JSP代碼生成和編譯臨時(shí)目錄

2.2 tomcat 8.5 之后的新特性

>> 自8.0版本開始宗雇,Tomcat支持Servlet 3.1 、JSP 2.3莹规、EL 3.0赔蒲、WebSocket 1.l;并且自9.0版本開始支持Servlet 4.0。
>> 為了讓用戶提前體驗(yàn)Servlet 4.0的新特性良漱,在8.5版本中舞虱,Tomcat提供了一套Servlet 4.0預(yù)覽API ( servlet4preview,它們并不屬于規(guī)范母市,而是Tomcat的一部分矾兜,也不會(huì)包含到9.0版本當(dāng)中)。
>> 自8.0版本開始患久,默認(rèn)的HTTP椅寺、AJP鏈接器采用NIO,而非Tomcat 7以及之前版本的BIO;并且自8.5開始蒋失,Tomcat移除了對(duì)BIO的支持返帕。
>> 在8.0版本中,Tomcat提供了一套全新的資源實(shí)現(xiàn)篙挽,采用單獨(dú)荆萤、一致的方法配置Web應(yīng)用的附加資源,以替代原有的Aliases铣卡、VirtualLoader观腊、VirtualDirContext邑闲、JAR。新的資源方案可以用于實(shí)現(xiàn)覆蓋梧油。例如可以將一個(gè)WAR作為多個(gè)Web應(yīng)用的基礎(chǔ),同時(shí)這些Web應(yīng)用各自擁有自己的定制功能州邢。
>> 自8.0版本開始儡陨,鏈接器新增支持JDK 7的NIO2。
>> 自8.0版本開始量淌,鏈接器新增支持HTTP/2協(xié)議骗村。
>> 默認(rèn)采用異步日志處理方式。

2.3 tomcat項(xiàng)目部署的幾種方式

3.tomcat整體架構(gòu)

#總體來講
tomcat 是一個(gè) <Http 服務(wù)器 && Servlet> 容器:
>> 屏蔽了應(yīng)用層協(xié)議和網(wǎng)絡(luò)通信細(xì)節(jié)呀枢,封裝了標(biāo)準(zhǔn)的 Request 和 Response 對(duì)象胚股;
>> 對(duì)于具體的業(yè)務(wù)邏輯則作為變化點(diǎn),交給使用者來實(shí)現(xiàn)裙秋。
>> 比如使用 SpringMVC 框架琅拌,不必考慮 TCP 連接等, 因?yàn)?Tomcat 已經(jīng)已封裝好, 使用者只需要關(guān)注每個(gè)請(qǐng)求的具體業(yè)務(wù)邏輯。

class Tomcat {
    List<Servlet> servletContainer;
}


#基于設(shè)計(jì)模式: 封裝變與不變
Tomcat 內(nèi)部隔離了變化點(diǎn)與不變點(diǎn)摘刑,使用了組件化設(shè)計(jì)进宝,目的就是為了實(shí)現(xiàn)「俄羅斯套娃式」的高度定制化(組合模式),
而每個(gè)組件的生命周期管理又有一些共性的東西枷恕,則被提取出來成為接口和抽象類党晋,讓具體子類實(shí)現(xiàn)變化點(diǎn),也就是模板方法設(shè)計(jì)模式(LifeCycleBase)徐块。

當(dāng)今流行的微服務(wù)也是這個(gè)思路未玻,按照功能將單體應(yīng)用拆成「微服務(wù)」,拆分過程要將共性提取出來胡控,而這些共性就會(huì)成為核心的基礎(chǔ)服務(wù)或者通用庫扳剿。「中臺(tái)」思想亦是如此铜犬。

#Servlet 是 Web 開發(fā)的基石
很多 Java Web 框架(比如 Spring)都是基于 Servlet 的封裝(Netty 除外, Netty 不遵循 Servlet 規(guī)范)舞终,
Spring 應(yīng)用本身就是一個(gè) Servlet(DispatchSevlet),Tomcat/Jetty/Undertow 等 Web 容器癣猾,負(fù)責(zé)加載和運(yùn)行 Servlet敛劝。

Servlet的位置.png

3.1 tomcat基礎(chǔ)組件

Server      表示整個(gè)Servlet容器,因此Tomcat運(yùn)行環(huán)境中只有唯一一個(gè)Server實(shí)例, 負(fù)責(zé)管理和啟動(dòng)多個(gè)Service, 同時(shí)監(jiān)聽8005端口對(duì)應(yīng)的shutdown命令, 用于關(guān)閉整個(gè)Server
Service     Service包含一個(gè)Container(即Engine)和多個(gè)Connector的集合纷宇,這些Connector共享同一個(gè)Container來處理其請(qǐng)求夸盟。在同一個(gè)Tomcat實(shí)例內(nèi)可以包含任意多個(gè)Service實(shí)例,它們彼此獨(dú)立, 但共享同一JVM資源.
Connector   即Tomcat鏈接器像捶,用于監(jiān)聽并轉(zhuǎn)化Socket請(qǐng)求上陕,同時(shí)將讀取的Socket請(qǐng)求交由Container處理,支持不同協(xié)議以及不同的I/O方式
Container   Container表示能夠執(zhí)行客戶端請(qǐng)求并返回響應(yīng)的一類對(duì)象桩砰。在Tomcat中存在不同級(jí)別的容器:Engine、Host释簿、Context亚隅、Wrapper
Engine      Engine表示整個(gè)Servlet引擎。在Tomcat中庶溶,Engine為最高層級(jí)的容器對(duì)象煮纵。盡管Engine不是直接處理請(qǐng)求的容器,卻是獲取目標(biāo)容器的入口
Host        Host作為一類容器偏螺,表示Servlet引擎(即Engine)中的虛擬機(jī)行疏,與一個(gè)服務(wù)器的網(wǎng)絡(luò)名有關(guān),如域名等套像∧鹆客戶端可以使用這個(gè)網(wǎng)絡(luò)名連接服務(wù)器,這個(gè)名稱必須要在DNS服務(wù)器上注冊
Context     Context作為一類容器夺巩,用于表示ServletContext贞让,在Servlet規(guī)范中,一個(gè)ServletContext即表示一個(gè)獨(dú)立的Web應(yīng)用劲够,并且一個(gè)Engine可以包含多個(gè)Context.
Wrapper     Wrapper作為一類容器震桶,用于表示W(wǎng)eb應(yīng)用中定義的Servlet
Executor    表示Tomcat組件間可以共享的線程池
Loader:封裝了 Java ClassLoader,用于 Container 加載類文件
Realm:Tomcat 中為 web 應(yīng)用程序提供訪問認(rèn)證和角色管理的機(jī)制征绎;
Pipeline:在容器中充當(dāng)管道的作用蹲姐,管道中可以設(shè)置各種 valve(閥門),請(qǐng)求和響應(yīng)在經(jīng)由管 道中各個(gè)閥門處理人柿,提供了一種靈活可配置的處理請(qǐng)求和響應(yīng)的機(jī)制柴墩。
JMX:Java SE 中定義技術(shù)規(guī)范,是一個(gè)為應(yīng)用程序凫岖、設(shè)備江咳、系統(tǒng)等植入管理功能的框架,通過 JMX 可以遠(yuǎn)程監(jiān)控 Tomcat 的運(yùn)行狀態(tài)
Naming:命名服務(wù)哥放,JNDI歼指, Java 命名和目錄接口,是一組在 Java 應(yīng)用中訪問命名和目錄服務(wù)的 API甥雕。命名服務(wù)將名稱和對(duì)象聯(lián)系起來踩身,使得我們可以用名稱訪問對(duì)象,目錄服務(wù)也是一種命名 服務(wù)社露,對(duì)象不但有名稱挟阻,還有屬性。Tomcat 中可以使用 JNDI 定義數(shù)據(jù)源、配置信息附鸽,用于開發(fā) 與部署的分離脱拼。
Jasper:Tomcat 的 Jsp 解析引擎,用于將 Jsp 轉(zhuǎn)換成 Java 文件坷备,并編譯成 class 文件熄浓。Session:負(fù)責(zé)管理和創(chuàng)建 session,以及 Session 的持久化(可自定義)省撑,支持 session 的集群玉组。

此外,Tomcat的Container還有一個(gè)很重要的功能丁侄,就是后臺(tái)處理。
在很多情況下朝巫,我們的Container需要執(zhí)行一些異步處理鸿摇,而且是定期執(zhí)行,如每隔30秒執(zhí)行一次劈猿,Tomcat對(duì)于Web應(yīng)用文件變更的掃描就是通過該機(jī)制實(shí)現(xiàn)的拙吉。
Tomcat針對(duì)后臺(tái)處理,在Container上定義了backgroundProcess()方法揪荣,并且其基礎(chǔ)抽象類( ContainerBase)確保在啟動(dòng)組件的同時(shí)筷黔,異步啟動(dòng)后臺(tái)處理。
因此仗颈,在絕大多數(shù)情況下佛舱,各個(gè)容器組件僅需要實(shí)現(xiàn)Container的background-Process()方法即可,不必考慮創(chuàng)建異步線程挨决。
tomcat整體架構(gòu)圖.png
Tomcat的啟動(dòng)方式可以作為非常好的示范來指導(dǎo)中間件產(chǎn)品設(shè)計(jì)请祖。
它實(shí)現(xiàn)了啟動(dòng)入口與核心環(huán)境的解耦,這樣不僅簡化了啟動(dòng)(不必配置各種依賴庫,因?yàn)橹挥歇?dú)立的幾個(gè)API ),
而且便于我們更靈活地組織中間件產(chǎn)品的結(jié)構(gòu),尤其是類加載器的方案脖祈,
否則肆捕,我們所有的依賴庫將統(tǒng)一放置到一個(gè)類加載器中,而無法做到靈活定制盖高。

3.1.1 Connector (連接器)

#Tomcat支持的 I/O 模型有:
>> NIO:非阻塞 I/O慎陵,采用 Java NIO 類庫實(shí)現(xiàn)。
>> NIO2:異步I/O喻奥,采用 JDK 7 最新的 NIO2 類庫實(shí)現(xiàn)席纽。
>> APR:采用 Apache 可移植運(yùn)行庫實(shí)現(xiàn),是 C/C++ 編寫的本地庫映凳。

#Tomcat 支持的應(yīng)用層協(xié)議有:
>> HTTP/1.1:這是大部分 Web 應(yīng)用采用的訪問協(xié)議胆筒。
>> AJP:用于和 Web 服務(wù)器集成(如 Apache)。
>> HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。

所以一個(gè)容器可能對(duì)接多個(gè)連接器仆救。
連接器對(duì) Servlet 容器屏蔽了網(wǎng)絡(luò)協(xié)議與 I/O 模型的區(qū)別抒和,
無論是 Http 還是 AJP,在容器中獲取到的都是一個(gè)標(biāo)準(zhǔn)的 ServletRequest 對(duì)象彤蔽。

#細(xì)化連接器的功能需求就是:
>> 監(jiān)聽網(wǎng)絡(luò)端口摧莽。
>> 接受網(wǎng)絡(luò)連接請(qǐng)求。
>> 讀取請(qǐng)求網(wǎng)絡(luò)字節(jié)流顿痪。
>> 根據(jù)具體應(yīng)用層協(xié)議(HTTP/AJP)解析字節(jié)流镊辕,生成統(tǒng)一的 Tomcat Request 對(duì)象。
>> 將 Tomcat Request 對(duì)象轉(zhuǎn)成標(biāo)準(zhǔn)的 ServletRequest蚁袭。
>> 調(diào)用 Servlet容器征懈,得到 ServletResponse。
>> 將 ServletResponse轉(zhuǎn)成 Tomcat Response 對(duì)象揩悄。
>> 將 Tomcat Response 轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)流卖哎。
>> 將響應(yīng)字節(jié)流寫回給瀏覽器。

#需求列清楚后删性,我們要考慮的下一個(gè)問題是亏娜,連接器應(yīng)該有哪些子模塊?
優(yōu)秀的模塊化設(shè)計(jì)應(yīng)該考慮高內(nèi)聚蹬挺、低耦合维贺。
>> 高內(nèi)聚是指相關(guān)度比較高的功能要盡可能集中,不要分散巴帮。
>> 低耦合是指兩個(gè)相關(guān)的模塊要盡可能減少依賴的部分和降低依賴的程度溯泣,不要讓兩個(gè)模塊產(chǎn)生強(qiáng)依賴。

#我們發(fā)現(xiàn)連接器需要完成 3 個(gè)高內(nèi)聚的功能:
>> 網(wǎng)絡(luò)通信晰韵。
>> 應(yīng)用層協(xié)議解析发乔。
>> Tomcat Request/Response 與 ServletRequest/ServletResponse 的轉(zhuǎn)化。

因此 Tomcat 的設(shè)計(jì)者設(shè)計(jì)了 3 個(gè)組件來實(shí)現(xiàn)這 3 個(gè)功能雪猪,分別是 EndPoint栏尚、Processor 和 Adapter。
網(wǎng)絡(luò)通信的 I/O 模型是變化的, 應(yīng)用層協(xié)議也是變化的只恨,但是整體的處理邏輯是不變的译仗,
>> EndPoint 負(fù)責(zé)提供字節(jié)流給 Processor,
>> Processor負(fù)責(zé)提供 Tomcat Request 對(duì)象給 Adapter官觅,
>> Adapter負(fù)責(zé)提供 ServletRequest對(duì)象給容器纵菌。

#封裝變與不變
因此 Tomcat 設(shè)計(jì)了一系列抽象基類來封裝這些穩(wěn)定的部分,抽象基類 AbstractProtocol實(shí)現(xiàn)了 ProtocolHandler接口休涤。
每一種應(yīng)用層協(xié)議有自己的抽象基類咱圆,比如 AbstractAjpProtocol和 AbstractHttp11Protocol笛辟,具體協(xié)議的實(shí)現(xiàn)類擴(kuò)展了協(xié)議層抽象基類。
這就是模板方法設(shè)計(jì)模式的運(yùn)用序苏。
Tomcat_Connector_Container.png

3.1.1.1 ProtocolHandler組件

ProtocoHandler主要處理網(wǎng)絡(luò)連接和應(yīng)用層協(xié)議手幢,包含了兩個(gè)重要部件 EndPoint 和 Processor。

#1.EndPoint
EndPoint是通信端點(diǎn)忱详,即通信監(jiān)聽的接口围来,是具體的 Socket 接收和發(fā)送處理器,是對(duì)傳輸層的抽象匈睁,
因此 EndPoint是用來實(shí)現(xiàn) TCP/IP 協(xié)議數(shù)據(jù)讀寫的监透,本質(zhì)調(diào)用操作系統(tǒng)的 socket 接口。
EndPoint是一個(gè)接口航唆,對(duì)應(yīng)的抽象實(shí)現(xiàn)類是 AbstractEndpoint胀蛮,而 AbstractEndpoint的具體子類,
比如在 NioEndpoint和 Nio2Endpoint中糯钙,有兩個(gè)重要的子組件:Acceptor和 SocketProcessor醇滥。
>> Acceptor 用于監(jiān)聽 Socket 連接請(qǐng)求超营。
>> SocketProcessor用于處理 Acceptor 接收到的 Socket請(qǐng)求,它實(shí)現(xiàn) Runnable接口演闭,在 Run方法里調(diào)用應(yīng)用層協(xié)議處理組件 Processor 進(jìn)行處理。為了提高處理能力吴趴,SocketProcessor被提交到線程池來執(zhí)行兰英。

#2.對(duì)于 Java 的多路復(fù)用器的使用趋厉,無非是兩步:
創(chuàng)建一個(gè) Seletor瞳脓,在它身上注冊各種感興趣的事件写妥,然后調(diào)用 select 方法嗜桌,等待感興趣的事情發(fā)生。
感興趣的事情發(fā)生了,比如可以讀了针贬,這時(shí)便創(chuàng)建一個(gè)新的線程從 Channel 中讀數(shù)據(jù)圆仔。
在 Tomcat 中 NioEndpoint 則是 AbstractEndpoint 的具體實(shí)現(xiàn)脉幢,里面組件雖然很多萎羔,但是處理邏輯還是前面兩步儿子。
它一共包含 LimitLatch犯助、Acceptor结胀、Poller、SocketProcessor和 Executor 共 5 個(gè)組件痒留,分別分工合作實(shí)現(xiàn)整個(gè) TCP/IP 協(xié)議的處理舷蟀。

>> LimitLatch 是連接控制器野宜,它負(fù)責(zé)控制最大連接數(shù),NIO 模式下默認(rèn)是 10000河胎,達(dá)到這個(gè)閾值后虎敦,連接請(qǐng)求被拒絕搁吓。

>> Acceptor跑在一個(gè)單獨(dú)的線程里吭历,它在一個(gè)死循環(huán)里調(diào)用 accept方法來接收新連接晌区,
一旦有新的連接請(qǐng)求到來朗若,accept方法返回一個(gè) Channel 對(duì)象哭懈,接著把 Channel對(duì)象交給 Poller 去處理遣总。

>> Poller 的本質(zhì)是一個(gè) Selector,也跑在單獨(dú)線程里容达。
Poller在內(nèi)部維護(hù)一個(gè) Channel數(shù)組花盐,它在一個(gè)死循環(huán)里不斷檢測 Channel的數(shù)據(jù)就緒狀態(tài)算芯,
一旦有 Channel可讀熙揍,就生成一個(gè) SocketProcessor任務(wù)對(duì)象扔給 Executor去處理。

>> SocketProcessor 實(shí)現(xiàn)了 Runnable 接口削葱,其中 run 方法中的 getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL); 代碼則是獲取 handler 并執(zhí)行處理 socketWrapper析砸,
最后通過 socket 獲取合適應(yīng)用層協(xié)議處理器首繁,也就是調(diào)用 Http11Processor 組件來處理請(qǐng)求。
Http11Processor 讀取 Channel 的數(shù)據(jù)來生成 ServletRequest 對(duì)象夹攒,Http11Processor 并不是直接讀取 Channel 的咏尝。
這是因?yàn)?Tomcat 支持同步非阻塞 I/O 模型和異步 I/O 模型编检,在 Java API 中允懂,相應(yīng)的 Channel 類也是不一樣的蕾总,比如有 AsynchronousSocketChannel 和 SocketChannel谤专,
為了對(duì) Http11Processor 屏蔽這些差異置侍,Tomcat 設(shè)計(jì)了一個(gè)包裝類叫作 SocketWrapper蜡坊,Http11Processor 只調(diào)用 SocketWrapper 的方法去讀寫數(shù)據(jù)赎败。

>> Executor就是線程池僵刮,負(fù)責(zé)運(yùn)行 SocketProcessor任務(wù)類搞糕,SocketProcessor 的 run方法會(huì)調(diào)用 Http11Processor 來讀取和解析請(qǐng)求數(shù)據(jù)窍仰。
我們知道驹吮,Http11Processor是應(yīng)用層協(xié)議的封裝,它會(huì)調(diào)用容器獲得響應(yīng)婚陪,再把響應(yīng)通過 Channel寫出近忙。

#3.Processor
Processor 用來實(shí)現(xiàn) HTTP 協(xié)議及舍,Processor 接收來自 EndPoint 的 Socket锯玛,
讀取字節(jié)流解析成 Tomcat Request 和 Response 對(duì)象攘残,
并通過 Adapter 將其提交到容器處理歼郭,Processor 是對(duì)應(yīng)用層協(xié)議的抽象病曾。

#4.請(qǐng)求由Connector傳導(dǎo)到Container
EndPoint 接收到 Socket 連接后泰涂,生成一個(gè) SocketProcessor 任務(wù)提交到線程池去處理逼蒙,
SocketProcessor 的 Run 方法會(huì)調(diào)用 HttpProcessor 組件去解析應(yīng)用層協(xié)議是牢,
Processor 通過解析生成 Request 對(duì)象后驳棱,會(huì)調(diào)用 Adapter 的 Service 方法蹈胡,
方法內(nèi)部通過 以下代碼將請(qǐng)求傳遞到容器中罚渐。
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
ProtocolHandler工作流程.png
Processor的位置.png

3.1.1.2 Adapter 組件

由于協(xié)議的不同荷并,Tomcat 定義了自己的 Request 類來存放請(qǐng)求信息源织,這里其實(shí)體現(xiàn)了面向?qū)ο蟮乃季S谈息。
但是這個(gè) Request 不是標(biāo)準(zhǔn)的 ServletRequest 侠仇,所以不能直接使用 Tomcat 定義 Request 作為參數(shù)直接容器逻炊。

Tomcat 設(shè)計(jì)者的解決方案是引入 CoyoteAdapter余素,這是適配器模式的經(jīng)典運(yùn)用桨吊,連接器調(diào)用 CoyoteAdapter 的 Sevice 方法屏积,
傳入的是 Tomcat Request 對(duì)象,CoyoteAdapter負(fù)責(zé)將 Tomcat Request 轉(zhuǎn)成 ServletRequest卷要,再調(diào)用容器的 Service方法僧叉。

3.1.2 容器

連接器負(fù)責(zé)外部交流隘道,容器負(fù)責(zé)內(nèi)部處理谭梗。
Connector: 連接器處理 Socket 通信和應(yīng)用層協(xié)議的解析激捏,得到 Servlet請(qǐng)求远舅;
Container: 容器則負(fù)責(zé)處理 Servlet請(qǐng)求图柏。

容器就是拿來裝東西的蚤吹, 所以 Tomcat 容器就是拿來裝載 Servlet距辆。

Tomcat 設(shè)計(jì)了 4 種容器(父子關(guān)系)跨算,分別是 Engine诸蚕、Host背犯、Context和 Wrapper漠魏。Server 代表 Tomcat 實(shí)例柱锹。

#你可能會(huì)問禁熏,為啥要設(shè)計(jì)這么多層次的容器瞧毙,這不是增加復(fù)雜度么?
原因在于Tomcat 通過一種分層的架構(gòu),使得 Servlet 容器具有很好的靈活性。
因?yàn)檫@里一個(gè) Host 多個(gè) Context檀训, 一個(gè) Context 也包含多個(gè) Servlet峻凫,而每個(gè)組件都需要統(tǒng)一生命周期管理荧琼,所以組合模式設(shè)計(jì)這些容器:
>> Wrapper 表示一個(gè) Servlet 命锄,Context 表示一個(gè) Web 應(yīng)用程序脐恩,而一個(gè) Web 程序可能有多個(gè) Servlet 驶冒;
>> Host 表示一個(gè)虛擬主機(jī)骗污,或者說一個(gè)站點(diǎn)。
>> 一個(gè) Tomcat 可以配置多個(gè)站點(diǎn)(Host)贴谎;
>> 一個(gè)站點(diǎn)( Host) 可以部署多個(gè) Web 應(yīng)用擅这;
>> Engine 代表 引擎仲翎,用于管理多個(gè)站點(diǎn)(Host)
>> 一個(gè) Service 只能有一個(gè) Engine溯香。

#可通過 Tomcat 配置文件加深對(duì)其層次關(guān)系理解玫坛。
// Server 頂層組件湿镀,可包含多個(gè) Service赫模,代表一個(gè) Tomcat 實(shí)例
<Server port="8005" shutdown="SHUTDOWN">


    // 頂層組件瀑罗,包含一個(gè) Engine 斩祭,多個(gè)連接器
    <Service name="Catalina">
  
        // 連接器
        <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
        // 連接器
        <!-- Define an AJP 1.3 Connector on port 8009 -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />  


        // 容器組件:一個(gè) Engine 處理 Service 所有請(qǐng)求,包含多個(gè) Host
        <Engine name="Catalina" defaultHost="localhost">
            // 容器組件:處理指定Host下的客戶端請(qǐng)求席赂, 可包含多個(gè) Context
            <Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
            // 容器組件:處理特定 Context Web應(yīng)用的所有客戶端請(qǐng)求
            <Context></Context>
            </Host>
        </Engine>
    </Service>
</Server>

3.1.2.1 如何管理這些容器: 組合模式

我們發(fā)現(xiàn)容器之間具有父子關(guān)系颅停,形成一個(gè)樹形結(jié)構(gòu)癞揉,Tomcat 就是用組合模式來管理這些容器的。
具體實(shí)現(xiàn)方法是芥牌,所有容器組件都實(shí)現(xiàn)了 Container 接口壁拉,
因此組合模式可以使得用戶對(duì)單容器對(duì)象和組合容器對(duì)象的使用具有一致性弃理。
這里單容器對(duì)象指的是最底層的 Wrapper钥勋,組合容器對(duì)象指的是上面的 Context算灸、Host或者 Engine。
Container 接口定義如下:
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);
}
getParent、SetParent沐旨、addChild和 removeChild等方法磁携,這里正好驗(yàn)證了組合模式谊迄。
Container接口拓展了 Lifecycle 统诺,Tomcat 就是通過 Lifecycle 統(tǒng)一管理所有容器的組件的生命周期。
通過組合模式管理所有容器啄寡,拓展 Lifecycle 實(shí)現(xiàn)對(duì)每個(gè)組件的生命周期管理挺物,
Lifecycle 主要包含的方法init()姻乓、start()嵌溢、stop() 和 destroy()。

3.2 tomcat服務(wù)器啟動(dòng)與請(qǐng)求處理過程

tomcat應(yīng)用服務(wù)器啟動(dòng)過程示意圖.png
tomcat請(qǐng)求處理過程示意圖.png

3.2.1 服務(wù)器啟動(dòng)原理

#Tomcat整體架構(gòu)
>> Server 對(duì)應(yīng)的就是一個(gè) Tomcat 實(shí)例蹋岩。
>> Service 默認(rèn)只有一個(gè)赖草,也就是一個(gè) Tomcat 實(shí)例默認(rèn)一個(gè) Service守问。
>> Connector:一個(gè) Service 可能多個(gè) 連接器,接受不同連接協(xié)議蠕蚜。
>> Container: 多個(gè)連接器對(duì)應(yīng)一個(gè)容器扣囊,頂層容器其實(shí)就是 Engine乎折。

每個(gè)組件都有對(duì)應(yīng)的生命周期,需要啟動(dòng)侵歇,同時(shí)還要啟動(dòng)自己內(nèi)部的子組件骂澄,
比如一個(gè) Tomcat 實(shí)例包含一個(gè) Service,一個(gè) Service 包含多個(gè)連接器和一個(gè)容器惕虑。
而一個(gè)容器包含多個(gè) Host坟冲, Host 內(nèi)部可能有多個(gè) Contex t 容器,而一個(gè) Context 也會(huì)包含多個(gè) Servlet溃蔫,
所以 Tomcat 利用組合模式管理組件每個(gè)組件健提,對(duì)待過個(gè)也想對(duì)待單個(gè)組一樣對(duì)待。
整體上每個(gè)組件設(shè)計(jì)就像是「俄羅斯套娃」一樣伟叛。

#Tomcat 啟動(dòng)流程:
startup.sh -> catalina.sh start ->java -jar org.apache.catalina.startup.Bootstrap.main()

#Tomcat 有 2 個(gè)核心功能:
>> 處理 Socket 連接私痹,負(fù)責(zé)網(wǎng)絡(luò)字節(jié)流與 Request 和 Response 對(duì)象的轉(zhuǎn)化。
>> 加載并管理 Servlet 统刮,以及處理具體的 Request 請(qǐng)求紊遵。
所以 Tomcat 設(shè)計(jì)了兩個(gè)核心組件連接器(Connector)和容器(Container)。連接器負(fù)責(zé)對(duì)外交流侥蒙,容器負(fù)責(zé)內(nèi)部處理癞蚕。
Tomcat為了實(shí)現(xiàn)支持多種 I/O 模型和應(yīng)用層協(xié)議,一個(gè)容器可能對(duì)接多個(gè)連接器辉哥,就好比一個(gè)房間有多個(gè)門桦山。

組合/觀察者/模板模式在LifeCycle中的運(yùn)用

#Lifecycle 生命周期
前面我們看到 Container容器 繼承了 Lifecycle 生命周期。
如果想讓一個(gè)系統(tǒng)能夠?qū)ν馓峁┓?wù)醋旦,我們需要?jiǎng)?chuàng)建恒水、組裝并啟動(dòng)這些組件;
在服務(wù)停止的時(shí)候饲齐,我們還需要釋放資源钉凌,銷毀這些組件,因此這是一個(gè)動(dòng)態(tài)的過程捂人。
也就是說御雕,Tomcat 需要?jiǎng)討B(tài)地管理這些組件的生命周期矢沿。
如何統(tǒng)一管理組件的創(chuàng)建、初始化酸纲、啟動(dòng)捣鲸、停止和銷毀?如何做到代碼邏輯清晰闽坡?
如何方便地添加或者刪除組件栽惶?如何做到組件啟動(dòng)和停止不遺漏、不重復(fù)疾嗅?

#答案是: 一鍵式啟停:LifeCycle 接口
設(shè)計(jì)就是要找到系統(tǒng)的變化點(diǎn)和不變點(diǎn)外厂。
這里的不變點(diǎn)就是每個(gè)組件都要經(jīng)歷創(chuàng)建、初始化代承、啟動(dòng)這幾個(gè)過程汁蝶,這些狀態(tài)以及狀態(tài)的轉(zhuǎn)化是不變的。
而變化點(diǎn)是每個(gè)具體組件的初始化方法论悴,也就是啟動(dòng)方法是不一樣的掖棉。

因此,Tomcat 把不變點(diǎn)抽象出來成為一個(gè)接口意荤,這個(gè)接口跟生命周期有關(guān)啊片,叫作 LifeCycle只锻。
LifeCycle 接口里定義這么幾個(gè)方法:init()玖像、start()、stop() 和 destroy()齐饮,每個(gè)具體的組件(也就是容器)去實(shí)現(xiàn)這些方法捐寥。

在父組件的 init() 方法里需要?jiǎng)?chuàng)建子組件并調(diào)用子組件的 init() 方法。
同樣祖驱,在父組件的 start()方法里也需要調(diào)用子組件的 start() 方法握恳,
因此調(diào)用者可以無差別的調(diào)用各組件的 init() 方法和 start() 方法,這就是組合模式的使用捺僻,
并且只要調(diào)用最頂層組件乡洼,也就是 Server 組件的 init()和start() 方法,整個(gè) Tomcat 就被啟動(dòng)起來了匕坯。
所以 Tomcat 采取組合模式管理容器束昵,容器繼承 LifeCycle 接口,
這樣就可以向針對(duì)單個(gè)對(duì)象一樣一鍵管理各個(gè)容器的生命周期葛峻,整個(gè) Tomcat 就啟動(dòng)起來锹雏。

#重用性(模板設(shè)計(jì)模式):LifeCycleBase 抽象基類
有了接口,我們就要用類去實(shí)現(xiàn)接口术奖。一般來說實(shí)現(xiàn)類不止一個(gè)礁遵,不同的類在實(shí)現(xiàn)接口時(shí)往往會(huì)有一些相同的邏輯轻绞,
如果讓各個(gè)子類都去實(shí)現(xiàn)一遍,就會(huì)有重復(fù)代碼佣耐。那子類如何重用這部分邏輯呢政勃?
其實(shí)就是定義一個(gè)基類來實(shí)現(xiàn)共同的邏輯,然后讓各個(gè)子類去繼承它晰赞,就達(dá)到了重用的目的稼病。

Tomcat 定義一個(gè)基類 LifeCycleBase 來實(shí)現(xiàn) LifeCycle 接口,把一些公共的邏輯放到基類中去掖鱼,
比如生命狀態(tài)的轉(zhuǎn)變與維護(hù)然走、生命事件的觸發(fā)以及監(jiān)聽器的添加和刪除等,而子類就負(fù)責(zé)實(shí)現(xiàn)自己的初始化戏挡、啟動(dòng)和停止等方法芍瑞。

#可擴(kuò)展性:LifeCycle Event
我們再來考慮另一個(gè)問題,那就是系統(tǒng)的可擴(kuò)展性褐墅。
因?yàn)楦鱾€(gè)組件init() 和 start() 方法的具體實(shí)現(xiàn)是復(fù)雜多變的拆檬,
比如在 Host 容器的啟動(dòng)方法里需要掃描 webapps 目錄下的 Web 應(yīng)用,創(chuàng)建相應(yīng)的 Context 容器妥凳,
如果將來需要增加新的邏輯竟贯,直接修改start() 方法?這樣會(huì)違反開閉原則逝钥,那如何解決這個(gè)問題呢屑那?
開閉原則說的是為了擴(kuò)展系統(tǒng)的功能,你不能直接修改系統(tǒng)中已有的類艘款,但是你可以定義新的類持际。

組件的 init() 和 start() 調(diào)用是由它的父組件的狀態(tài)變化觸發(fā)的,上層組件的初始化會(huì)觸發(fā)子組件的初始化哗咆,
上層組件的啟動(dòng)會(huì)觸發(fā)子組件的啟動(dòng)蜘欲,因此我們把組件的生命周期定義成一個(gè)個(gè)狀態(tài),把狀態(tài)的轉(zhuǎn)變看作是一個(gè)事件晌柬。
而事件是有監(jiān)聽器的姥份,在監(jiān)聽器里可以實(shí)現(xiàn)一些邏輯,并且監(jiān)聽器也可以方便的添加和刪除年碘,這就是典型的觀察者模式澈歉。

#接口分離的原則的體現(xiàn)
Container 繼承了 LifeCycle,
StandardEngine盛泡、StandardHost闷祥、StandardContext 和 StandardWrapper 是相應(yīng)容器組件的具體實(shí)現(xiàn)類,因?yàn)樗鼈兌际侨萜鳎岳^承了 ContainerBase 抽象基類凯砍,
ContainerBase 實(shí)現(xiàn)了 Container 接口箱硕,也繼承了 LifeCycleBase 類,它們的生命周期管理接口和功能接口是分開的悟衩,
這也符合設(shè)計(jì)中接口分離的原則剧罩。

#如果你需要維護(hù)一堆具有父子關(guān)系的實(shí)體,可以考慮使用組合模式座泳。
public abstract class LifecycleBase implements Lifecycle {
    // 持有所有的觀察者
    private final List<LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
    // 發(fā)布事件
    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);
        }
    }
    // 模板方法定義整個(gè)啟動(dòng)流程惠昔,啟動(dòng)所有容器
    @Override
    public final synchronized void init() throws LifecycleException {
        //1. 狀態(tài)檢查
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
        try {
            //2. 觸發(fā) INITIALIZING 事件的監(jiān)聽器
            setStateInternal(LifecycleState.INITIALIZING, null, false);
            // 3. 調(diào)用具體子類的初始化方法
            initInternal();
            // 4. 觸發(fā) INITIALIZED 事件的監(jiān)聽器
            setStateInternal(LifecycleState.INITIALIZED, null, false);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }
}

3.2.2 服務(wù)器接收請(qǐng)求原理

從本質(zhì)上講,應(yīng)用服務(wù)器的請(qǐng)求處理開始于監(jiān)聽的Socket端口接收到數(shù)據(jù)挑势,結(jié)束于將服務(wù)器處理結(jié)果寫入Socket輸出流镇防。

#一個(gè)請(qǐng)求是如何定位到讓哪個(gè) Wrapper 的 Servlet 處理的?
Tomcat 是用 Mapper 組件來定位到讓哪個(gè) Wrapper 的 Servlet 處理請(qǐng)求的潮饱。
Mapper 組件的功能就是將用戶請(qǐng)求的 URL 定位到一個(gè) Servlet来氧,它的工作原理是:
Mapper組件里保存了 Web 應(yīng)用的配置信息,其實(shí)就是容器組件與訪問路徑的映射關(guān)系香拉,
比如 Host容器里配置的域名啦扬、Context容器里的 Web應(yīng)用路徑,以及 Wrapper容器里 Servlet 映射的路徑凫碌,
你可以想象這些配置信息就是一個(gè)多層次的 Map扑毡。
當(dāng)一個(gè)請(qǐng)求到來時(shí),Mapper 組件通過解析請(qǐng)求 URL 里的域名和路徑盛险,再到自己保存的 Map 里去查找瞄摊,就能定位到一個(gè) Servlet。
一個(gè)請(qǐng)求 URL 最后只會(huì)定位到一個(gè) Wrapper容器枉层,也就是一個(gè) Servlet泉褐。

#MVC框架進(jìn)一步封裝
當(dāng)然赐写,如果我們的應(yīng)用不是基于簡單的Servlet API,而是基于當(dāng)前成熟的MVC框架(如Apache Struts鸟蜡、Spring MVC),
那么在多數(shù)情況下請(qǐng)求將進(jìn)一步匹配到Servlet下的一個(gè)控制器——這部分已經(jīng)不屬于應(yīng)用服務(wù)器的處理范疇挺邀,而是由具體的MVC框架進(jìn)行匹配揉忘。
當(dāng)Servlet或者控制器的業(yè)務(wù)處理結(jié)束后,處理結(jié)果將被寫入一個(gè)與通信方案無關(guān)的響應(yīng)對(duì)象。

最后端铛,該響應(yīng)對(duì)象將按照既定協(xié)議寫入輸出流泣矛。

3.2.3 Tomcat處理http請(qǐng)求流程

假如有用戶訪問一個(gè) URL,比如http://user.shopping.com:8080/order/buy禾蚕,Tomcat 如何將這個(gè) URL 定位到一個(gè) Servlet 呢您朽?

1.首先根據(jù)協(xié)議和端口號(hào)確定 Service 和 Engine。
Tomcat 默認(rèn)的 HTTP 連接器監(jiān)聽 8080 端口、默認(rèn)的 AJP 連接器監(jiān)聽 8009 端口哗总。
上面例子中的 URL 訪問的是 8080 端口几颜,因此這個(gè)請(qǐng)求會(huì)被 HTTP 連接器接收,
而一個(gè)連接器是屬于一個(gè) Service 組件的讯屈,這樣 Service 組件就確定了蛋哭。
我們還知道一個(gè) Service 組件里除了有多個(gè)連接器,還有一個(gè)容器組件涮母,
具體來說就是一個(gè) Engine 容器谆趾,因此 Service 確定了也就意味著 Engine 也確定了。

2.根據(jù)域名選定 Host叛本。 
Service 和 Engine 確定后沪蓬,Mapper 組件通過 URL 中的域名去查找相應(yīng)的 Host 容器,
比如例子中的 URL 訪問的域名是user.shopping.com来候,因此 Mapper 會(huì)找到 Host2 這個(gè)容器怜跑。

3.根據(jù) URL 路徑找到 Context 組件。 
Host 確定以后吠勘,Mapper 根據(jù) URL 的路徑來匹配相應(yīng)的 Web 應(yīng)用的路徑性芬,
比如例子中訪問的是 /order,因此找到了 Context4 這個(gè) Context 容器剧防。

4.根據(jù) URL 路徑找到 Wrapper(Servlet)植锉。 
Context 確定后,Mapper 再根據(jù) web.xml 中配置的 Servlet 映射路徑來找到具體的 Wrapper 和 Servlet峭拘。
Tomcat處理http請(qǐng)求流程.png
SpringMVC對(duì)HttpServlet的改造.png

3.2.4 父子容器的請(qǐng)求傳遞: Pipeline-Valve

連接器中的 Adapter 會(huì)調(diào)用容器的 Service 方法來執(zhí)行 Servlet俊庇,最先拿到請(qǐng)求的是 Engine 容器,
Engine 容器對(duì)請(qǐng)求做一些處理后鸡挠,會(huì)把請(qǐng)求傳給自己子容器 Host 繼續(xù)處理辉饱,
依次類推,最后這個(gè)請(qǐng)求會(huì)傳給 Wrapper 容器拣展,Wrapper 會(huì)調(diào)用最終的 Servlet 來處理彭沼。
那么這個(gè)調(diào)用過程具體是怎么實(shí)現(xiàn)的呢?答案是使用 Pipeline-Valve 管道备埃。

#1.Pipeline-Valve 是責(zé)任鏈模式:
責(zé)任鏈模式是指在一個(gè)請(qǐng)求處理的過程中有很多處理者依次對(duì)請(qǐng)求進(jìn)行處理姓惑,
每個(gè)處理者負(fù)責(zé)做自己相應(yīng)的處理,處理完之后將再調(diào)用下一個(gè)處理者繼續(xù)處理按脚,
Valve 表示一個(gè)處理點(diǎn)(也就是一個(gè)處理閥門)于毙,因此 invoke方法就是來處理請(qǐng)求的。
"
public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

public interface Pipeline {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}
"

#2.Pipeline && Valve
Pipeline中有 addValve方法辅搬。Pipeline 中維護(hù)了 Valve鏈表唯沮,Valve可以插入到 Pipeline中,對(duì)請(qǐng)求做某些處理。
我們還發(fā)現(xiàn) Pipeline 中沒有 invoke 方法介蛉,因?yàn)檎麄€(gè)調(diào)用鏈的觸發(fā)是 Valve 來完成的夯缺,
Valve完成自己的處理后,調(diào)用 getNext.invoke() 來觸發(fā)下一個(gè) Valve 調(diào)用甘耿。

其實(shí)每個(gè)容器都有一個(gè) Pipeline 對(duì)象踊兜,只要觸發(fā)了這個(gè) Pipeline 的第一個(gè) Valve,這個(gè)容器里 Pipeline中的 Valve 就都會(huì)被調(diào)用到佳恬。
但是捏境,不同容器的 Pipeline 是怎么鏈?zhǔn)接|發(fā)的呢,比如 Engine 中 Pipeline 需要調(diào)用下層容器 Host 中的 Pipeline毁葱。

答案是利用 Pipeline 中的 getBasic方法垫言。
這個(gè) BasicValve 處于 Valve 鏈表的末端,它是 Pipeline中必不可少的一個(gè) Valve倾剿,
負(fù)責(zé)調(diào)用下層容器的 Pipeline 里的第一個(gè) Valve筷频。

#3.整個(gè)過程分是通過連接器中的 CoyoteAdapter 觸發(fā),它會(huì)調(diào)用 Engine 的第一個(gè) Valve

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) {
    // 省略其他代碼
    // Calling the container
    connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
    ...
}
Pipeline-Valve.png
Pipeline-Valve.png

3.2.5 Filter機(jī)制

Wrapper 容器的最后一個(gè) Valve 會(huì)創(chuàng)建一個(gè) Filter 鏈前痘,并調(diào)用 doFilter() 方法凛捏,最終會(huì)調(diào)到 Servlet的 service方法。

前面我們不是講到了 Valve芹缔,似乎也有相似的功能坯癣,那 Valve 和 Filter有什么區(qū)別嗎?
>> Valve是 Tomcat的私有機(jī)制最欠,與 Tomcat 的基礎(chǔ)架構(gòu) API是緊耦合的示罗。
Servlet API是公有的標(biāo)準(zhǔn),所有的 Web 容器包括 Jetty 都支持 Filter 機(jī)制芝硬。
>> Valve工作在 Web 容器級(jí)別蚜点,攔截所有應(yīng)用的請(qǐng)求;而 Servlet Filter 工作在應(yīng)用級(jí)別拌阴,只能攔截某個(gè) Web 應(yīng)用的所有請(qǐng)求绍绘。
如果想做整個(gè) Web容器的攔截器,必須通過 Valve來實(shí)現(xiàn)皮官。

3.3 tomcat生命周期事件

Tomcat生命周期事件與狀態(tài)映射.png

3.4 tomcat類加載器

參考此文:
https://www.cnblogs.com/psy-code/p/14853753.html

3.5 tomcat熱部署與熱加載

得益于tomcat的類加載器設(shè)計(jì)模型, tomcat支持熱部署與熱加載.

3.5.1 tomcat 熱加載

3.5.2 tomcat 熱部署

3.6 tomcat 四種線程模型

#通過配置方法 server.xml來改變

#1.NIO  同步非阻塞
比傳統(tǒng)BIO能更好的支持大并發(fā)脯倒,tomcat 8.0 后默認(rèn)采用該模式     <Connector port="8080" protocol="HTTP/1.1"/> 改為 protocol="org.apache.coyote.http11.Http11NioProtocol"

#2.BIO  阻塞式IO
tomcat7之前默認(rèn)实辑,采用傳統(tǒng)的java IO進(jìn)行操作捺氢,該模式下每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)線程,適用于并發(fā)量小的場景    protocol =" org.apache.coyote.http11.Http11Protocol"

#3.APR  
tomcat 以JNI形式調(diào)用http服務(wù)器的核心動(dòng)態(tài)鏈接庫來處理文件讀取或網(wǎng)絡(luò)傳輸操作剪撬,需要編譯安裝APR庫     protocol ="org.apache.coyote.http11.Http11AprProtocol"

#4.AIO  異步非阻塞 (NIO2)
tomcat8.0后支持    protocol ="org.apache.coyote.http11.Http11Nio2Protocol" 多用于連接數(shù)目多且連接比較長(重操作)的架構(gòu)摄乒,比如相冊服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜馍佑,JDK7開始支持

參考資源
<<Tomcat架構(gòu)解析>> 劉光瑞
<<看透springMvc源代碼分析與實(shí)踐>> 韓路彪
http://www.reibang.com/p/075ec0deb1f3

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斋否,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拭荤,更是在濱河造成了極大的恐慌茵臭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舅世,死亡現(xiàn)場離奇詭異旦委,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)雏亚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門缨硝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人罢低,你說我怎么就攤上這事查辩。” “怎么了网持?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵宜岛,是天一觀的道長。 經(jīng)常有香客問我功舀,道長谬返,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任日杈,我火速辦了婚禮遣铝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘莉擒。我一直安慰自己酿炸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布涨冀。 她就那樣靜靜地躺著填硕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹿鳖。 梳的紋絲不亂的頭發(fā)上扁眯,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音翅帜,去河邊找鬼姻檀。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涝滴,可吹牛的內(nèi)容都是我干的绣版。 我是一名探鬼主播胶台,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杂抽!你這毒婦竟也來了诈唬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤缩麸,失蹤者是張志新(化名)和其女友劉穎铸磅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杭朱,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡愚屁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了痕檬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霎槐。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖梦谜,靈堂內(nèi)的尸體忽然破棺而出丘跌,到底是詐尸還是另有隱情,我是刑警寧澤唁桩,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布闭树,位于F島的核電站,受9級(jí)特大地震影響荒澡,放射性物質(zhì)發(fā)生泄漏报辱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一单山、第九天 我趴在偏房一處隱蔽的房頂上張望碍现。 院中可真熱鬧,春花似錦米奸、人聲如沸昼接。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慢睡。三九已至,卻和暖如春铡溪,著一層夾襖步出監(jiān)牢的瞬間漂辐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工棕硫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留髓涯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓饲帅,卻偏偏與公主長得像复凳,于是被迫代替她去往敵國和親瘤泪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灶泵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容