Java後端自頂向下方法——Tomcat初步

Java後端自頂向下方法——Tomcat初步

(一)瞭解Tomcat

講了這麼多,終於講到我們的Tomcat(不就是湯姆貓?我也不知道爲啥要起這個名字)了。首先我們要明白Tomcat到底是什麼?Tomcat其實就是就是一個Servlet容器。一個Servlet容器對於發送到每個Servlet的HTTP請求會完成以下事情:
(1)當Servlet 第一次被調用的時候,加載了Servlet類並調用它的init方法(僅調用一次)
(2)響應每次請求的時候,構建ServletRequest和ServletResponse實例。
(3)調用Servlet的service方法,將ServletRequest對象和ServletResponse對象當作參數傳入。
(4)當servlet類關閉的時候,調用Servlet的destroy方法,並卸載Servlet類。

因此,Tomcat和Servlet是密不可分的,瞭解Servlet才能幫助我們更好的瞭解Tomcat。首先我先把Tomcat的原本的配置文件(註釋已經都去掉了)寫在這,方便參考:

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>
</Server>

(二)Tomcat頂層結構

在這裏插入圖片描述

我們先來看這張圖,裏面有Server、Service、Connector、Container之類的東西,我們就挑選一些重要的來講。

1. Server

Server是服務器的意思,代表整個Tomcat服務器,一個Tomcat只有一個Server。Server是Tomcat最頂層的容器,Server中包含至少一個Service組件,用於提供具體服務。Tomcat中其標準實現是:org.apache.catalina.core.StandardServer類。

我們可以思考一下,我們的Tomcat服務器主要要實現哪些功能。首先是接收客戶端的請求,然後解析請求,完成相應的業務邏輯,然後把處理後的結果返回給客戶端。服務器一般需要提供兩個基本方法,一個start打開服務Socket連接,監聽服務端口,一個stop停止服務釋放網絡資源。

但是這種將監聽請求可處理請求放在一起的思路會影響擴展性,比如我們不想用HTTP了,我們想換一個網絡協議,那我們的Tomcat就需要大量的修改,顯然是很不方便的。所以,Tomcat開發者將監聽請求和處理請求分開,這就有了我們的Connector模塊和Container模塊。

2. Connector

Connector是連接器,用於監聽請求並將請求封裝成ServletRequest和ServletResponse對象,然後交給Container進行處理,Container處理完之後在交給Connector返回給客戶端。一個Connector會監聽一個獨立的端口來處理來自客戶端的請求。server.xml中默認配置了一個Connector,端口號爲8080。

在這裏插入圖片描述
其實Connector結構非常複雜,不過我們暫時不需要了解這麼多,我們只需要知道他的功能即可,詳細的東西我會在Tomcat源碼分析中講。

3. Container

在這裏插入圖片描述

我們可以看到,Container實際上是一個Engine,一個Engine裏面可以有多個Host,一個Host裏面有多個Context,每個Context裏面又有多個Wrapper,而每個Wrapper裏面都有一個Servlet。這就是Container大體的結構。下面我們來看看細節:

首先我們來看Engine,Engine表示整個Servlet引擎,一個Engine下面可以包含一個或者多個Host,即一個Tomcat實例可以配置多個虛擬主機,默認的情況下server.xml配置文件中<Engine name=“Catalina” defaultHost=“localhost”> 定義了一個名爲Catalina的Engine。

然後是Host,代表一個站點,也可以叫虛擬主機,一個Host可以配置多個Context,在server.xml文件中的默認配置爲<Host name=“localhost” appBase=“webapps” unpackWARs=“true” autoDeploy=“true”>。一個Engine包含多個Host的設計,使得一個服務器實例可以承擔多個域名的服務,是很靈活的設計。

緊接着是最重要的Context。Context代表一個應用程序,也就是我們開發的應用,或者一個WEB-INF目錄以及下面的web.xml文件,換句話說每一個運行的webapp最終都是以Context的形式存在,每個Context都有一個根路徑和請求路徑。Context與Host的區別是Context代表一個應用,默認配置下webapps下的每個目錄都是一個應用,其中ROOT目錄中存放主應用,其他目錄存放別的子應用,而整個webapps是一個站點,也就是Host。

最後是Wrapper,在Tomcat中Servlet被稱爲Wrapper,至於爲什麼要用Wrapper,這與Tomcat的處理機制有關,現在暫時不用瞭解。

4. Service

很顯然,Tomcat可以有多個Connector,同時Container也可以有多個。那這就存在一個問題,哪個Connector對應哪個Container?相信看過server.xml文件的人已經知道了Tomcat是怎麼處理的了。

沒錯,Service就是這樣來的。在server.xml文件中,可以看到Service組件包含了Connector組件和Engine組件(Engine就是一種Container),即Service相當於Connector和Engine組件的包裝器,將一個或者多個Connector和一個Engine建立關聯關係。

在默認的配置文件中,定義了一個叫Catalina的service,它將HTTP/1.1和AJP/1.3這兩個Connector與一個名爲Catalina的Engine關聯起來。一個Server可以包含多個Service,它們相互獨立,一個Service負責維護多個Connector和一個Container。

下面兩張圖幫我們將Tomcat結構總結一下:

在這裏插入圖片描述
在這裏插入圖片描述
我們要記住的就是各個模塊的作用,以及所有模塊的層次關係,這樣在Tomcat出現問題的時候我們才能快速定位到錯誤的位置和原因。

(三)Tomcat啓動流程

你一定會很好奇Tomcat是如何啓動的,爲什麼在idea裏面鼠標一點就可以啓動我們的服務器?在這段時間中Tomcat又爲我們做了什麼?想了解這些,我們有必要來看看Tomcat的啓動流程。

因爲具體講啓動流程會涉及到源碼,但初學階段不建議查閱源碼,我們就用圖形大概的看看就行。以後我會出Tomcat源碼分析來具體講這個問題。

在這裏插入圖片描述
由於Tomcat組件之間的包含關係,一個組件中可以包含多個子組件,且這些組件都實現了Lifecycle接口。因此只要啓動最上層的server組件,它包含的所有組件也會跟着啓動了,例如啓動server,所有子service都會跟着啓動,service啓動了,它的Container和Connector等也會跟着啓動。因此,tomat要啓動,只需要啓動server就可以了。那我們的Server如何啓動呢?這就需要Catalina類。

Catalina是整個Tomcat的管理類,他有三個方法load、start、stop分別用來管理整個服務器的生命週期。load方法用於加載Tomcat/conf目錄下的server.xml配置文件,用來創建Server並調用Server的init方法進行初始化操作,start用於啓動服務器,stop用於停止服務器,start和stop方法在內部分別調用Server的start和stop方法,load方法在內部調用了Server的init方法,這三個方法都會按層次分逐層調用相應的方法。Catalina的啓動又依賴Bootstrap。

Bootstrap是main方法所在地,是Tomcat真正的入口類,我們可以簡單看一下main方法裏面有什麼:

public static void main(String args[]) {
        //首先判斷Bootstrap daemon 是否爲空,就是創建一個Bootstrap實例daemon
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.init(); //初始化ClassLoader,並用ClassLoader創建了一個Catalina實例,並將這個實例賦值給了cataLinaDaemon
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            String command = "start"; //指定默認的執行指令是start
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            if (command.equals("startd")) {
                args[args.length - 1] = "start";
                daemon.load(args); //BootStrap的load方法,其實是調用Calatina的load方法
                daemon.start();
            } else if (command.equals("stopd")) {
                args[args.length - 1] = "stop";
                daemon.stop();
            } else if (command.equals("start")) {
                daemon.setAwait(true); //setWait方法 主要是爲了設置Server啓動完成後是否進入等待狀態的標誌,如果爲true則進入,否則不進入
                daemon.load(args);   //load方法用於加載配置文件,創建並初始化Server
                daemon.start();     //start方法用於啓動服務器
            } else if (command.equals("stop")) {
                daemon.stopServer(args);
            } else if (command.equals("configtest")) {
                daemon.load(args);
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

這段代碼基本人人都能看得懂,就是創建了一個Bootstrap對象並藉助init方法初始化了Catalina對象,接着將這個Bootstrap對象賦值給daemon。然後通過判斷main函數的參數來決定調用daemon的哪個方法。很顯然,如果main函數的參數爲start,那麼就會調用daemon的start方法,不用看源碼都知道,這個方法肯定幫助我們調用了Catalina的start方法,然後啓動整個服務器。具體的代碼我會在Tomcat源碼分析裏面詳細說明。

2020年5月27日

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章