Tomcat 原理解說:啓動過程分析

從開始學Java,使用網頁編程,我們的程序就一直在web容器內運行。容器的概念也是彷佛距離我們相當遙遠。Spring給了我們一扇窗戶,讓我們理解輕量級容器的解決方案。非常慶幸我們所處在一個開源的Java世界,所有的優秀軟件都可以在代碼級進行親密接觸。
Tomcat結構寫的相當之好,極易閱讀。本系列文檔計劃着重以下3個方面:
1. Tomcat 啓動過程分析。分析tomcat的啓動過程
2. Tomcat Web容器功能分析。分析web容器的實現方法
3. Tomcat 其他配置說明。一些我們目前應用較少的Tomcat配置項說明
注意,本文檔分析基於Tomcat5.028完成。
Tomcat的啓動過程簡單說起來,就是讀取配置文件server.xml,然後對其進行實例化的過程。
1. 啓動命令行參數
命令行方式下,直接啓動startup.bat/.sh即可。bat文件目的就在於獲得啓動參數。一般情況下,使用類似如下所示的啓動方法。注意:bootstrap.jar文件中,還指定了3個包在classpath中。
java -classpath "d:\tools\jdk1.5.0\lib\tools.jar;D:\Tomcat-5.0.28\bin\bootstrap.jar" -Dcatalina.base="D:\Tomcat-5.0.28" -Dcatalina.home="D:\Tomcat-5.0.28" -Djava.io.tmpdir="D:\Tomcat-5.0.28\temp" org.apache.catalina.startup.Bootstrap start
Bootstrap是Tomcat的入口。比如啓動、關閉、重啓都是通過這個類實現對tomcat的控制。

2. ClassLoader的使用
Tomcat對不同的模塊可能使用不同的ClassLoader加載。這也就是爲什麼很多類不在啓動的 classpath中,卻可以被它調用的原因。
下面是Bootstrap初始化ClassLoader的方法:
private void initClassLoaders() {
try {
ClassLoaderFactory.setDebug(debug);
commonLoader = createClassLoader("common", null);
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
log("Class loader creation threw exception", t);
System.exit(1);
}
}

下圖是Tomcat用戶手冊上看到的。
Bootstrap
|
System
|
Common
/ \
Catalina Shared
(server) / \
Webapp1 Webapp2 ...
Bootstrap 是JVM提供的
System是在classpath中提供的
Common包含配置文件/org/apache/catalina /startup/catalina.properties中指定的類庫支持
Catalina和Shared都從Common中繼承,包含的類庫也在上面配置文件中指定。
WebappX在部署單個Tomcat5實例時指定。一個webapp下面的類庫對另外一個是不可見的
Tomcat 加載類的順序和普通的不太一樣,如下:
Bootstrap classes of your JVM
System class loader classses (described above)
/WEB-INF/classes of your web application
/WEB-INF/lib/*.jar of your web application
$CATALINA_HOME/common/classes
$CATALINA_HOME/common/endorsed/*.jar
$CATALINA_HOME/common/lib/*.jar
$CATALINA_BASE/shared/classes
$CATALINA_BASE/shared/lib/*.jar
注意,如果希望不使用JVM本身提供的類。這時可以使用jdk的endorsed 特性。

3. Catalina類的作用
如果要啓動Tomcat,那麼一個 org.apache.catalina.startup.Catalina實例就生成,由它完成接下來的工作。
下面是它的啓動代碼
public void start() {
if (server == null) {
load();
}
long t1 = System.currentTimeMillis();
// Start the new server
if (server instanceof Lifecycle) {
try {
((Lifecycle) server).start();
} catch (LifecycleException e) {
log.error("Catalina.start: ", e);
}
}
long t2 = System.currentTimeMillis();
log.info("Server startup in " + (t2 - t1) + " ms");
try {
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
} catch (Throwable t) {
}
if (await) {
await();
stop();
}
}
啓動過程先載入配置文件,然後根據配置文件啓動的Server實例啓動實例,在實例中註冊關閉鉤子。
接下來的工作就是等待發出關閉指令或重啓指令了。
4. Server對象的生成
服務的生成就是根據配置文件server.xml,實例化的對象。對象實例化過程中,會做載入webapp,在特定端口等待客戶連接等工作。
從server.xml到對象的映射是通過commons-digester.jar包完成的。這個包的一個主要功能就是映射 xml到java對象。
catalina類的方法createStartDigester完成了這個工作。部分代碼如下
Digester digester = new CatalinaDigester();
digester.setValidating(false);
digester.setClassLoader(StandardServer.class.getClassLoader());

// Configure the actions we will be using
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer","org.apache.catalina.Server");
digester 會在解析之後,返回一個對象。
5. 服務的中止
StandardServer.await是保持tomcat運行的祕密。方法啓動一個 ServerSocket,偵聽發出停止的字符串。這是一個死循環。當有停止運行的字符發出,跳出此循環。
Socket socket = null;
InputStream stream = null;
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (AccessControlException ace) {
......
while (true) {
......
StringBuffer command = new StringBuffer();
......
while (expected > 0) {
......
ch = stream.read();
......
command.append((char) ch);
......
}
......
boolean match = command.toString().equals(shutdown);
if (match) break;
......
}
......
serverSocket.close();
跳出循環後,系統執行關閉連接等資源的操作,服務就中止了。
我們上面談到,因爲Catalina已經註冊了關閉鉤子,所以從命令行方式關閉進程也是可以釋放資源的。但前提是JVM必須在。如果JVM都立刻殺掉了,釋放的操作就不能進行了。
Catalina.stopServer方法用於發出一個讓服務停止的指令
Socket socket = new Socket("127.0.0.1", server.getPort());
OutputStream stream = socket.getOutputStream();
String shutdown = server.getShutdown();
for (int i = 0; i < shutdown.length(); i++)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
這一篇內容就是這些。下一篇文章將介紹StandardService的工作過程,着重在Web容器的工作原理。

參考資料:
Tomcat用戶手冊
另外一個哥們的Tomcat源碼分析
Tomcat源碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章