文章目錄
- 一、官網下載Tomcat 的原始碼匯入IDEA
- 二、Tomcat啟動重要檔案
- startup原始碼分析
- catalina.bat原始碼分析
- 三、Tomcat啟動流程分析
- 1、部署專案的方式
- 2、Tomcat中Container管理四大Servlet容器
- 3、分析啟動時序圖
- 四、Request請求過來Tomcat在干嘛
本來這篇文章只是我前輩引路我自學tomcat的一篇水文,看到大家反應很好,我覺得我有義務那它完善一下,謝謝你們!
一、官網下載Tomcat 的原始碼匯入IDEA
1、地址:http://tomcat.apache.org/ 左側 Download Tomcat 9,在網頁最下面下載Tomcat原始碼;

下載完了直接解壓得到apache-tomcat-9.0.39檔案夾,
2、打開IDEA匯入解壓后的檔案夾 , 選擇File->Open->選擇tomcat的原始碼目錄(我下載的是apache-tomcat-9.0.39);
注意:打開的目錄是剛剛解壓的檔案夾,而不是打開解壓檔案夾的中的那個子目錄(我是這么操作的),
二、Tomcat啟動重要檔案
startup原始碼分析
我覺得要研究一個技術的原始碼要從它是怎么啟動運行的開始,特別是很復雜的原始碼,所有我就從Tomcat的啟動開始,

在Tomcat的bin目錄下有兩個啟動Tomcat的檔案,一個是startup.bat,它用于windows環境下啟動Tomcat;另一個是startup.sh,它用于Linux環境下Tomcat的啟動,大概看了下這兩個檔案中的實作思路差不多一樣的,我就看了startup.bat(windows啟動檔案)
以下是startup.bat檔案,我加了一些注解:


通過以上閱讀可以得到一個結論: startup.bat檔案實際上就做了一件事情 -> 啟動catalina.bat
這樣我也明白了,為什么在此之前我在windows下配置了catalina.bat就可以使用catalina run 啟動Tomcat了,所以未必一定要通過startup.bat來啟動Tomcat,用catalina.bat start也是可以的,
catalina.bat原始碼分析
既然在 startup.bat有關聯到 catalina.bat ,那么就肯定要看看這個檔案是干嘛的了,
由于注解比代碼多,我就梳理一下大概的執行邏輯
首先會直接跳到mainEntry代碼段 -> 在確定CATALINA_HOME下有catalina.bat后再把CATALINA_HOME賦給變數CATALINA_BASE -> 之后再去獲得CLASSPATH(就是我們配置的環境變數)-> 系統拿到classpath路徑后把它和CATALINA_HOME拼接在一起,最終定位到一個叫bootstrap.jar的檔案;

然后到 doStart代碼塊(當然還有doDebug和doRun)并設定引數,最終跳轉到execCmd代碼段;
通過以上閱讀可以得到一個結論: catalina.bat最終執行了Bootstrap類中的main方法,
讀完catalina.bat會發現,我們可以通過設定不同的引數讓Tomcat以不同的方式運行,比如說:在IDEA中可以選擇debug等模式啟動Tomcat的,也可以為其配置引數,在catalina.bat中我們可以看到了啟動Tomcat背后的運作流程,
//public final class Bootstrap Bootstrap類的main方法
public static void main(String args[]) {
synchronized (daemonLock) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init(); //注意這個init()方法
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to
// prevent a range of class not found exceptions.
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}
剛剛說了,既然啟動是靠Bootstrap的main()方法,那么不妨這么設定一下來運行專案:
運行tomcat原始碼我知道有兩種方式,一種是使用 Ant 工具進行編譯原始碼,一種是使用 Maven 工具,我使用的是 Maven,使用 Ant 得去下載 Ant 工具進行編譯原始碼,一種是使用 Maven 工具,我使用的是 Maven,使用 Ant 得有IDEA Ant 的插件,我就說一下常用 Maven的操作:
首先:新建 catalina-home檔案夾和像下圖一樣新建第一個pom.xml檔案,并在apache-tomcat-9.0.39-src檔案里新建第二個pom.xml(這兩個pom檔案可以在IDEA新建,也可以去其他專案中引入)

將apache-tomcat-9.0.39-src中的 conf檔案復制到新建的catalina-home目錄之下,再將以下內容復制到apache-tomcat-9.0.39-src中的pom.xml中,為什么需要這些依賴?就是運行tomcat需要這些依賴作為支撐,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.tomcat</groupId>
<artifactId>Tomcat9.0</artifactId>
<name>Tomcat9</name>
<version>9.0.39</version>
<build>
<finalName>Tomcat9</finalName>
<sourceDirectory>java</sourceDirectory>
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.9</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-apache-log4j</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant-commons-logging</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>javax.xml.rpc</groupId>
<artifactId>javax.xml.rpc-api</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt.core.compiler</groupId>
<artifactId>ecj</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
再將以下內容復制到外層pom.xml中:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yang</groupId>
<artifactId>apache-tomcat-study</artifactId>
<name>Tomcat 9.0 Study</name>
<version>1.0</version>
<packaging>pom</packaging>
<!--表示外層專案包含內層專案,這里要是tomcat版本不同的話,得修改一下,其實就是目錄名-->
<!--這種方式,類似構建微服務子模塊與root專案的關系-->
<modules>
<module>apache-tomcat-9.0.39-src</module>
</modules>
</project>

再:① 在專案配置中設定JDK和原始碼目錄:File->Project Structure->project->project SDK
②設定 java包設定為apche-tomcat-9.0.39-src這個專案的Sources檔案:File->Project Structure->Modules


注意:Modules 沒有專案就添加一下,一個父模塊,一個子模塊,
另外:這里有一個問題,就是把 test 檔案夾設定為了 Test 了之后 運行我們的專案 TestCookieFilter 類就會報錯,


這時候有兩種解決辦法,一是不把 test 檔案夾設定為 Test ,二是看到有的直接洗掉了這個類,

然后:配置 Edit Configrations

Main class:
org.apache.catalina.startup.Bootstrap
vm options配置:
-Dcatalina.home="E:\Code\apache-tomcat-9.0.39-src\catalina-home"
-Dfile.encoding=UTF8
-Duser.language=en
-Duser.region=US


保存運行即可即可,

三、Tomcat啟動流程分析
啟動的分析思路就先看到這里,先來看看service.xml到底配置了什么,于是就延伸出來了以下知識,我先說一下Tomcat 部署專案的方式,然后再來引入Tomcat的四大Servlet容器,
1、部署專案的方式
背景:我把Tomcat原始碼這個專案跑在了IDEA中
有三種方式:這三種方式并不是憑空產生,是有原始碼對應的,

(1)方式一:將web專案(應用)打包成 war 包,之后直接把這個war包放在webapps檔案里
這種方式很好理解,就是把應用交給Tomcat去執行,
那么問題是Tomcat是怎么知道這個war包在webapps下,并它就是一個應用呢?
這個問題一會看到四大容器之一的Host就知道了,


(2)方式二:檔案夾部署(只要有.class位元組碼即可運行專案)
第一種部署方式運行專案時,就是解壓 war 包并且放到當前webapps目錄下,此時是可以洗掉掉 war包的,只存在這個解壓過來的檔案也是可以獨立運行專案的,這就引出了第二種部署方式,檔案夾部署,
(3)方式二:配置應用指定的位置
把應用的位置指定在Host配置中,

2、Tomcat中Container管理四大Servlet容器
先來看看有哪一些,分別是

(1)Engine 表示整個Catalina Servlet引擎,是Container 組件的最高層,用來管理Host 或者Context的實作,是指定默認的Host為 localhost,名字指定為 Catalina,這就是為什么我們不指定Host也可以使用localhost虛擬主機來訪問到應用,

也就是說一個 Engine 對應一個
List<Host> hosts;
(2)Host:我理解它是一個Engine管理下的一個虛擬主機,默認的虛擬主機是 localhost,也就是使用這個虛擬的主機來告訴我們要訪問的 Tomcat 服務的位置,比如說使用 localhost://8080這個就是對應一個虛擬主機,然后再這個Host 里面來配置相應的應用,這樣就是順利訪問到我們指定要訪問的應用了,
當然可以存在多個虛擬主機,雖然它們的都是對應同一個 Tomcat,但是對應的應用不一樣,一個虛擬主機里面也可以對應不同的應用,說白了,多個Host就是來做一個多個應用的指定位置的,

這也解釋了為什么 Tomcat 會去webapps里找應用,
也就是說一個 Host 對應一個
List<Context> contexts;
(3)Context:直接理解的話,是背景關系,但是我理解它是一個應用,或者是一個應用的配置,使用檔案描述符組態檔的話,就會使用到 Context 容器,一個Context可以對應一個應用,
也就是說一個 Context 對應一個
List<Servlet> servlets;
(4)Wrapper:我理解它就是來對我們同一個 Servlet 的不同實體來進行分類管理的,也就是多次請求Tomcat 中同一個 Servlet 資源就會產生不同的 Servlet 實體,然而這些實體不可能任意在容器中,這樣就不要管理,會造成混亂,所以就用 Weapper 來對同一類Servlet 的不同實體進行分類,Wrapper 還用來管理一個 Servlet 的生命周期,
也就是一個 Wrapper 對應一個 Servlet 型別,
List<Servlet> servlets;
而一個Context 就對應了一個Wrapper了
List<Wrapper> wrappers;
Http請求在Container中的傳遞流程

好了,以上就是對 Container 介面的四大子介面的分析,它們分別對應四大 Servlet容器,
花了一個晚上搞清楚了這些介面和容器的關系了,直接看這個時序圖:

3、分析啟動時序圖
從Bootstrap類的main方法開始,Tomcat會以鏈的方式逐級呼叫各個模塊的init()方法進行初始化,待各個模塊都初始化后,又會逐級呼叫各個模塊的start()方法啟動各個模塊,

Bootstrap類首先會創建一個本類物件,然后呼叫init()方法進行初始化,
這里說一下,實體化是在堆空間中開辟相應的空間并賦默認值,初始化是呼叫 init() 方法賦實際值的一個程序,

假如是正常執行 start 的方式的話,可以看到在設定等待后,呼叫了本類物件的load()方法,查看load()方法的原始碼:

可以看到方法的最后通過反射的方式呼叫了成員變數catalinaDaemon的load()方法,通過跟蹤原始碼可以看到catalinaDaemon是Bootstrap類的一個私有成員變數,
public final class Bootstrap {
private static final Log log = LogFactory.getLog(Bootstrap.class);
/**
* Daemon object used by main.di
*/
private static final Object daemonLock = new Object();
private static volatile Bootstrap daemon = null;
/**
* Daemon reference.
*/
//在init()方法中使用反射機制創建catalina賦給catalinaDaemon
private Object catalinaDaemon = null;
它會在Bootstrap的init()方法中通過反射的方式完成初始化,下面我們回過頭來看init()方法的原始碼

可以看到init()方法創建了一個Catalina物件, 并把該物件賦給了catalinaDaemon,
之后再執行 getServer 方法來創建 Server 容器,
public interface Service extends Lifecycle {
/**
* @return the <code>Container</code> that handles requests for all
* <code>Connectors</code> associated with this Service.
*/
public Container getContainer();
來到了 Service 里,你會看到第一句就定義一個獲取 Container 的方法,也就是可以獲取一個唯一的Container, 在這個 Service 里,你會發現有 Executor和Connctor,
此時也就是說可以走 Container 被繼承的四個 Servlet 容器了,
到此也就可以畫出簡單的一個UML圖,來清晰展示這些介面之間的大概關系,

實作與繼承關系:

四、Request請求過來Tomcat在干嘛
1、Pipeline
…
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/222829.html
標籤:java
