TOMCAT源碼分析(啟動框架)
前言:
?? 本文是我閱讀了TOMCAT源碼后的一些心得。 主要是講解TOMCAT的系統框架, 以及啟動流程。若有錯漏之處,敬請批評指教!
建議:
?? 畢竟TOMCAT的框架還是比較復雜的, 單是從文字上理解, 是不那么容易掌握TOMCAT的框架的。 所以得實踐、實踐、再實踐。 建議下載一份TOMCAT的源碼, 調試通過, 然后單步跟蹤其啟動過程。 如果有不明白的地方, 再來查閱本文, 看是否能得到幫助。 我相信這樣效果以及學習速度都會好很多!
??
1. Tomcat的整體框架結構
?? Tomcat的基本框架, 分為4個層次。
?? Top Level Elements:
??? Server
??? Service??
?? Connector
??? HTTP
??? AJP
?? Container
?? Engine
???? Host
?? Context
?? Component?
??? manager
?? logger
?? loader
?? pipeline
?? valve
???????? ...
?? 站在框架的頂層的是Server和Service
?? Server:? 其實就是BackGroud程序, 在Tomcat里面的Server的用處是啟動和監聽服務端事件(諸如重啟、關閉等命令。 在tomcat的標準配置文件:server.xml里面, 我們可以看到“<Server port="8005" shutdown="SHUTDOWN" debug="0">”這里的"SHUTDOWN"就是server在監聽服務端事件的時候所使用的命令字)
?? Service: 在tomcat里面, service是指一類問題的解決方案。? 通常我們會默認使用tomcat提供的:Tomcat-Standalone 模式的service。 在這種方式下的service既給我們提供解析jsp和servlet的服務, 同時也提供給我們解析靜態文本的服務。
??
?? Connector: Tomcat都是在容器里面處理問題的, 而容器又到哪里去取得輸入信息呢?
Connector就是專干這個的。 他會把從socket傳遞過來的數據, 封裝成Request, 傳遞給容器來處理。
?? 通常我們會用到兩種Connector,一種叫http connectoer, 用來傳遞http需求的。 另一種叫AJP, 在我們整合apache與tomcat工作的時候, apache與tomcat之間就是通過這個協議來互動的。 (說到apache與tomcat的整合工作, 通常我們的目的是為了讓apache 獲取靜態資源, 而讓tomcat來解析動態的jsp或者servlet。)
?? Container: 當http connector把需求傳遞給頂級的container: Engin的時候, 我們的視線就應該移動到Container這個層面來了。
?? 在Container這個層, 我們包含了3種容器: Engin, Host, Context.
?? Engin: 收到service傳遞過來的需求, 處理后, 將結果返回給service( service 是通過 connector 這個媒介來和Engin互動的 ).
?? Host: Engin收到service傳遞過來的需求后,不會自己處理, 而是交給合適的Host來處理。
Host在這里就是虛擬主機的意思, 通常我們都只會使用一個主機,既“localhost”本地機來處理。
?? Context: Host接到了從Host傳過來的需求后, 也不會自己處理, 而是交給合適的Context來處理。
?? 比如: <
http://127.0.0.1:8080/foo/index.jsp
>
???????? <
http://127.0.1:8080/bar/index.jsp
>
?? 前者交給foo這個Context來處理, 后者交給bar這個Context來處理。
?? 很明顯吧! context的意思其實就是一個web app的意思。
?? 我們通常都會在server.xml里面做這樣的配置
?? <Context path="/foo" docBase="D:/project/foo/web" />
?? 這個context容器,就是用來干我們該干的事兒的地方的。
??
?? Compenent: 接下來, 我們繼續講講component是干什么用的。
?? 我們得先理解一下容器和組件的關系。
?? 需求被傳遞到了容器里面, 在合適的時候, 會傳遞給下一個容器處理。
?? 而容器里面又盛裝著各種各樣的組件, 我們可以理解為提供各種各樣的增值服務。
?? manager: 當一個容器里面裝了manager組件后,這個容器就支持session管理了, 事實上在tomcat里面的session管理, 就是靠的在context里面裝的manager component.
?? logger: 當一個容器里面裝了logger組件后, 這個容器里所發生的事情, 就被該組件記錄下來啦! 我們通常會在logs/ 這個目錄下看見 catalina_log.time.txt 以及 localhost.time.txt 和localhost_examples_log.time.txt。 這就是因為我們分別為:engin, host以及context(examples)這三個容器安裝了logger組件, 這也是默認安裝, 又叫做標配 :)
?? loader: loader這個組件通常只會給我們的context容器使用, loader是用來啟動context以及管理這個context的classloader用的。
??? pipline: pipeline是這樣一個東西, 當一個容器決定了要把從上級傳遞過來的需求交給子容器的時候, 他就把這個需求放進容器的管道(pipeline)里面去。 而需求傻呼呼得在管道里面流動的時候, 就會被管道里面的各個閥門攔截下來。 比如管道里面放了兩個閥門。 第一個閥門叫做“access_allow_vavle”, 也就是說需求流過來的時候,它會看這個需求是哪個IP過來的, 如果這個IP已經在黑名單里面了, sure, 殺! 第二個閥門叫做“defaul_access_valve”它會做例行的檢查, 如果通過的話,OK, 把需求傳遞給當前容器的子容器。 就是通過這種方式, 需求就在各個容器里面傳遞,流動, 最后抵達目的地的了。
??? valve: 就是上面所說的閥門啦。
?? Tomcat里面大概就是這么些東西, 我們可以簡單地這么理解tomcat的框架,它是一種自上而下, 容器里又包含子容器的這樣一種結構。
2. Tomcat的啟動流程
?? 這篇文章是講tomcat怎么啟動的,既然我們大體上了解了TOMCAT的框架結構了, 那么我們可以望文生意地就猜到tomcat的啟動, 會先啟動父容器,然后逐個啟動里面的子容器。 啟動每一個容器的時候, 都會啟動安插在他身上的組件。 當所有的組件啟動完畢, 所有的容器啟動完畢的時候, tomcat本身也就啟動完畢了。
?? 順理成章地, 我們同樣可以猜到, tomcat的啟動會分成兩大部分, 第一步是裝配工作。 第二步是啟動工作。
?? 裝配工作就是為父容器裝上子容器, 為各個容器安插進組件的工作。 這個地方我們會用到digester模式, 至于digester模式什么, 有什么用, 怎么工作的. 請參考 <
http://software.ccidnet.com/pub/article/c322_a31671_p2.html
>
?? 啟動工作是在裝配工作之后, 一旦裝配成功了, 我們就只需要點燃最上面的一根導線, 整個tomcat就會被激活起來。 這就好比我們要開一輛已經裝配好了的汽車的時候一樣,我們只要把鑰匙插進鑰匙孔,一擰,汽車的引擎就會發動起來,空調就會開起來, 安全裝置就會生效, 如此一來,汽車整個就發動起來了。(這個過程確實和TOMCAT的啟動過程不謀而和, 讓我們不得不懷疑 TOMCAT的設計者是在GE做JAVA開發的)。
2.1 一些有意思的名稱:
?? Catalina
?? Tomcat
?? Bootstrap
?? Engin
?? Host
?? Context
?? 他們的意思很有意思:
?? Catalina: 遠程轟炸機
?? Tomcat: 熊貓轟炸機 -- 轟炸機的一種(這讓我想起了讓國人引以為豪的熊貓手機,是不是英文可以叫做tomcat??? , 又讓我想起了另一則廣告: 波導-手機中的戰斗機、波音-客機中的戰斗機 )
?? Bootstap: 引導
?? Engin: 發動機
?? Host: 主機,領土
?? Context: 內容, 目標, 上下文
??
?? ... 在許多許多年后, 現代人類已經滅絕。 后現代生物發現了這些單詞零落零落在一塊。 一個自以為聰明的家伙把這些東西翻譯出來了:
?? 在地勤人員的引導(bootstrap)下, 一架轟炸架(catalina)騰空躍起, 遠看是熊貓轟炸機(tomcat), 近看還是熊貓轟炸機! 憑借著優秀的發動機技術(engin), 這架熊貓轟炸機飛臨了敵國的領土上空(host), 對準目標(context)投下了毀天滅地的核彈頭,波~ 現代生物就這么隔屁了~
?
?? 綜上所述, 這又不得不讓人聯想到GE是不是也參與了軍事設備的生產呢?
?? 反對美帝國主義! 反對美霸權主義! 和平萬歲! 自由萬歲!
??
2.2? 歷史就是那么驚人的相似! tomcat的啟動就是從org.apache.catalina.startup.Bootstrap這個類悍然啟動的!
?? 在Bootstrap里做了兩件事:
?? 1. 指定了3種類型classloader:
????? commonLoader: common/classes、common/lib、common/endorsed
????? catalinaLoader: server/classes、server/lib、commonLoader
????? sharedLoader:? shared/classes、shared/lib、commonLoader
?? 2. 引導Catalina的啟動。
????? 用Reflection技術調用org.apache.catalina.startup.Catalina的process方法, 并傳遞參數過去。
??
2.3 Catalina.java
?? Catalina完成了幾個重要的任務:
?? 1. 使用Digester技術裝配tomcat各個容器與組件。
????? 1.1?裝配工作的主要內容是安裝各個大件。 比如server下有什么樣的servcie。 Host會容納多少個context。 Context都會使用到哪些組件等等。
????? 1.2?同時呢, 在裝配工作這一步, 還完成了mbeans的配置工作。 在這里,我簡單地但不十分精確地描述一下mbean是什么,干什么用的。
????????? 我們自己生成的對象, 自己管理, 天經地義! 但是如果我們創建了對象了, 想讓別人來管, 怎么辦呢? 我想至少得告訴別人我們都有什么, 以及通過什么方法可以找到? 吧! JMX技術給我們提供了一種手段。 JMX里面主要有3種東西。Mbean, agent, connector.
?????? Mbean: 用來映射我們的對象。也許mbean就是我們創建的對象, 也許不是, 但有了它, 就可以引用到我們的對象了。
?????? Agent:? 通過它, 就可以找到mbean了。
?????? Connector: 連接Agent的方式。 可以是http的, 也可以是rmi的,還可以直接通過socket。
????? 發生在tomcat 裝配過程中的事情:? GlobalResourcesLifecycleListener 類的初始化會被觸發:
???????? protected static Registry registry = MBeanUtils.createRegistry();? 會運行
???????? MBeanUtils.createRegistry()? 會依據/org/apache/catalina/mbeans/mbeans-descriptors.xml這個配置文件創建 mbeans. Ok, 外界就有了條途徑訪問tomcat中的各個組件了。(有點像后門兒)
?? 2. 為top level 的server 做初始化工作。 實際上就是做通常會配置給service的兩條connector.(http, ajp)
?? 3. 從server這個容器開始啟動, 點燃整個tomcat.
?? 4. 為server做一個hook程序, 檢測當server shutdown的時候, 關閉tomcat的各個容器用。
?? 5. 監聽8005端口, 如果發送"SHUTDOWN"(默認培植下字符串)過來, 關閉8005serverSocket。
2.4 啟動各個容器
?? 1. Server
????? 觸發Server容器啟動前(before_start), 啟動中(start), 啟動后(after_start)3個事件, 并運行相應的事件處理器。
????? 啟動Server的子容器:Servcie.
?? 2. Service
????? 啟動Service的子容器:Engin
????? 啟動Connector
?? 3. Engin
????? 到了Engin這個層次,以及以下級別的容器, Tomcat就使用了比較一致的啟動方式了。
????? 首先,? 運行各個容器自己特有一些任務
????? 隨后,? 觸發啟動前事件
????? 立即,? 設置標簽,就表示該容器已經啟動
????? 接著,? 啟動容器中的各個組件: loader, logger, manager等等
????? 再接著,啟動mapping組件。(注1)
????? 緊跟著,啟動子容器。
????? 接下來,啟動該容器的管道(pipline)
????? 然后,? 觸發啟動中事件
????? 最后,? 觸發啟動后事件。
?
????? Engin大致會這么做, Host大致也會這么做, Context大致還是會這么做。 那么很顯然地, 我們需要在這里使用到代碼復用的技術。 tomcat在處理這個問題的時候, 漂亮地使用了抽象類來處理。 ContainerBase. 最后使得這部分完成復雜功能的代碼顯得干凈利落, 干練爽快, 實在是令人覺得嘆為觀止, 細細品來, 直覺如享佳珍, 另人齒頰留香, 留戀往返啊!
?????
????? Engin的觸發啟動前事件里, 會激活綁定在Engin上的唯一一個Listener:EnginConfig。
????? 這個EnginConfig類基本上沒有做什么事情, 就是把EnginConfig的調試級別設置為和Engin相當。 另外就是輸出幾行文本, 表示Engin已經配置完畢, 并沒有做什么實質性的工作。
????? 注1: mapping組件的用處是, 當一個需求將要從父容器傳遞到子容器的時候, 而父容器又有多個子容器的話, 那么應該選擇哪個子容器來處理需求呢? 這個由mapping 組件來定奪。
???
?? 4. Host
?????? 同Engin一樣, 也是調用ContainerBase里面的start()方法, 不過之前做了些自個兒的任務,就是往Host這個容器的通道(pipline)里面, 安裝了一個叫做
?“org.apache.catalina.valves.ErrorReportValve”的閥門。
?????? 這個閥門的用處是這樣的:? 需求在被Engin傳遞給Host后, 會繼續傳遞給Context做具體的處理。 這里需求其實就是作為參數傳遞的Request, Response。 所以在context把需求處理完后, 通常會改動response。 而這個org.apache.catalina.valves.ErrorReportValve的作用就是檢察response是否包含錯誤, 如果有就做相應的處理。
?? 5. Context
?????? 到了這里, 就終于輪到了tomcat啟動中真正的重頭戲,啟動Context了。
?StandardContext.start() 這個啟動Context容器的方法被StandardHost調用.
?5.1 webappResources 該context所指向的具體目錄
?5.2 安裝defaultContex, DefaultContext 就是默認Context。 如果我們在一個Host下面安裝了DefaultContext,而且defaultContext里面又安裝了一個數據庫連接池資源的話。 那么其他所有的在該Host下的Context, 都可以直接使用這個數據庫連接池, 而不用格外做配置了。
? 5.3 指定Loader. 通常用默認的org.apache.catalina.loader.WebappLoader這個類。?? Loader就是用來指定這個context會用到哪些類啊, 哪些jar包啊這些什么的。
?5.4 指定 Manager. 通常使用默認的org.apache.catalina.session. StandardManager 。 Manager是用來管理session的。
???? 其實session的管理也很好實現。 以一種簡單的session管理為例。 當需求傳遞過來的時候, 在Request對象里面有一個sessionId 屬性。 OK, 得到這個sessionId后, 我們就可以把它作為map的key,而value我們可以放置一個HashMap. HashMap里邊兒, 再放我們想放的東西。
?5.5 postWorkDirectory (). Tomcat下面有一個work目錄。 我們把臨時文件都扔在那兒去。 這個步驟就是在那里創建一個目錄。 一般說來會在%CATALINA_HOME%/work/Standalone\localhost\ 這個地方生成一個目錄。
5.6? Binding thread。到了這里, 就應該發生 class Loader 互換了。 之前是看得見tomcat下面所有的class和lib. 接下來需要看得見當前context下的class。 所以要設置contextClassLoader, 同時還要把舊的ClassLoader記錄下來,因為以后還要用的。
5.7? 啟動 Loader. 指定這個Context具體要使用哪些classes, 用到哪些jar文件。 如果reloadable設置成了true, 就會啟動一個線程來監視classes的變化, 如果有變化就重新啟動Context。
5.8? 啟動logger
5.9? 觸發安裝在它身上的一個監聽器。
?lifecycle.fireLifecycleEvent(START_EVENT, null);
?作為監聽器之一,ContextConfig會被啟動. ContextConfig就是用來配置web.xml的。 比如這個Context有多少Servlet, 又有多少Filter, 就是在這里給Context裝上去的。
?5.9.1 defaultConfig. 每個context都得配置 tomcat/conf/web.xml 這個文件。
?5.9.2 applicationConfig 配置自己的 WEB-INF/web.xml 文件
5.9.3 validateSecurityRoles 權限驗證。 通常我們在訪問/admin 或者/manager的時候,需要用戶要么是admin的要么是manager的, 才能訪問。 而且我們還可以限制那些資源可以訪問, 而哪些不能。 都是在這里實現的。
5.9.4 tldScan: 掃描一下, 需要用到哪些標簽(tag lab)
5.10 啟動 manager
5.11 postWelcomeFiles() 我們通常會用到的3個啟動文件的名稱:
index.html、index.htm、index.jsp 就被默認地綁在了這個context上
?5.12 listenerStart 配置listener
?5.13 filterStart 配置 filter
?5.14 啟動帶有<load-on-startup>1</load-on-startup>的Servlet.
? 順序是從小到大: 1,2,3… 最后是0
? 默認情況下, 至少會啟動如下3個的Servlet:
? org.apache.catalina.servlets.DefaultServlet??
????? 處理靜態資源的Servlet. 什么圖片啊, html啊, css啊, js啊都找他
? org.apache.catalina.servlets.InvokerServlet
????? 處理沒有做Servlet Mapping的那些Servlet.
? org.apache.jasper.servlet.JspServlet
????? 處理JSP文件的.
?????? 5.15? 標識context已經啟動完畢。
?走了多少個步驟啊, Context總算是啟動完畢嘍。
??? OK! 走到了這里, 每個容器以及組件都啟動完畢。 Tomcat終于不辭辛勞地為人民服務了!
3. 參考文獻:
??? <
http://jakarta.apache.org/tomcat/
>
??? <
http://www.onjava.com/pub/a/onjava/2003/05/14/java_webserver.html
>
???
4. 后記
??? 這篇文章是講解tomcat啟動框架的,還有篇文章是講解TOMCAT里面的消息處理流程的細節的。 文章內容已經寫好了, 現在正在整理階段。 相信很快就可以做出來, 大家共同研究共同進步。
??? 這篇文章是獨自分析TOMCAT源碼所寫的, 所以一定有地方是帶有個人主觀色彩, 難免會有片面之處。若有不當之處敬請批評指教,這樣不僅可以使剛開始研究TOMCAT的兄弟們少走彎路, 我也可以學到東西。
??? email:
sojan_java@yahoo.com.cn
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
