自己最近在學習Java Web編程,先是讀了Sun公司的官方文檔Java EE 5的tutorial(http://java.sun.com/javaee/5/docs/tutorial/doc/)中的關鍵章節,然后學了一陣Java Passion里的相關在線課程(見:http://www.javapassion.com/j2ee/),前幾天還看完了一本電子書<<Struts2 in Action>>,但總覺得缺少了什么。這兩天突然想起為什么不把tomcat的源碼下載下來仔細研究研究呢?記得以前自己一直想學習著名的Java Build工具Ant(http://ant.apache.org/)的用法,但怎么看文檔都理解不深,試了幾次效果都不好。最后終于把源碼下載下來讀了其中的主要源碼,頓時感覺不僅輕松學會了Ant的用法,而且知道為什么要那么設計。這給我很大的啟發:對于開源軟件來說,比書籍更好的是在線文檔,比在線文檔更好的源碼本身。其實tomcat和Ant有很深的內在聯系,tomcat的早期開發者James Duncan Davidson就是Ant工具的發明人,為了解決開發tomcat過程中各種復雜的build流程他專門創造了Ant。顯然,了解Ant對讀懂tomcat會有極大的幫助。tomcat現在的架構師Craig McClanahan又是另一個著名的MVC框架struts的發明人。
步入正題吧。
首先到tomcat.apache.org里下載最新的源碼包,現在最新的版本是6.0.20,下載后解壓縮,發現它的根目錄里有一個build.xml,這是Ant的默認輸入文件。tomcat源碼本身是用Eclipse寫的,所以我們要做的第一步是在Eclipse里把tomcat工程部署好,這其實很容易,在Eclipse里選擇File>New>Project,選擇Java project with an existing Ant buildfile,選擇那個build.xml就可以了。
然后是下載tomcat的依賴包,這一步也很容易,只要執行ant download(可以打開Eclipse的Ant的view以方便操作)就可以了。它會把下載的依賴包放在一個名稱為base.path的目錄,最好自己配置一下,它默認配置在build.xml.properties.default文件里。下載完依賴包后再ant deploy后就在output/build目錄里得到了可以使用的tomcat。Ant真方便!
Eclipse里要想使得即時編譯沒有任何錯誤提示,需要在build path里配置兩個變量:一個是ANT_HOME,另一個是保存依賴包的TOMCAT_LIBS_BASE,就是保存依賴包的那個目錄。
看一下tomcat的目錄結構,真的很簡單,啟動tomcat的腳本放在bin目錄里,分別有對應windows和unix的兩種腳本。我在Ubuntu環境下,所以先來看一下它的unix腳本。啟動腳本是startup.sh,它實際上是另一個腳本catalina.sh的封裝,所以直接跳到catalina.sh,它會調用另一個腳本setclasspath.sh以設置java,catalina.sh設置好各種環境后會調用tomcat的啟動類:org.apache.catalina.startup.Bootstrap的start方法。
跟蹤一下代碼,Bootstrap類里主要是設置幾個不同的ClassLoader和JMX Server,然后通過反射技術把任務移交給真正的啟動類:org.apache.catalina.startup.Catalina。
怎么樣,這是不是很像啟動腳本一樣,先有個包裝,最后都調的是catalina,不管是shell還是Java的類。
Catalina里的方法最主要是這個方法:protected Digester createStartDigester();它是讀取conf/server.xml配置文件進行動態加載配置組件的過程,用的是apache的另一個子項目:Digester,它也是struts讀取配置文件動態加載的機制,別忘了tomcat的架構師可是struts項目的作者啊。
加載完server的層次化組件后,通過調用 ((Lifecycle) server).start();整個server就自頂向下啟動起來了。另外,代碼中大量運用了observer這個設計模式,把各種狀態的變化通知給相應的listener。
在Bootstrap類的源碼中發現一處小的bug:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + replace);
應該是:
if (replace && log.isDebugEnabled())
log.debug("Expanded " + before + " to " + repository);