1. 程式人生 > >tomcat瞭解:系統架構

tomcat瞭解:系統架構

前言

Tomcat是Apache基金組織下的開源專案,性質是一個Web伺服器。下面這種情況很普遍:在eclipse床架一個web專案並部署到Tomcat中,啟動tomcat,在瀏覽器中輸入一個類似http://localhost:8080/webproject/anyname.jsp的url,然後就可以看到我們寫好的jsp頁面的內容了。一切都是那麼自然和順理成章,然而這一切都是源於tomcat帶給我們的,那麼在tomcat背後,這一切又是怎麼樣發生的呢?帶著對tomcat工作原理的好奇心,我決定研究一下tomcat的原始碼,然而部署原始碼環境的過程卻讓我心灰意冷,本著搞不定我還真不信的熱情,折騰了一個晚上+一個早上,終於把原始碼原始碼環境搭建好了。

為了讓文章顯得更有條理性,我將從以下幾個方面說明Tomcat的工作流程:

  • 搭建Tomcat原始碼環境指導
  • Tomcat的系統架構
  • Tomcat中的核心元件說明
  • Servlet工作原理
  • 一個例子

Tomcat的系統架構

首先我們從一個巨集觀的角度來看一下Tomcat的系統的架構:

Tomcat

從這張圖中可以看到,Tomcat的核心元件就兩個Connector和Container(後面還有詳細說明),一個Connector+一個Container構成一個Service,Service就是對外提供服務的元件,有了Service元件Tomcat就可以對外提供服務了,但是光有服務還不行,還得有環境讓你提供服務才行,所以最外層的Server就為Service提供了生存的土壤。那麼這些個元件到底是幹嘛用的呢?Connector是一個聯結器,主要負責接收請求並把請求交給Container,Container就是一個容器,主要裝的是具體處理請求的元件。Service主要是為了關聯Container與Connector,一個單獨的Container或者一個單獨的Connector都不能完整處理一個請求,只有兩個結合在一起才能完成一個請求的處理。Server這是負責管理Service集合,從圖中我們看到一個Tomcat可以提供多種服務,那麼這些Serice就是由Server來管理的,具體的工作包括:對外提供一個介面訪問Service,對內維護Service集合,維護Service集合又包括管理Service的生命週期、尋找一個請求的Service、結束一個Service等。以上就是對Tomcat的核心元件的簡要說明,下面我們詳細看看每一個元件的執行流程:

Server

上面說Server是管理Service介面的,Server是Tomcat的頂級容器,是一個介面,Server介面的標準實現類是StandardServer類,在Server介面中有許多方法,我們重點關注兩個方法:addService()和findService(String)。我們先來看看Server介面的全貌:

Server介面全貌

接著看看addService()和findService(String)的實現程式碼:

程式碼清單1-1:

/**
 * Add a new Service to the set of defined Services.
 *
 * @param service The Service to be added
 */
@Override
public void addService(Service service) {

    service.setServer(this);

    synchronized (services) {
        Service results[] = new Service[services.length + 1];
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;

        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("service", null, service);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

可以看到,Server使用一個數組來管理Service的,每新增一個Service就把原來的Service拷貝到一個新的陣列中,再把新的Service放入Service陣列中。所以Server與Service是關聯在一起的,那麼後面的getState().isAvailable()是幹嘛的呢?判斷狀態是否無效,從而決定是否執行service方法。這裡說到了狀態,就不得不說Tomcat管理各元件生命週期的Lifecycle介面了:

Lifecycle介面

Tomcat中的元件都交給這個介面管理,但是具體元件的生命週期是由包含元件的父容器來管理的,Tomcat中頂級容器管理著Service的生命週期,Service容器又是Connector和Container的父容器,所以這兩個元件的生命週期是由Service管理的,Container也有子容器,所以管理著這些子容器的生命週期。這樣,只要所有元件都實現了Lifecycle介面,從頂層容器Server開始,就可以控制所有容器的生命週期了。Lifecycle介面中定義了很多狀態,在api中詳細說明了呼叫不同方法後的狀態轉變,同時定義了不同的方法,這些方法在執行後狀態會發生相應的改變,在Lifecycle介面中定義瞭如下方法:

Lifecycle介面方法

在StandServer中實現了startInernal()方法,就是迴圈啟動StandServer管理的Service的過程,Tomcat的Service都實現了Lifecycle介面,所以被管理的Service都將被通知到,從而執行start()方法,startIntenal()方法是這樣的:

程式碼清單1-2:

/**
 * Start nested components ({@link Service}s) and implement the requirements
 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
 *
 * @exception LifecycleException if this component detects a fatal error
 *  that prevents this component from being used
 */
@Override
protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (services) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

現在所有的Service就會收到通知繼而執行start方法。如果一個Service不允許被使用將會丟擲一個LifecycleException異常。

stopIntenal()會通知所有Service執行stop方法,具體處理流程與startIntenal()方法類似。這個執行過程涉及一個非常重要的設計模式,就是觀察者模式

現在我們已經能夠知道了容器通過Lifecycle介面管理容器的生命週期,那麼在父容器的狀態改變具體是怎麼樣通知給子容器的呢?回到程式碼清單1-2,我們注意到有一個fireLifecycleEvent()方法,fireLifecycleEvent()的執行流程如下:

  1. 呼叫LifecycleBase的fireLifecycleEvent(LifecycleListener listener)方法,LifecycleBase是一個抽象類,實現了Lifecycle介面
  2. 繼續呼叫LifecycleSupport(是一個輔助完成對已經註冊監聽器的事件通知類,不可被繼承,使用final)的fireLifecycleEvent(String type, Object data)方法
  3. 完成事件通知

fireLifecycleEvent(String type, Object data)的方法如下:

程式碼清單1-3:

/**
 * Notify all lifecycle event listeners that a particular event has
 * occurred for this Container.  The default implementation performs
 * this notification synchronously using the calling thread.
 *
 * @param type Event type
 * @param data Event data
 */
public void fireLifecycleEvent(String type, Object data) {

    LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++)
        interested[i].lifecycleEvent(event);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

所以,具體事件的通知是由LifecycleListener介面的lifecycleEvent方法完成的,各實現類可以根據不同的情況實現不同的事件監聽邏輯

Service

Service是具體提供服務的介面,一個Service包裝了Connector和一個Container,在Tomcat中這點是如何實現的呢?Service是一個介面,其標準實現類是StandardService,下面是這兩個類的鳥瞰圖:

Service介面 StandardService類

這裡,我們只關心與Connector和Container最緊密的方法:setContainer()和addConnector()方法,先看一下setContainer()方法的原始碼:

程式碼清單2-1:

/**
 * Set the <code>Container</code> that handles requests for all
 * <code>Connectors</code> associated with this Service.
 *
 * @param container The new Container
 */
@Override
public void setContainer(Container container) {

    Container oldContainer = this.container;
    if ((oldContainer != null) && (oldContainer instanceof Engine))
        ((Engine) oldContainer).setService(null);
    this.container = container;
    if ((this.container != null) && (this.container instanceof Engine))
        ((Engine) this.container).setService(this);
    if (getState().isAvailable() && (this.container != null)) {
        try {
            this.container.start();
        } catch (LifecycleException e) {
            // Ignore
        }
    }
    if (getState().isAvailable() && (oldContainer != null)) {
        try {
            oldContainer.stop();
        } catch (LifecycleException e) {
            // Ignore
        }
    }

    // Report this property change to interested listeners
    support.firePropertyChange("container", oldContainer, this.container);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

從程式碼中可以看到這個方法主要的任務是設定一個Container容器來處理一個或者多個Connector傳送過來的請求。首先判斷當前的Service是否已經關聯了Container容器,如果已經關聯了就去除這個關聯關係。如果原來的Container容器已經啟動了就終止其生命週期,結束執行並設定新的關聯關係,這個新的Container容器開始新的生命週期。最後把這個過程通知給感興趣的事件監聽程式。

下面看看addConnector的方法:

程式碼清單2-2:

/**
 * Add a new Connector to the set of defined Connectors, and associate it
 * with this Service's Container.
 *
 * @param connector The Connector to be added
 */
@Override
public void addConnector(Connector connector) {

    synchronized (connectors) {
        connector.setService(this);
        Connector results[] = new Connector[connectors.length + 1];
        System.arraycopy(connectors, 0, results, 0, connectors.length);
        results[connectors.length] = connector;
        connectors = results;

        if (getState().isAvailable()) {
            try {
                connector.start();
            } catch (LifecycleException e) {
                log.error(sm.getString(
                        "standardService.connector.startFailed",
                        connector), e);
            }
        }

        // Report this property change to interested listeners
        support.firePropertyChange("connector", null, connector);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

執行過程也比較清楚:用一個同步程式碼塊包住connectors陣列,首先設定connector與container和service的關聯關係,然後讓connector開始新的生命週期,最後通知感興趣的事件監聽程式。注意到Connector的管理和Server管理Service一樣都使用了陣列拷貝並把新的陣列賦給當前的陣列,從而間接實現了動態陣列。之所以使用陣列我想可能是出於效能的考慮吧。