文章目錄
1、官網下載Tomcat 的源碼導入IDEA
一、地址:http://tomcat.apache.org/ 左側 Download Tomcat 7,在網頁最下面下載Tomcat源碼;
二、下載完成後打開 , 選擇File->Open->選擇tomcat的源碼目錄(我下載的是apache-tomcat-7.0.106);
java
三、① 在項目配置中設置JDK和源碼目錄:File->Project Structure->project->project SDK
②設置 java包設置爲Tomcat項目的Sources文件:File->Project Structure->Modules
web
2、Tomcat啓動重要文件
startup源碼分析
我以爲要研究一個技術的源碼要從它是怎麼啓動運行的開始,特別是很複雜的源碼,全部我就從Tomcat的啓動開始。
apache
在Tomcat的bin目錄下有兩個啓動Tomcat的文件,一個是startup.bat,它用於windows環境下啓動Tomcat;另外一個是startup.sh,它用於Linux環境下Tomcat的啓動。大概看了下這兩個文件中的實現思路差很少同樣的,我就看了startup.bat(windows啓動文件)bootstrap
如下是startup.bat文件,我加了一些註解:
經過以上閱讀能夠獲得一個結論: startup.bat文件實際上就作了一件事情 -> 啓動catalina.bat
這樣我也明白了,爲何在此以前我在windows下配置了catalina.bat就可使用catalina run 啓動Tomcat了。因此未必必定要經過startup.bat來啓動Tomcat,用catalina.bat start也是能夠的。
windows
catalina.bat源碼分析
既然在 startup.bat有關聯到 catalina.bat ,那麼就確定要看看這個文件是幹嗎的了。api
因爲註解比代碼多,我就梳理一下大概的執行邏輯tomcat
首先會直接跳到mainEntry代碼段 -> 在肯定CATALINA_HOME下有catalina.bat後再把CATALINA_HOME賦給變量CATALINA_BASE -> 以後再去得到CLASSPATH(就是咱們配置的環境變量)-> 系統拿到classpath路徑後把它和CATALINA_HOME拼接在一塊兒,最終定位到一個叫bootstrap.jar的文件;
而後到 doStart
代碼塊(固然還有doDebug和doRun)並設定參數,最終跳轉到execCmd
代碼段;
app
經過以上閱讀能夠獲得一個結論: catalina.bat最終執行了Bootstrap類中的main方法。
讀完catalina.bat會發現,咱們能夠經過設定不一樣的參數讓Tomcat以不一樣的方式運行。好比說:在IDEA中能夠選擇debug等模式啓動Tomcat的,也能夠爲其配置參數,在catalina.bat中咱們能夠看到了啓動Tomcat背後的運做流程。
eclipse
//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()方法,那麼不妨這麼設置一下來運行項目:
首先:新建 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中
webapp
<?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.20</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> <!--表示外層項目包含內層項目--> <modules> <module>apache-tomcat-9.0.39-src</module> </modules> </project>
而後:配置 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
保存運行便可便可。
3、Tomcat啓動流程分析
啓動的分析思路就先看到這裏,先來看看service.xml到底配置了什麼,因而就延伸出來了如下知識。我先說一下Tomcat 部署項目的方式,而後再來引入Tomcat的四大Servlet容器。
一、部署項目的方式
背景:我把Tomcat源碼這個項目跑在了IDEA中
有三種方式:這三種方式並非憑空產生,是有源碼對應的。
(1)方式一:將web項目(應用)打包成 war 包,以後直接把這個war包放在webapps文件裏
這種方式很好理解,就是把應用交給Tomcat去執行。
那麼問題是Tomcat是怎麼知道這個war包在webapps下,並它就是一個應用呢?
這個問題一會看到四大容器之一的Host就知道了。
(2)方式二:文件夾部署(只要有.class字節碼便可運行項目)
第一種部署方式運行項目時,就是解壓 war 包而且放到當前webapps目錄下,此時是能夠刪除掉 war包的,只存在這個解壓過來的文件也是能夠獨立運行項目的,這就引出了第二種部署方式,文件夾部署。
(3)方式二:配置應用指定的位置
把應用的位置指定在Host配置中。
二、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容器。
花了一個晚上搞清楚了這些接口和容器的關係了,直接看這個時序圖:
三、分析啓動時序圖
從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圖。來清晰展現這些接口之間的大概關係。
實現與繼承關係:
4、Request請求過來Tomcat在幹嗎
一、Pipeline
…