classLoader
commonLoader:加載的jar可以提供給tomcat本身和其下面各個項目使用
catalinaLoader:加載的jar只能提供給tomcat本身
sharedLoader:加載的jar包可以給各個項目使用
他們三個都是urlclassloader類型
在bootStrap啟動期間 將線程上下文的classloader設置為catalinaLoader鸣皂,
并使用catalinaLoader加載我們的catalina這個類寞缝,這就意味著這個類不會被我們項目中使用
然后后期都是調用catalina方法的load和start方法來啟動tomcat,此外我們還將catalina的parentClassLoader設置為sharedLoader
這是為了后期設置weappclassloader的父類埋下伏筆
相關名詞解釋
catalina.home:tomcat產品的安裝目錄被啼,bin 和 lib 目錄被多個tomcat示例公用
catalina.base:是tomcat啟動過程中需要讀取的各種配置及日志的根目錄泡挺,其它目錄conf娄猫、logs媳溺、temp、webapps和work 每個Tomcat實例必須擁有其自己獨立的備份碍讯。
實現在一臺機器上運行多個tomcat實例的目的悬蔽。
主要就是利用catalina.base,因為它是Tomcat啟動過程中讀取各自配置的根目錄
java.io.tmpdir:獲取操作系統緩存的臨時目錄捉兴,不同操作系統的緩存臨時目錄不一樣蝎困。
user.dir:則是獲取當前tomcat容器啟動的位置
SystemLogHandler:
startCapture方法:首先從resume(stack)中獲取一個captureLog對象,如果該stack為空則new一個captureLog對象然后從logs(threadLocal)獲取stack<caputure>對象轴术,如果為空則創(chuàng)建一個
stopCapture方法:從threadlocal獲取stack<caputure>难衰,然后彈出一個CaptureLog 然后寫出期間的打印信息
重置CaptureLog存入reuse钦无。
這個SystemLogHanlder彼宠,可以收集一段代碼內的打印信息摧冀,然后統一輸出且不影響性能。
appBase和docBase的區(qū)別
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" docBase="D:\WebContent" sessionCookiePath="/" sessionCookieName="JSESSIONID" />
appBase:
1.appBase是host的屬性潮罪,其會自動部署appbase指定的目錄的項目
docBase
1.docBase是context的屬性月洛,指定這個項目的路徑(絕對或相對路徑)
2.訪問路徑我們可以通過path指定
外置的Tomcat的啟動過程:
一隔崎、bootStrap的靜態(tài)代碼塊和初始化:
1.靜態(tài)代碼塊:主要就是設置catalina.home和catalina.base,其中獲取了user.dir 作為上述2個參數未設置時候的備選方案
2.init方法:初始化三個主要的classloader撵彻,設置線程上下文的classloader為catalinaLoader生成catalina對象,并將sharedLoader設置為其parentClassLoader
二、調用catalina的load方法
1.初始化工作時候的temp目錄-java.io.tmpdir
2.初始化jndi相關的目錄
3.創(chuàng)建Digester對象,并使用該對象解析server.xml文件,最終創(chuàng)建Server對象
4.設置server的屬性督弓,catalina蒜危,catalinaHome和catalinaBase
5.優(yōu)化system.out和system.error
6.調用server的init方法
三、調用catalina的start方法
1.server的start方法調用
2.如果需要使用鉤子則注冊一個CatalinaShutdownHook纵刘,該鉤子會在觸發(fā)的時候調用catalina的stop方法
惧蛹,該方法會先刪除鉤子(因為有可能不是鉤子線程調用的stop方法,這樣會導致stop調用兩次)然后調用server的stop和destory方法双抽。
3.然后調用await和stop方法
4.await方法主要就是根據通過循環(huán)判斷標識位來判斷是否需要結束tomcat或者通過socket監(jiān)聽關閉端口來判斷
內置的Tomcat啟動過程
一睬棚、創(chuàng)建WebServer(內部包含tomcat類篱瞎,所以這個webServer類似于Facade)
1.創(chuàng)建tomcat類。該類主要是服務于內置或者測試的starter竖慧,包含我們基礎配置信息,也是用來啟動我們的server服務,并且可以添加container的類角溃。
2.設置tomcat的baseDir
3.設置connector左冬,默認協議是Http11NioProtocol類
4.創(chuàng)建一個standardserver,設置catalina.home和catalina.base
5.創(chuàng)建一個standardService,將connector和service互相綁定板鬓。
6.調整connector屬性悟耘,比如端口鹰服,還有就是如果我們配置了地址屠尊,則協議需要綁定該地址,還有就是給connector
設置uriEncoding,ssl摇予。還有是給協議設置compression。
7.我們也可以通過實現TomcatConnectorCustomizer接口來優(yōu)化connector或者其內部協議隆圆,比如修改adpter或者endpoint
8.當然還有這個TomcatContextCustomizer接口 我們可以優(yōu)化我們的context
9.上述的兩個customizer秃踩,我們可以寫個BeanPostProcessor台谢,在TomcatServletWebServerFactory初始化之后筋夏,調用其add方法添加
10.將被自定義修改后的connector和service中的connector進行對比丢烘,如果對象被替換了 就將新的connector加入到service中否則就不用添加
11.禁止host自動部署犀暑,在設置engine的是設置了該engine的默認的host為localhost和realm
12.配置engine,首先設置backGroupProcessorDelay時長洪唐,這個會啟動一個線程每隔一段時間執(zhí)行后臺任務
13.給engine設置valve
14.我們還可以給該service添加我們額外寫的connector
15.設置我們context=TomcatEmbeddedContext枯怖,設置name和path,displayName,指定docBase
16.添加lifeCycleListeners和parentClassloader捌治,設置webApploader(我們的webappclassLoader的創(chuàng)建者)县袱,以及webappclassLoader的類型TomcatEmbeddedWebappClassLoader
17.將webApploader的加載模式設置為true,這就代表我們的這邊webappclassLoader采用的是雙親加載模式
18.注冊DefaultServlet鲸沮,將tomcatStarter和context綁定,配置context的valve憎亚,修改我們的context
19.最終將tomcat和port包裝成tomcatWebServer返回驴剔,在tomcatWebServer的構造函數中好友初始化方法
20.該初始化方法主要是:啟動tomcat的container,但是會刪除connector這樣就可以避免connector也被啟動,啟動一個后臺線程每隔一段時間去檢測是否關閉標識奄抽。
二抑胎、啟動webServer
1.添加我們上述被刪除的connector
2.啟動我們的connector
tomcat啟動過程就是 調用Server的init方法和start方法,所以我們著重分析這兩個方法
一诵闭、server的init:
1.standardserver的init方法
- 設置JMX相關類
- 設置全局的字符串緩存
- 初始化globalNamingResources
- 從我們的sharedLoader中獲取jar包資源時報包含MANIFEST晤郑,如果包含則添加到容器的集合中
- 初始化service的init方法
2.service的init方法
- engine的初始化
- 線程池的初始化
- mapperlistener的初始化
- connector的初始化
3.engine的初始化(少了host,context晰绎,wrapper的初始化陨簇,他們的初始化都是在各自start的時候先進行進行的)
- 創(chuàng)建Realm
- 重新配置startStopExecutor蔓涧,其主要是幫忙啟動和關閉子容器
4.host的初始化
- 配置startStopExecutor
5.context的初始化
- 配置startStopExecutor
- namingResources初始化
6.wrapper的初始化
- 配置startStopExecutor
7.connector的初始化
- 創(chuàng)建adapter并和protocolhandler綁定件已,其主要是用來獲取httpServeletRequest和httpServeletResponse
- 設置默認的解析bodymethod==post
- protocolHandler的初始化
8.protocolHandler的初始化
- 判斷是否需要升級協議
- 創(chuàng)建endpoint并初始化
- enpoint可以理解為專門負責處理socket,類似于netty的服務端或者客戶端boostrap
- Nioendpoint的初始化-實際是調用bind方法
- 創(chuàng)建socket并給socket設置相關屬性元暴,比如接受和發(fā)送的緩沖區(qū)大小篷扩,超時,拒絕連接的地址等
- 讓該socket綁定我們設置好的地址和端口昨寞,同時還設置了backlog=accpetAccount瞻惋,也就是說最大可以有一定數目的
即將要連接的該socket,超過就拒絕援岩。這個backlog對應的accept queue - 設置該通道為非阻塞模式
- 設置acceptorThreadCount(也就是acceptor線程默其專門獲取socket連接)認為1歼狼,其對應我們的acceptor線程大小類似于nioeventloop,根據我們對netty的了解就算設置多了意義也不大
- 設置pollerThreadCount最小為1享怀,其是專門接受acceptor線程傳遞過來的socket羽峰,然后將該scoekt注冊到selector上面
- 初始化ssl
- 創(chuàng)建共享selector,然后創(chuàng)建一個NioBlockingSelector去包裹該selector
- NioBlockingSelector包含一個共享的多路復用器,BlockPoller線程
- BlockPoller線程的作用:專門處理ssl的channel上面注冊的blocking的socket,其他的非ssl裁員poller線程去注冊
二梅屉、server的start:
1.server的start方法
- 激活configure_start監(jiān)聽事件
- 設置狀態(tài)為start
- 調用globalNamingResources的start方法
- 調用service的start方法
2.service的start方法
- 設置start啟動標識
- 啟動engine
- 啟動線程池
- 啟動mapperListener
- 啟動connector
- engine的start
啟動realm
- 迭代啟動子容器
- 啟動pipeline,設置啟動標識值纱,也就是給pipeline添加基本的vavle,并啟動vavle(也就是設置啟動標識)
- 每個容器都有一個starnardpipeline,每個容器都有一個baiscvavlve--standardXXvalve坯汤,他們的主要任務是
連接當前容器的子容器 - 啟動ContainerBackgroundProcessor線程執(zhí)行后臺任務(目前只有engine容器可以啟動該線程虐唠,其他子類沒有)
- ContainerBackgroundProcessor的處理邏輯:對于container為context的容器 先調用其bind方法
- 調用所有container的backgroundProcess方法,而各個container的backgroundProcess內部還包含其他組建的backgroundProcess
比如cluster,realm,valve,Loader,Manager,WebResourceRoot,InstanceManager等 - 而context容器的bind方法的作用就是就是確保我們的webApploader中的webAppClassLoader與線程上下文的classloader一致
不一致的話就設置為一樣的惰聂。這樣的話我們熱部署的時候classloader的變更 進而可以進行體會線程上下文中的classloader
- host的start方法
- 配置errorValve
- start子容器
- start我們的pipeline
- context的start方法
- 給該項目設置工作目錄
- 設置WebResourceRoot并調用其start方法 主要是將這個context中的文件比如class和配置文件變成WebResource
留給classloader去加載 - 創(chuàng)建WebappLoader,并設置其內部的WebappClassLoader的加載模式為非雙親加載模式
- 檢測context中的mainest文件疆偿,設置NamingContextListener
- 啟動WebappLoader的start方法主要就是創(chuàng)建WebAppClassLoader,設置加載模式 classPaths搓幌,將classes和lib下面的
資源放入到localRepositories中 - 設置WebAppClassLoader的屬性杆故,然后將該classloader放入到線程上下文后期只要碰到任何需要加載的類都采用我們的classloader
- 啟動子容器
- 啟動pipeline
- 將sources,jarScanner放入到servletContext
- 執(zhí)行ServletContainerInitializer的onStarup方法
- 啟動filter和servlet
- 將webAppClassLoader替換為原始的classloader
-通過監(jiān)聽者ContextConfig的webConfig和applicationAnnotationsConfig方法加載項目 首先生成對應的web.xml文件(先從項目中尋找溉愁,沒有在獲取全局的) 然后獲取利用spi機制去各個jar包中尋找META-INF/services/javax.servlet.ServletContainerInitializer 文件獲取ServletContainerInitializer处铛,這邊使用webappclassloader去加載。 然后加載所有的/WEB-INF/classes的class拐揭,主要是處理這些class中的annotation/WebServlet 撤蟆,annotation/WebFilter,annotation/WebListener投队。 然后在處理所有的lib目錄的jar包枫疆,最后添加我們ServletContainerInitializer
- 回收resouce中的資源
- wrapper的start方法
- 啟動器內部的cluster ,realm敷鸦,pipeline和子容器的start方法
- 設置available=0代表可用
7.connector的start方法
- 檢測端口是否符合要求
- 啟動protocolHandler的start方法
8.protocolHandler的start方法
- 啟動endpoint的start方法
- 啟動一個異步線程AsyncTimeout,每隔1秒去檢測我們的異步處理的Processor是否超時寝贡,如果超時則執(zhí)行超時操作
- 該線程的優(yōu)先級被設置為normal扒披,且是后臺線程
- Nioendpoint的start方法
- 設置nioendpoint的啟動標識
- 設置processorCache,EventCache,nioChannels三個緩存
- 如果我們的tomcat沒有設置線程池 則我們自己創(chuàng)建worker線程池
- 線程池的屬性有minspareThreads圃泡,maxThreads
- 設置maxConnection的大小默認是1萬
- 創(chuàng)建poller線程碟案,數量可以設置
- 每個poller包含獨立的多路復用器,他們從acceptor線程獲取socket注冊到自己的多路復用器颇蜡,其類似于nioeventloop
- 開啟acceptor線程
三价说、tomcat接受處理請求的全流程
-一、Acceptor線程攜帶endpoint风秤,不斷的循環(huán)獲取socketChannel
- 1.首先判斷endPoint是否還在running鳖目,如果不是則跳出循環(huán)
- 2.如果當前endpoint是暫停且還在running狀態(tài)則現場沉睡50毫秒
- 3.在正式獲取socketChannel的時候需要先去獲取connection(通過maxConnection限制)
- 4.socketChannel是一個channel,獲取的過程中如果發(fā)生io異常需要設置下errorDelay缤弦,即讓主線程沉睡一會
- 5.當我們獲取到socketchannel則需要綁定到poller線程上领迈,綁定失敗或者acceptor線程暫停或者關閉 則需要關閉該通道
-二、將socketChannel包裝成Niochannel交給poller線程
- 1.將socketChannel設置為非阻塞
- 2.將socket的屬性填充=超時狸捅,接受和發(fā)送buffer(屬于socket的buffer)衷蜓,ReuseAddress ,tcpNoDelay等
- 3.嘗試從緩存獲取niochannel尘喝,并創(chuàng)建了一個SocketBufferHandler磁浇,其實作為applicaiton的buffer(包含發(fā)送,接受和堆外的buffer)
- 4.最終我們將SocketBufferHandler和socketchannel包裝成niochannel注冊到poller線程上
- 5.注冊過程就是講我們的niochannel和nioendpoint包裝成NioSocketWrapper
- 6.然后給NioSocketWrapper設置poller線程朽褪,timeout扯夭,alive,ssl等屬性,并注冊read事件
- 從eventCache緩存中獲取pollerEvent鞍匾,并集成niochannel交洗,NioSocketWrapper和注冊事件。
- 8.然后將該pollerEvent放入poller線程的events內部集合橡淑,如果wakeupCounter=-1 則代表當前poller線程在沉睡我們需要喚醒
-三构拳、Poller線程的run方法-將后續(xù)獲取到的事件(鏈接事件)交給SocketProcessorBase處理
- 1.從我們的events集合中循環(huán)的執(zhí)行PollerEvent的run和reset方法 然后將PollerEvent放入eventCache
- 2.run方法:第一次執(zhí)行都是將我們的channel注冊到該poller內部的多路復用器
- 3.reset方法:情況pollerEvent內部對象
- 4.設置wakeupCounter為-1 如果之前的值是大于0代表已經有其他的event加入進來 我們直接selectNow否則阻塞獲取事件
- 5.設置wakeupCounter為0 然后判斷是否該線程已經關閉如果是的話則cancel 所有已經發(fā)生的SelectionKey然后關閉多路復用器再跳出循環(huán)
- 6.否則判斷是否有對應的SelectionKey或者其他的event加入
- 7.先處理SelectionKey,然后在查看是否超時
- 8.只有處理以下幾種情況的timeout:nextExpiration已經過了梁棠,沒有selectionKey或者其他event加入 或者socket已經開始clsoe
- 9.處理selectionkey的邏輯是:如果已經close了 則直接cancel置森,否則檢測sk是否有效且需要攜帶附件,只處理讀寫事件
- 10.如果附件攜帶文件符糊,則調用filechannel的transTo方法進行處理凫海,其他事件則當成socket進行處理。
- 11.在處理sk中攜帶文件之前需要先取消之前該channel注冊的事件男娄,等待處理完成在且是支持keepalive的則重新注冊該channel否則直接關閉
- 12.對于attacheMent是非文件的則先取消之前該channel注冊的事件行贪,并不會重新注冊 因為事件已經處理完畢
- 13.上述attacheMent是非文件是先處理read在處理write,如果處理失敗就直接cancel
- 14.然后從processorCache獲取SocketProcessorBase模闲,根據是否支持dispatch以及是否存在線程池來決定剩余的處理
是否在poller線程中做還是交給cotainer的線程池
-四建瘫、SocketProcessorBase的處理流程
- 1.首先進行ssl握手
- 2.獲取對應的handler進行處理
- 3.對應的handler獲取合適的Processor
- 4.調用Processor的process進行處理,最終交給service方法
- 五尸折、Processor的service方法邏輯
- 1.獲取requestIno啰脚,設置請求的stage,設置input和output的buffer
- 2.默認是支持keepAlive实夹,讀取請求的數據
- 3.最終通過adapter的service方法傳遞我們的原始request和response
- 六橄浓、然后將在第五步得到request和response交給Adapter(CoyoteAdapter),調用其service方法處理
- 1.首先將request和response轉換成HttpServletRequest和HttpServletResponse
- 2.獲取connector 通過connector得到container的vavle亮航,然后交給他們執(zhí)行
- 七荸实、最終一層層調用到我們的standardWrapperValve的invoke方法
- 1.獲取對應的servlet,這邊就是我們的dispatchServlet
- 2.執(zhí)行dispatchServlet的service方法塞赂,在此之前先執(zhí)行過濾器
- 3.我們的controller方法被映射成地址和我們的請求地址匹配泪勒,最終可以得到method 進而反射執(zhí)行方法完成調用
server.xml文件的各個標簽的意義
我們的engine標簽會配置一個默認的host
名稱一般都是localhost昼蛀,當我們用localhost去請求的時候
我們本地的系統有hosts配置文件可以將我們的localhost解析為實際的ip
默認都是127.0.0.1,當然我們也可以配置localhost1等127.0.0.1
MANIFEST
一般編寫MANIFEST.MF文件只需要用到Manifest-Version(MF文件版本號)
圆存、Main-Class(包含main方法的類)叼旋、Class-Path(執(zhí)行這個jar包時的ClassPath,第三方依賴)
比如下面:
Manifest-Version: 1.0
Main-Class: test.Main
Class-Path: ./ ./lib/commons-collections-3.2.jar ./lib/commons-dbcp-1.2.2.jar ./lib/commons-lang-2.3.jar ./lib/commons-logging-1.1.jar
Tomcat中的backlog參數
在linux 2.2以前沦辙,backlog大小包括了半連接狀態(tài)和全連接狀態(tài)兩種隊列大小夫植。
linux 2.2以后,分離為兩個backlog來分別限制半連接SYN_RCVD狀態(tài)的未完成連接隊列大小
跟全連接ESTABLISHED狀態(tài)的已完成連接隊列大小油讯。
當服務端接受到客戶端的syn請求后放入syns的隊列中详民,然后服務端回復syn+ack,等客戶端收到ack后 再回復ack給服務端
則服務的就把sync中的半連接放入到accpet queue陌兑。一般backlog=完全隊列
我們完全隊列大小取值=min(backlog,somaxconn)
我們半完全隊列大小=
table_entries = min(min(somaxconn沈跨,backlog),tcp_max_syn_backlog)
roundup_pow_of_two(table_entries + 1)
roundup_pow_of_two表示取最近的2的n次方的值,舉例來說:假設somaxconn為128兔综,backlog值為50饿凛,tcp_max_syn_backlog值為4096,則第一步計算出來的為50软驰,
然后roundup_pow_of_two(50 + 1),找到比51大的2的n次方的數為64涧窒,所以最終半連接隊列的長度是64。
connector容易混淆的名詞解釋
acceptorThreadCount:代表從socket的完全隊列獲取socket連接的線程數锭亏,其socket連接交給poller
pollerThreadCount:將該socket注冊到selector上面纠吴,然后捕捉到后續(xù)的讀寫事件交給worker線程處理
maxThreads:則是指worker線程--真正去處理這些socket請求的線程個數
acceptCount: 代表acceptor線程從tcp完全隊列里面取connection的限制(因為acceptorCount限制了隊列大小)
maxConnections:每當獲取到一個socket就代表得到一個connection慧瘤,然后該connection只有在完成或者某些異常情況下才釋放
戴已,從這個角度來說如果不限制connection我們不停的拿socket處理也是不行的,這就是從java層面控制
Tomcat的reload機制
- 1.WebappLoader的backgroundProcess方法
- 2.首先檢測是否支持熱部署碑隆,其次檢測項目是否有變化
- 3.變化的依據是檢測jar包和我們的其他文件的變更時間恭陡,如果不一致則將當前的線程上下文中的classloader替換為加載
webApploader的classloader 也就是我們的sharedLoader - 4.然后調用standardContext的reload方法去加載
- 5.加載完成后將線程上下文的加載器設置為webAppclassloader
reload方法的主要邏輯
- 設置暫停標識,可以讓request請求休息1秒
- 獲取老的classloader 關閉后臺線程(由于context沒有后臺線程所以不用關閉)
- 獲取context的子容器進行stop
- 關閉過濾器和manager 等等
- 然后重新調用start方法 上煤,設置paused為false