前幾天為了解決sinpool兄的《多線程的問題?!芬惶?,專門看了一下tomcat 4.1.30的源碼,
其中重點研究了tomcat的啟動這一部分,個人感覺tomcat的源碼還是寫的很清楚易懂,值得一看。
(以前看過struts的部分代碼,感覺也比較經(jīng)典)????
然后我看后的代碼整理了一下,附在下面,希望對其他人有用,也希望感興趣的兄弟可以多看看好的代碼,
肯定對自己的程序設(shè)計和代碼質(zhì)量頗有益處。
一. 啟動類(包含main()方法的類):
org.apache.catalina.startup.Bootstrap
這個類是tomcat的啟動類,主要按照如下步驟,進行主要的啟動工作:
1. 創(chuàng)建3個ClassLoader:common,catalina和share,它們對應(yīng)tomcat的3個Classloader,我想對tomcat
的classloader有研究的兄弟對這個肯定不陌生,其中common classloader是緊跟在系統(tǒng)的classloader(也就是
系統(tǒng)環(huán)境變量中設(shè)置的CLASSPATH所對應(yīng)的classloader),而catalina classloader是common的子classloader,是tomcat
運行所需要的類的classloader,而shared classloader也是common的子classloader,是和catalina平級的classloader,
之所以說是shared classloader,是因為它是所有tomcat下面發(fā)布的webapp classloader(每一個web app都有一個自己的classloader)
的父classloader。它們這3個classloader分別讀取tomcat home下面的common, server和shared三個目錄里面的classes和lib目錄,
用于初始化自己所控制的類庫和資源。
2. 創(chuàng)建啟動一個的org.apache.catalina.startup.Catalina類的實例,并調(diào)用它的process方法,這里使用的是java的reflection技術(shù)。
然后調(diào)用這個實例的process方法,并把Bootstrap接受到的命令行參數(shù)傳遞進去了,這里Bootstrap類并沒有解析傳給它的命令行參數(shù)。
當(dāng)然在調(diào)用process之前還使用setParentClassLoader方法設(shè)置了一下父classloader。這里簡單介紹一下有關(guān)classloader的一個重要
特性,就是如果classloader要load一個類時,不是自己先找,而是先把這個任務(wù)委派給自己的父classloader,然后自己的父classloader
也不找,在把這個任務(wù)委派給自己的父classloader,直到找到最頂層的classloader,然后再自頂向下的找對應(yīng)的這個要load的類的定義,
如果那個classloader先找到,就返回。所以接合上面第一點介紹的tomcat中3個classloader,大家就可以明白tomca的classloader找類
的順序了,這個對程序開發(fā)人員來說特別重要。我想使用過tomcat或者其他app server的兄弟肯定碰到過一個類明明存在可就是找不到,
或者總是找到一個老的版本,我想主要是在多個地方放置的原因,或者哪里有重名的類:-)
二.org.apache.catalina.startup.Catalina類
現(xiàn)在程序轉(zhuǎn)到org.apache.catalina.startup.Catalina類里面的process方法。
這個方法首先設(shè)置一下catalina的home和base目錄,然后通過arguments方法解析命令行參數(shù),
最后調(diào)用execute()方法啟動server。而execute方法很簡單,就是根據(jù)arguments解析的命令行參數(shù),
決定是啟動server,還是stop server,如果是start server,就調(diào)用start方法,而下面重點講一下這個start()方法,
因為才算是一個真正開始的啟動tomcat的地方:-)
1. start方法首先使用Digester(這個東東是jakarta commons里面的一個用于解析xml文件的工具包,一開始是專門用于解析struts配置文件的,
后來被抽象成現(xiàn)在的一個通用工具,主要還是用來解析xml配置文件,根據(jù)一些定義的rule自動生成對應(yīng)的類的實例,具體信息可以參考
apache網(wǎng)站上的文檔)來設(shè)置tomcat配置文件,也就是/conf/server.xml這個文件的解析規(guī)則
然后通過如下代碼來將配置文件中的數(shù)據(jù)轉(zhuǎn)化成內(nèi)存中的實例:
代碼:
??????? File file = configFile();
??????? try {
??????????? InputSource is =
??????????????? new InputSource("file://" + file.getAbsolutePath());
??????????? FileInputStream fis = new FileInputStream(file);
??????????? is.setByteStream(fis);
??????????? digester.push(this);
??????????? digester.parse(is);
??????????? fis.close();
??????? } catch (Exception e) {
??????????? System.out.println("Catalina.start: " + e);
??????????? e.printStackTrace(System.out);
??????????? System.exit(1);
??????? }???
????
?
轉(zhuǎn)換的規(guī)則如下(我只作一些簡單的介紹),例如配置文件中的
a. Server對應(yīng)可以產(chǎn)成一個org.apache.catalina.core.StandardServer類(這個類很重要,是tomcat server的實現(xiàn))
b. Server/GlobalNamingResources對應(yīng)生成org.apache.catalina.deploy.NamingResources類
而大家比較熟悉的監(jiān)聽8080端口的類配置如下:
c. Server/Service/Connector:org.apache.catalina.connector.http.HttpConnector
d. Server/Service/Engine/Host/Context/:org.apache.catalina.core.StandardContext
有興趣的兄弟可以參考jakarta commons里面的Digester文檔和org.apache.catalina.startup.Catalina
這個類里面的createStartDigester方法.
在這段代碼之后,一個叫server的變量已經(jīng)通過Digester工具生成了,它將會用于啟動tomcat。
2. 然后程序進行了一些server啟動前的設(shè)置工作,例如重定向log輸出流等等。而server啟動的代碼如下:
代碼:
??????? // Start the new server
??????? if (server instanceof Lifecycle) {
??????????? try {
??????????????? server.initialize();
??????????????? ((Lifecycle) server).start();
??????????????? try {
??????????????????? // Register shutdown hook
??????????????????? Runtime.getRuntime().addShutdownHook(shutdownHook);
??????????????? } catch (Throwable t) {
??????????????????? // This will fail on JDK 1.2. Ignoring, as Tomcat can run
??????????????????? // fine without the shutdown hook.
??????????????? }
??????????????? // Wait for the server to be told to shut down
??????????????? server.await();
??????????? } catch (LifecycleException e) {
??????????????? System.out.println("Catalina.start: " + e);
??????????????? e.printStackTrace(System.out);
??????????????? if (e.getThrowable() != null) {
??????????????????? System.out.println("----- Root Cause -----");
??????????????????? e.getThrowable().printStackTrace(System.out);
??????????????? }
??????????? }
??????? }
????
?
其中server這個變量就是在剛才Digester解析時創(chuàng)建好的。
(當(dāng)時這個地方我看了很長時間,后來才發(fā)現(xiàn)是這樣的,因為以前不太了解Digester這個東東)。
然后大家可以看到server啟動主要是分3步:
1. initialize方法進行server啟動的初始化操作
2. start方法啟動server,主要是server中的的service和service中的connector
3. await方法等待server shutdown
其中我重點給大家介紹一下initialize方法和start方法
initialize方法:
這里面只有一個主要任務(wù),就是逐次調(diào)用server中所有定義的service的initialize方法,
而每個service的initialize方法中調(diào)用這個service中定義的所有connector的initialize方法,
而connector的initialize方法則是創(chuàng)建一個serversocket用于接受客戶端的請求就結(jié)束了。
如果大家看一下tomcat下面conf/server.xml,就可以發(fā)現(xiàn),tomcat默認(rèn)只定義了一個service叫做Tomcat-Standalone,
而下面只有默認(rèn)定義了3個connector:
1. 8080端口的http connector
2. 8443端口的http ssl connector
3. 8009端口的Coyote/JK2 AJP 1.3 Connector
我想大家對這3個端口一定不陌生吧。
start方法:
這個方法里面有一個tomcat很重要,也是我認(rèn)為tomcat設(shè)計對一個亮點,就是Lifecycle這個東東,它很象一個bus(總線)。
我想大家進行過程序設(shè)計的一定知道,開始設(shè)計的時候總要根據(jù)一個原則分出幾個模塊來,是為了代碼分塊,或者將
一部分功能相似的代碼組織成一個模塊,這樣比較清楚,例如一個進銷存系統(tǒng)會有采購,銷售,庫存和財務(wù)等模塊,但是
我想很多人也碰到過這樣的情況就是雖然分了模塊但是如果在開發(fā)完畢以后,另外一個客戶說只想要其中的銷售模塊,我想
大部分的開發(fā)人員肯定傻眼,因為雖然當(dāng)時設(shè)計的時候分了模塊,但是這些模塊編寫的時候卻是交織在一起,互相的接口定義
很模糊,基本上都是直接調(diào)用另一個模塊的方法,這樣肯定分不開。而tomcat的這個Lifecycle的設(shè)計理念就可以解決這個問題的
一部分,它的原理就象是一個bus(總線),例如一個模塊做完一個動作以后,例如銷售模塊創(chuàng)建好一個訂單后,本來要直接調(diào)用
庫存模塊的api鎖住一部分庫存(我只是隨便舉個例子,實際業(yè)務(wù)不一定是這樣),這樣銷售模塊就需要依賴庫存模塊了。但是使用了
bus方式。我們就可以在訂單創(chuàng)建后,向bus上發(fā)送一個訂單創(chuàng)建的消息,而總線有一個事件注冊機制,有點象swing的event,listener,
例如庫存模塊有一個listener專門用于監(jiān)聽訂單創(chuàng)建的消息,進行處理,這樣2個模塊就互不依賴了。有興趣的兄喜可以看看jcp上面
的一個叫做infobus的專題。
當(dāng)然這個方式只是解決有效降低模塊偶合度的一個方面(因為有的時候必須要直接調(diào)用另外一個模塊的接口,
例如庫存模塊一定要直接缺德一個銷售訂單的信息,那么就需要定義一個接口類來描述訂單的詳細(xì)信息啦,這里就不具體解釋了,
有空可以專門發(fā)個帖子跟大家探討這個問題:-) ),就是不要顯式觸發(fā)另一個模塊的某個動作,而是通過bus機制來發(fā)送消息,
而每個模塊都有一個自己的handler,會監(jiān)聽bus,對自感興趣的事件進行處理。tomcat的Lifecycle就是這個東西。
下面再回到start方法:
1. 它首先向總線發(fā)送了2個事件:BEFORE_START_EVENT和START_EVENT
2. 然后調(diào)用每個service的start方法,最后發(fā)送AFTER_START_EVENT消息通知其他程序
而service的start方法主要進行的動作如下:
1. 發(fā)送BEFORE_START_EVENT消息
2. 調(diào)用container的start方法
3. 然后調(diào)用connector的start方法
4. 最后發(fā)送AFTER_START_EVENT消息.
而connector的start方法就是大家最熟悉的socket編程了,大家可以參看org.apache.catalina.connector.http.HttpConnector這個類,
主要是使用java里面的多線程操作,初始化一個HttpProcessor的線程池,然后通過wait方法阻塞住每個HttpProcessor線程,只有
當(dāng)接受到一個http請求時,在通過notify方法激活HttpProcessor線程,讓其處理用戶的http請求。
到此為止主要簡單介紹了一下tomcat 4.1.30的啟動過程,下次有機會的話,可以再看看它的webapp的deploy的管理部分的代碼,然后和大家分享。如果大家對我寫的帖子有什么意見的話,也歡迎批評指正,希望感興趣的兄弟可以一起探討:-) 廢話不說了,很晚了該睡覺了,祝大家周一工作愉快