springboot 就像一條巨蟒,慢慢纏繞著我們,使我們麻痹。不得不承認(rèn),使用了 springboot 確實(shí)提高了工作效率,但同時(shí)也讓我們遺忘了很多技能。剛?cè)肷鐣?huì)的時(shí)候,我還是通過 tomcat 手動(dòng)部署 javaweb 項(xiàng)目,還經(jīng)常對(duì) tomcat 進(jìn)行性能調(diào)優(yōu)。除此之外,還需要自己理清楚各 jar 之間的關(guān)系,以避免 jar 丟失和各版本沖突導(dǎo)致服務(wù)啟動(dòng)異常的問題。到如今,這些繁瑣而又重復(fù)的工作已經(jīng)統(tǒng)統(tǒng)交給 springboot 處理,我們可以把更多的精力放在業(yè)務(wù)邏輯上。但是,清楚 tomcat 的工作原理和處理請(qǐng)求流程和分析 spring 框架源碼一樣的重要。至少面試官特別喜歡問這些底層原理和設(shè)計(jì)思路。希望這篇文章能給你一些幫助。
tomcat 整體架構(gòu)
tomcat 是一個(gè)免費(fèi)的、開源的、輕量級(jí)的 web 應(yīng)用服務(wù)器。適合在并發(fā)量不是很高的中小企業(yè)項(xiàng)目中使用。
文件目錄結(jié)構(gòu)
以下是 tomcat 8 主要目錄結(jié)構(gòu)
目錄 | 功能說明 |
---|---|
bin | 存放可執(zhí)行的文件,如 startup 和 shutdown |
conf | 存放配置文件,如核心配置文件 server.xml 和應(yīng)用默認(rèn)的部署描述文件 web.xml |
lib | 存放 tomcat 運(yùn)行需要的jar包 |
logs | 存放運(yùn)行的日志文件 |
webapps | 存放默認(rèn)的 web 應(yīng)用部署目錄 |
work | 存放 web 應(yīng)用代碼生成和編譯文件的臨時(shí)目錄 |
功能組件結(jié)構(gòu)
tomcat 的核心功能有兩個(gè),分別是負(fù)責(zé)接收和反饋外部請(qǐng)求的連接器 connector,和負(fù)責(zé)處理請(qǐng)求的容器 container。其中連接器和容器相輔相成,一起構(gòu)成了基本的 web 服務(wù) service。每個(gè) tomcat 服務(wù)器可以管理多個(gè) service。
組件 | 功能 |
---|---|
connector | 負(fù)責(zé)對(duì)外接收反饋請(qǐng)求。它是 tomcat 與外界的交通樞紐,監(jiān)聽端口接收外界請(qǐng)求,并將請(qǐng)求處理后傳遞給容器做業(yè)務(wù)處理,最后將容器處理后的結(jié)果反饋給外界。 |
container | 負(fù)責(zé)對(duì)內(nèi)處理業(yè)務(wù)邏輯。其內(nèi)部由engine、host、context 和 wrapper 四個(gè)容器組成,用于管理和調(diào)用 servlet 相關(guān)邏輯。 |
service | 對(duì)外提供的 web 服務(wù)。主要包含連接器和容器兩個(gè)核心組件,以及其他功能組件。tomcat 可以管理多個(gè) service,且各 service 之間相互獨(dú)立。 |
tomcat 連接器核心原理
tomcat 連接器框架——coyote
連接器核心功能
一、監(jiān)聽網(wǎng)絡(luò)端口,接收和響應(yīng)網(wǎng)絡(luò)請(qǐng)求。
二、網(wǎng)絡(luò)字節(jié)流處理。將收到的網(wǎng)絡(luò)字節(jié)流轉(zhuǎn)換成 tomcat request 再轉(zhuǎn)成標(biāo)準(zhǔn)的 servletrequest 給容器,同時(shí)將容器傳來的 servletresponse 轉(zhuǎn)成 tomcat response 再轉(zhuǎn)成網(wǎng)絡(luò)字節(jié)流。
連接器模塊設(shè)計(jì)
為滿足連接器的兩個(gè)核心功能,我們需要一個(gè)通訊端點(diǎn)來監(jiān)聽端口;需要一個(gè)處理器來處理網(wǎng)絡(luò)字節(jié)流;最后還需要一個(gè)適配器將處理后的結(jié)果轉(zhuǎn)成容器需要的結(jié)構(gòu)。
組件 | 功能 |
---|---|
endpoint | 端點(diǎn),用來處理 socket 接收和發(fā)送的邏輯。其內(nèi)部由 acceptor 監(jiān)聽請(qǐng)求、handler 處理數(shù)據(jù)、asynctimeout 檢查請(qǐng)求超時(shí)。具體的實(shí)現(xiàn)有 nioendpoint、aprendpoint 等。 |
processor | 處理器,負(fù)責(zé)構(gòu)建 tomcat request 和 response 對(duì)象。具體的實(shí)現(xiàn)有 http11processor、streamprocessor 等。 |
adapter | 適配器,實(shí)現(xiàn) tomcat request、response 與 servletrequest、servletresponse之間的相互轉(zhuǎn)換。這采用的是經(jīng)典的適配器設(shè)計(jì)模式。 |
protocolhandler | 協(xié)議處理器,將不同的協(xié)議和通訊方式組合封裝成對(duì)應(yīng)的協(xié)議處理器,如 http11nioprotocol 封裝的是 http + nio。 |
對(duì)應(yīng)的源碼包路徑 org.apache.coyote
。對(duì)應(yīng)的結(jié)構(gòu)圖如下
tomcat 容器核心原理
tomcat 容器框架——catalina
容器結(jié)構(gòu)分析
每個(gè) service 會(huì)包含一個(gè)容器。容器由一個(gè)引擎可以管理多個(gè)虛擬主機(jī)。每個(gè)虛擬主機(jī)可以管理多個(gè) web 應(yīng)用。每個(gè) web 應(yīng)用會(huì)有多個(gè) servlet 包裝器。engine、host、context 和 wrapper,四個(gè)容器之間屬于父子關(guān)系。
容器 | 功能 |
---|---|
engine | 引擎,管理多個(gè)虛擬主機(jī)。 |
host | 虛擬主機(jī),負(fù)責(zé) web 應(yīng)用的部署。 |
context | web 應(yīng)用,包含多個(gè) servlet 封裝器。 |
wrapper | 封裝器,容器的最底層。對(duì) servlet 進(jìn)行封裝,負(fù)責(zé)實(shí)例的創(chuàng)建、執(zhí)行和銷毀功能。 |
對(duì)應(yīng)的源碼包路徑 org.apache.coyote
。對(duì)應(yīng)的結(jié)構(gòu)圖如下
容器請(qǐng)求處理
容器的請(qǐng)求處理過程就是在 engine、host、context 和 wrapper 這四個(gè)容器之間層層調(diào)用,最后在 servlet 中執(zhí)行對(duì)應(yīng)的業(yè)務(wù)邏輯。各容器都會(huì)有一個(gè)通道 pipeline,每個(gè)通道上都會(huì)有一個(gè) basic valve(如standardenginevalve), 類似一個(gè)閘門用來處理 request 和 response 。其流程圖如下。
tomcat 請(qǐng)求處理流程
上面的知識(shí)點(diǎn)已經(jīng)零零碎碎地介紹了一個(gè) tomcat 是如何處理一個(gè)請(qǐng)求。簡(jiǎn)單理解就是連接器的處理流程 + 容器的處理流程 = tomcat 處理流程。哈!那么問題來了,tomcat 是如何通過請(qǐng)求路徑找到對(duì)應(yīng)的虛擬站點(diǎn)?是如何找到對(duì)應(yīng)的 servlet 呢?
映射器功能介紹
這里需要引入一個(gè)上面沒有介紹的組件 mapper。顧名思義,其作用是提供請(qǐng)求路徑的路由映射。根據(jù)請(qǐng)求url地址匹配是由哪個(gè)容器來處理。其中每個(gè)容器都會(huì)它自己對(duì)應(yīng)的mapper,如 mappedhost。不知道大家有沒有回憶起被 mapper class not found 支配的恐懼。在以前,每寫一個(gè)完整的功能,都需要在 web.xml 配置映射規(guī)則,當(dāng)文件越來越龐大的時(shí)候,各個(gè)問題隨著也會(huì)出現(xiàn)
http請(qǐng)求流程
打開 tomcat/conf 目錄下的 server.xml 文件來分析一個(gè)http://localhost:8080/docs/api 請(qǐng)求。
第一步:連接器監(jiān)聽的端口是8080。由于請(qǐng)求的端口和監(jiān)聽的端口一致,連接器接受了該請(qǐng)求。
第二步:因?yàn)橐娴哪J(rèn)虛擬主機(jī)是 localhost,并且虛擬主機(jī)的目錄是webapps。所以請(qǐng)求找到了 tomcat/webapps 目錄。
第三步:解析的 docs 是 web 程序的應(yīng)用名,也就是 context。此時(shí)請(qǐng)求繼續(xù)從 webapps 目錄下找 docs 目錄。有的時(shí)候我們也會(huì)把應(yīng)用名省略。
第四步:解析的 api 是具體的業(yè)務(wù)邏輯地址。此時(shí)需要從 docs/web-inf/web.xml 中找映射關(guān)系,最后調(diào)用具體的函數(shù)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?xml version= "1.0" encoding= "utf-8" ?> <server port= "8005" shutdown= "shutdown" > <service name= "catalina" > <!-- 連接器監(jiān)聽端口是 8080 ,默認(rèn)通訊協(xié)議是 http/ 1.1 --> <connector port= "8080" protocol= "http/1.1" connectiontimeout= "20000" redirectport= "8443" /> <!-- 名字為 catalina 的引擎,其默認(rèn)的虛擬主機(jī)是 localhost --> <engine name= "catalina" defaulthost= "localhost" > <!-- 名字為 localhost 的虛擬主機(jī),其目錄是 webapps--> <host name= "localhost" appbase= "webapps" unpackwars= "true" autodeploy= "true" > </host> </engine> </service> </server> |
springboot 如何啟動(dòng)內(nèi)嵌的 tomcat
springboot 一鍵啟動(dòng)服務(wù)的功能,讓有很多剛?cè)肷鐣?huì)的朋友都忘記 tomcat 是啥。隨著硬件的性能越來越高,普通中小項(xiàng)目都可以直接用內(nèi)置 tomcat 啟動(dòng)。但是有些大一點(diǎn)的項(xiàng)目可能會(huì)用到 tomcat 集群和調(diào)優(yōu),內(nèi)置的 tomcat 就不一定能滿足需求了。
我們先從源碼中分析 springboot 是如何啟動(dòng) tomcat,以下是 springboot 2.x 的代碼。
代碼從 main 方法開始,執(zhí)行 run 方法啟動(dòng)項(xiàng)目。
1
|
springapplication.run |
從 run 方法點(diǎn)進(jìn)去,找到刷新應(yīng)用上下文的方法。
1
2
3
|
this .preparecontext(context, environment, listeners, applicationarguments, printedbanner); this .refreshcontext(context); this .afterrefresh(context, applicationarguments); |
從 refreshcontext 方法點(diǎn)進(jìn)去,找 refresh 方法。并一層層往上找其父類的方法。
1
|
this .refresh(context); |
在 abstractapplicationcontext 類的 refresh 方法中,有一行調(diào)用子容器刷新的邏輯。
1
2
3
4
5
6
7
8
9
|
this .postprocessbeanfactory(beanfactory); this .invokebeanfactorypostprocessors(beanfactory); this .registerbeanpostprocessors(beanfactory); this .initmessagesource(); this .initapplicationeventmulticaster(); this .onrefresh(); this .registerlisteners(); this .finishbeanfactoryinitialization(beanfactory); this .finishrefresh(); |
從 onrefresh 方法點(diǎn)進(jìn)去,找到 servletwebserverapplicationcontext 的實(shí)現(xiàn)方法。在這里終于看到了希望。
1
2
3
4
5
6
7
8
9
|
protected void onrefresh() { super .onrefresh(); try { this .createwebserver(); } catch (throwable var2) { throw new applicationcontextexception( "unable to start web server" , var2); } } |
從 createwebserver 方法點(diǎn)進(jìn)去,找到從工廠類中獲取 webserver的代碼。
1
2
3
4
5
6
7
8
9
10
11
12
|
if (webserver == null && servletcontext == null ) { servletwebserverfactory factory = this .getwebserverfactory(); // 獲取 web server this .webserver = factory.getwebserver( new servletcontextinitializer[]{ this .getselfinitializer()}); } else if (servletcontext != null ) { try { // 啟動(dòng) web server this .getselfinitializer().onstartup(servletcontext); } catch (servletexception var4) { throw new applicationcontextexception( "cannot initialize servlet context" , var4); } } |
從 getwebserver 方法點(diǎn)進(jìn)去,找到 tomcatservletwebserverfactory 的實(shí)現(xiàn)方法,與之對(duì)應(yīng)的還有 jetty 和 undertow。這里配置了基本的連接器、引擎、虛擬站點(diǎn)等配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public webserver getwebserver(servletcontextinitializer... initializers) { tomcat tomcat = new tomcat(); file basedir = this .basedirectory != null ? this .basedirectory : this .createtempdir( "tomcat" ); tomcat.setbasedir(basedir.getabsolutepath()); connector connector = new connector( this .protocol); tomcat.getservice().addconnector(connector); this .customizeconnector(connector); tomcat.setconnector(connector); tomcat.gethost().setautodeploy( false ); this .configureengine(tomcat.getengine()); iterator var5 = this .additionaltomcatconnectors.iterator(); while (var5.hasnext()) { connector additionalconnector = (connector)var5.next(); tomcat.getservice().addconnector(additionalconnector); } this .preparecontext(tomcat.gethost(), initializers); return this .gettomcatwebserver(tomcat); } |
服務(wù)啟動(dòng)后會(huì)打印日志
o.s.b.w.embedded.tomcat.tomcatwebserver : tomcat initialized with port(s): 8900 (http)
o.apache.catalina.core.standardservice : starting service [tomcat]
org.apache.catalina.core.standardengine : starting servlet engine: apache tomcat/8.5.34
o.a.catalina.core.aprlifecyclelistener : the apr based apache tomcat native library which allows optimal ...
o.a.c.c.c.[tomcat].[localhost].[/] : initializing spring embedded webapplicationcontext
o.s.web.context.contextloader : root webapplicationcontext: initialization completed in 16858 ms
end
文章到這里就結(jié)束了,實(shí)在是 hold 不住了,周末寫了一整天還沒有寫到源碼部分,只能放在下一章了。再寫真的就要廢了,有什么不對(duì)的地方請(qǐng)多多指出
以上就是tomcat的工作原理是怎樣的的詳細(xì)內(nèi)容,更多關(guān)于tomcat 工作原理的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://www.cnblogs.com/itdragon/p/13657104.html?utm_source=tuicool&utm_medium=referral