本章結(jié)構(gòu)如下:
- 前言
- Tomcat頂層結(jié)構(gòu)
- Server
- Service
- Connector
- Container
- Tomncat啟動(dòng)流程
一、前言
一般而言唱较,對(duì)于一個(gè)復(fù)雜的系統(tǒng)哥纫,直接扎進(jìn)去看源碼會(huì)是很難受的霉旗,會(huì)浪費(fèi)大量的時(shí)間和腦細(xì)胞,卻得不到理想的效果蛀骇。
這個(gè)時(shí)候厌秒,策略很重要,應(yīng)該明白擅憔,越是復(fù)雜的東西鸵闪,越會(huì)有良好的邏輯和層次,否則開發(fā)者自己估計(jì)過段時(shí)間怕也搞不清了暑诸。
tomcat是一個(gè)很大的系統(tǒng)蚌讼,有復(fù)雜的結(jié)構(gòu),想要了解它个榕,就應(yīng)該順著開發(fā)者設(shè)計(jì)之初的思路來(lái)篡石,先了解整體的結(jié)構(gòu),對(duì)整體有了一定的掌控后西采,再逐個(gè)分析凰萨,了解感興趣的細(xì)節(jié)。
二械馆、Tomcat頂層結(jié)構(gòu)
先上一張圖:
上圖大概展示了tomcat的結(jié)構(gòu)胖眷,下面先簡(jiǎn)單介紹各個(gè)模塊:
- Server:服務(wù)器的意思,代表整個(gè)tomcat服務(wù)器狱杰,一個(gè)tomcat只有一個(gè)Server瘦材;
- Service:Server中的一個(gè)邏輯功能層厅须, 一個(gè)Server可以包含多個(gè)Service仿畸;
- Connector:稱作連接器,是Service的核心組件之一朗和,一個(gè)Service可以有多個(gè)Connector错沽,主要是連接客戶端請(qǐng)求;
- Container:Service的另一個(gè)核心組件眶拉,按照層級(jí)有Engine千埃,Host,Context忆植,Wrapper四種放可,一個(gè)Service只有一個(gè)Engine谒臼,其主要作用是執(zhí)行業(yè)務(wù)邏輯;
- Jasper:JSP引擎耀里;
- Session:會(huì)話管理蜈缤;
- ...
上面簡(jiǎn)單列了tomcat的模塊結(jié)構(gòu),下面結(jié)合配置文件更加具體一點(diǎn)來(lái)分析冯挎,當(dāng)然更多是集中在Connector和Container兩個(gè)組件上底哥,畢竟這是兩個(gè)核心組件,后續(xù)的內(nèi)容也會(huì)更多集中在這兩個(gè)組件上面房官。
先將conf/server.xml配置文件內(nèi)容貼出供參考(注釋部分沒有貼出):
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"/>
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"/>
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"/>
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b"/>
</Host>
</Engine>
</Service>
</Server>
三趾徽、Server
Server是Tomcat最頂層的容器,代表著整個(gè)服務(wù)器翰守,即一個(gè)Tomcat只有一個(gè)Server孵奶,Server中包含至少一個(gè)Service組件,用于提供具體服務(wù)蜡峰。這個(gè)在配置文件中也得到很好的體現(xiàn)(port="8005" shutdown="SHUTDOWN"是在8005端口監(jiān)聽到"SHUTDOWN"命令拒课,服務(wù)器就會(huì)停止)。
Tomcat中其標(biāo)準(zhǔn)實(shí)現(xiàn)是:org.apache.catalina.core.StandardServer類事示,其繼承結(jié)構(gòu)類圖如下:
StandardServer實(shí)現(xiàn)Server很好理解早像,tomcat為所有的組件都提供了生命周期管理,繼承LifecycleMBeanBase則跟tomcat中的生命周期機(jī)制有關(guān)肖爵,后續(xù)文章會(huì)有介紹卢鹦。
四、Service
可以想象劝堪,一個(gè)Server服務(wù)器冀自,它最基本的功能肯定是:
接收客戶端的請(qǐng)求,然后解析請(qǐng)求秒啦,完成相應(yīng)的業(yè)務(wù)邏輯熬粗,然后把處理后的結(jié)果返回給客戶端,一般會(huì)提供兩個(gè)節(jié)本方法余境,一個(gè)start打開服務(wù)Socket連接驻呐,監(jiān)聽服務(wù)端口,一個(gè)stop停止服務(wù)釋放網(wǎng)絡(luò)資源芳来。
這時(shí)的服務(wù)器就是一個(gè)Server類:
如何實(shí)現(xiàn)這個(gè)簡(jiǎn)單的服務(wù)器含末,看過《深入剖析tomcat》的應(yīng)都知道,這部分代碼之前也敲過即舌,在github上(https://github.com/w1992wishes/tomcat-work)佣盒,其實(shí)就是在一個(gè)端口上監(jiān)聽Socket請(qǐng)求,然后解析請(qǐng)求顽聂,返回處理結(jié)果肥惭。
但如果將請(qǐng)求監(jiān)聽和請(qǐng)求處理放在一起盯仪,擴(kuò)展性會(huì)變差,畢竟網(wǎng)絡(luò)協(xié)議不止HTTP一種蜜葱,如果想適配多種網(wǎng)絡(luò)協(xié)議磨总,請(qǐng)求處理又相同,這時(shí)就無(wú)能為力了笼沥,tomcat的設(shè)計(jì)大師不會(huì)采取這種做法蚪燕,而是將請(qǐng)求監(jiān)聽和請(qǐng)求處理分開為兩個(gè)模塊,分別是Connector和Container奔浅,Connector負(fù)責(zé)處理請(qǐng)求監(jiān)聽馆纳,Container負(fù)責(zé)處理請(qǐng)求處理。
但顯然tomcat可以有多個(gè)Connector汹桦,同時(shí)Container也可以有多個(gè)鲁驶。那這就存在一個(gè)問題,哪個(gè)Connector對(duì)應(yīng)哪個(gè)Container舞骆,提供復(fù)雜的映射嗎钥弯?相信看過server.xml文件的人已經(jīng)知道了tomcat是怎么處理的了。
沒錯(cuò)督禽,Service就是這樣來(lái)的脆霎。在conf/server.xml文件中,可以看到Service組件包含了Connector組件和Engine組件(前面有提過狈惫,Engine就是一種容器)睛蛛,即Service相當(dāng)于Connector和Engine組件的包裝器,將一個(gè)或者多個(gè)Connector和一個(gè)Engine建立關(guān)聯(lián)關(guān)系胧谈。在默認(rèn)的配置文件中忆肾,定義了一個(gè)叫Catalina 的服務(wù),它將HTTP/1.1和AJP/1.3這兩個(gè)Connector與一個(gè)名為Catalina 的Engine關(guān)聯(lián)起來(lái)菱肖。
一個(gè)Server可以包含多個(gè)Service(它們相互獨(dú)立客冈,只是公用一個(gè)JVM及類庫(kù)),一個(gè)Service負(fù)責(zé)維護(hù)多個(gè)Connector和一個(gè)Container稳强。
其標(biāo)準(zhǔn)實(shí)現(xiàn)是StandardService场仲,UML類圖如下:
這時(shí)tomcat就是這樣了:
四、Connector
前面介紹過Connector是連接器键袱,用于接受請(qǐng)求并將請(qǐng)求封裝成Request和Response燎窘,然后交給Container進(jìn)行處理摹闽,Container處理完之后在交給Connector返回給客戶端蹄咖。
一個(gè)Connector會(huì)監(jiān)聽一個(gè)獨(dú)立的端口來(lái)處理來(lái)自客戶端的請(qǐng)求。server.xml默認(rèn)配置了兩個(gè)Connector:
-
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>
付鹿,它監(jiān)聽端口8080,這個(gè)端口值可以修改澜汤,connectionTimeout定義了連接超時(shí)時(shí)間蚜迅,單位是毫秒,redirectPort 定義了ssl的重定向接口俊抵,根據(jù)上述配置谁不,Connector會(huì)將ssl請(qǐng)求轉(zhuǎn)發(fā)到8443端口。 -
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
, AJP表示Apache Jserv Protocol徽诲,它將處理Tomcat和Apache http服務(wù)器之間的交互刹帕,此連接器用于處理我們將Tomcat和Apache http服務(wù)器結(jié)合使用的情況,如在同一臺(tái)物理Server上部署一個(gè)Apache http服務(wù)器和多臺(tái)Tomcat服務(wù)器谎替,通過Apache服務(wù)器來(lái)處理靜態(tài)資源以及負(fù)載均衡時(shí)偷溺,針對(duì)不同的Tomcat實(shí)例需要AJP監(jiān)聽不同的端口。
Connector在tomcat中的設(shè)計(jì)比較復(fù)雜钱贯,先大致列上一個(gè)圖:
這里先簡(jiǎn)單介紹Connector挫掏,后續(xù)會(huì)詳細(xì)分析。
Connector使用ProtocolHandler來(lái)處理請(qǐng)求的秩命,不同的ProtocolHandler代表不同的連接類型尉共,比如:Http11Protocol使用的是普通Socket來(lái)連接的(tomcat9已經(jīng)刪除了這個(gè)類,不再采用BIO的方式)弃锐,Http11NioProtocol使用的是NioSocket來(lái)連接的袄友。
其中ProtocolHandler由包含了三個(gè)部件:Endpoint、Processor霹菊、Adapter杠河。
- Endpoint用來(lái)處理底層Socket的網(wǎng)絡(luò)連接,Processor用于將Endpoint接收到的Socket封裝成Request(這個(gè)Request和ServletRequest無(wú)關(guān))浇辜,Adapter充當(dāng)適配器券敌,用于將Request轉(zhuǎn)換為ServletRequest交給Container進(jìn)行具體的處理。
- Endpoint由于是處理底層的Socket網(wǎng)絡(luò)連接柳洋,因此Endpoint是用來(lái)實(shí)現(xiàn)TCP/IP協(xié)議的待诅,而Processor用來(lái)實(shí)現(xiàn)HTTP協(xié)議的,Adapter將請(qǐng)求適配到Servlet容器進(jìn)行具體的處理熊镣。
- Endpoint的抽象實(shí)現(xiàn)AbstractEndpoint里面定義的Acceptor和AsyncTimeout兩個(gè)內(nèi)部類和一個(gè)Handler接口卑雁。Acceptor用于監(jiān)聽請(qǐng)求,AsyncTimeout用于檢查異步Request的超時(shí)绪囱,Handler用于處理接收到的Socket测蹲,在內(nèi)部調(diào)用Processor進(jìn)行處理。
先不用太糾結(jié)鬼吵,了解Connector是做什么的就可以扣甲,后續(xù)再深入。
五、Container
tomcat的container層次如下:
5.1琉挖、Engine
一個(gè)Service中有多個(gè)Connector和一個(gè)Engine启泣,Engine表示整個(gè)Servlet引擎,一個(gè)Engine下面可以包含一個(gè)或者多個(gè)Host示辈,即一個(gè)Tomcat實(shí)例可以配置多個(gè)虛擬主機(jī)寥茫,默認(rèn)的情況下 conf/server.xml 配置文件中<Engine name="Catalina" defaultHost="localhost">
定義了一個(gè)名為Catalina的Engine。
Engine的UML類圖如下:
ContainerBase和LifecycleBase都是抽象出來(lái)的公共層矾麻。
5.2纱耻、Host
Host,代表一個(gè)站點(diǎn)险耀,也可以叫虛擬主機(jī)膝迎,一個(gè)Host可以配置多個(gè)Context,在server.xml文件中的默認(rèn)配置為<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
, 其中appBase=webapps胰耗, 也就是<CATALINA_HOME>\webapps目錄限次,unpackingWARS=true 屬性指定在appBase指定的目錄中的war包都自動(dòng)的解壓,autoDeploy=true 屬性指定對(duì)加入到appBase目錄的war包進(jìn)行自動(dòng)的部署柴灯。
一個(gè)Engine包含多個(gè)Host的設(shè)計(jì)卖漫,使得一個(gè)服務(wù)器實(shí)例可以承擔(dān)多個(gè)域名的服務(wù),是很靈活的設(shè)計(jì)赠群。
其標(biāo)準(zhǔn)實(shí)現(xiàn)繼承圖如下:
5.3羊始、Context
Context,代表一個(gè)應(yīng)用程序查描,就是日常開發(fā)中的web程序突委,或者一個(gè)WEB-INF目錄以及下面的web.xml文件,換句話說(shuō)每一個(gè)運(yùn)行的webapp最終都是以Context的形式存在冬三,每個(gè)Context都有一個(gè)根路徑和請(qǐng)求路徑匀油;與Host的區(qū)別是Context代表一個(gè)應(yīng)用,如勾笆,默認(rèn)配置下webapps下的每個(gè)目錄都是一個(gè)應(yīng)用敌蚜,其中ROOT目錄中存放主應(yīng)用,其他目錄存放別的子應(yīng)用窝爪,而整個(gè)webapps是一個(gè)站點(diǎn)弛车。
在Tomcat中通常采用如下方式創(chuàng)建一個(gè)Context:
- 在<CATALINA_HOME>\webapps 目錄中創(chuàng)建一個(gè)目錄dirname,此時(shí)將自動(dòng)創(chuàng)建一個(gè)context蒲每,默認(rèn)context的訪問url為http://host:port/dirname纷跛,也可以通過在ContextRoot\META-INF 中創(chuàng)建一個(gè)context.xml文件,其中包含如下內(nèi)容來(lái)指定應(yīng)用的訪問路徑:<context path="/urlpath">
- 在server.xml文件中增加context 元素邀杏,如下:
<Context path="/urlpath" docBase="/test/xxx" reloadable=true />
這樣就可以通過http://host:port/urlpath訪問上面配置的應(yīng)用贫奠。
可以打開tomcat目錄對(duì)照一下:
其標(biāo)準(zhǔn)實(shí)現(xiàn)類圖如下:
5.4、Wrapper
一個(gè)Context可以包含多個(gè)Servlet處理不同請(qǐng)求,當(dāng)然現(xiàn)在的SpringMVC叮阅,struts框架的出現(xiàn)導(dǎo)致程序中不再是大量的Servlet刁品,但其實(shí)本質(zhì)是沒變的泣特,都是由Servlet來(lái)處理或者當(dāng)作入口浩姥。
在tomcat中Servlet被稱為wrapper,其標(biāo)準(zhǔn)類圖如下:
那么為什么要用Wrapper來(lái)表示Servlet状您?這和tomcat的處理機(jī)制有關(guān)勒叠,為了更加靈活,便于擴(kuò)展膏孟,tomcat是用管道(pipeline)和閥(valve)的形式來(lái)處理請(qǐng)求眯分,所以將Servlet丟給Wrapper。這個(gè)后續(xù)再分析柒桑。
那么現(xiàn)在tomcat就是這樣的:
六弊决、Tomncat啟動(dòng)流程
tomcat的啟動(dòng)流程很標(biāo)準(zhǔn)化,入口是BootStrap魁淳,統(tǒng)一按照生命周期管理接口Lifecycle的定義進(jìn)行啟動(dòng)飘诗。首先,調(diào)用init()方法逐級(jí)初始化界逛,接著調(diào)用start()方法進(jìn)行啟動(dòng)昆稿,同時(shí),每次調(diào)用伴隨著生命周期狀態(tài)變更事件的觸發(fā)息拜。
每一級(jí)組件除完成自身的處理外溉潭,還有負(fù)責(zé)調(diào)用子組件的相關(guān)調(diào)用,組件和組件之間是松耦合的少欺,可以通過配置進(jìn)行修改喳瓣。
大致流程圖如下:
七、總結(jié)
差不多就是這樣赞别,大概了解總體的結(jié)構(gòu)夫椭,當(dāng)然這里面沒有過多提及除Connector和Container外的其他結(jié)構(gòu),如果感興趣氯庆,可以自行學(xué)習(xí)蹭秋。