Tomcat 系統架構與設計模式,第 1 部分: 工作原理

這個分爲兩個部分的系列文章將研究 Apache Tomcat 的系統架構以及其運用的很多經典設計模式。本文是第 1 部分,將主要從 Tomcat 如何分發請求、如何處理多用戶同時請求,還有它的多級容器是如何協調工作的角度來分析 Tomcat 的工作原理,這也是一個 Web 服務器首要解決的關鍵問題。


本文以 Tomcat 5 爲基礎,也兼顧最新的 Tomcat 6 和 Tomcat 4。Tomcat 的基本設計思路和架構是具有一定連續性的。

Tomcat 總體結構

Tomcat 的結構很複雜,但是 Tomcat 也非常的模塊化,找到了 Tomcat 最核心的模塊,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的總體結構圖:

圖 1.Tomcat 的總體結構
圖 1.Tomcat 的總體結構

從上圖中可以看出 Tomcat 的心臟是兩個組件:Connector 和 Container,關於這兩個組件將在後面詳細介紹。Connector 組件是可以被替換,這樣可以提供給服務器設計者更多的選擇,因爲這個組件是如此重要,不僅跟服務器的設計的本身,而且和不同的應用場景也十分相關,所以一個 Container 可以選擇對應多個 Connector。多個 Connector 和一個 Container 就形成了一個 Service,Service 的概念大家都很熟悉了,有了 Service 就可以對外提供服務了,但是 Service 還要一個生存的環境,必須要有人能夠給她生命、掌握其生死大權,那就非 Server 莫屬了。所以整個 Tomcat 的生命週期由 Server 控制。

以 Service 作爲“婚姻”

我們將 Tomcat 中 Connector、Container 作爲一個整體比作一對情侶的話,Connector 主要負責對外交流,可以比作爲 Boy,Container 主要處理 Connector 接受的請求,主要是處理內部事務,可以比作爲 Girl。那麼這個 Service 就是連接這對男女的結婚證了。是 Service 將它們連接在一起,共同組成一個家庭。當然要組成一個家庭還要很多其它的元素。

說白了,Service 只是在 Connector 和 Container 外面多包一層,把它們組裝在一起,向外面提供服務,一個 Service 可以設置多個 Connector,但是只能有一個 Container 容器。這個 Service 接口的方法列表如下:

圖 2. Service 接口
圖 2. Service 接口

從 Service 接口中定義的方法中可以看出,它主要是爲了關聯 Connector 和 Container,同時會初始化它下面的其它組件,注意接口中它並沒有規定一定要控制它下面的組件的生命週期。所有組件的生命週期在一個 Lifecycle 的接口中控制,這裏用到了一個重要的設計模式,關於這個接口將在後面介紹。

Tomcat 中 Service 接口的標準實現類是 StandardService 它不僅實現了 Service 藉口同時還實現了 Lifecycle 接口,這樣它就可以控制它下面的組件的生命週期了。StandardService 類結構圖如下:

圖 3. StandardService 的類結構圖
圖 3. StandardService 的類結構圖

從上圖中可以看出除了 Service 接口的方法的實現以及控制組件生命週期的 Lifecycle 接口的實現,還有幾個方法是用於在事件監聽的方法的實現,不僅是這個 Service 組件,Tomcat 中其它組件也同樣有這幾個方法,這也是一個典型的設計模式,將在後面介紹。

下面看一下 StandardService 中主要的幾個方法實現的代碼,下面是 setContainer 和 addConnector 方法的源碼:

清單 1. StandardService. SetContainer
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 (started && (this.container != null) && (this.container instanceof Lifecycle)) {
        try {
            ((Lifecycle) this.container).start();
        } catch (LifecycleException e) {
            ;
        }
    }
    synchronized (connectors) {
        for (int i = 0; i < connectors.length; i++)
            connectors[i].setContainer(this.container);
    }
    if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {
        try {
            ((Lifecycle) oldContainer).stop();
        } catch (LifecycleException e) {
            ;
        }
    }
    support.firePropertyChange("container", oldContainer, this.container);
}

這段代碼很簡單,其實就是先判斷當前的這個 Service 有沒有已經關聯了 Container,如果已經關聯了,那麼去掉這個關聯關係—— oldContainer.setService(null)。如果這個 oldContainer 已經被啓動了,結束它的生命週期。然後再替換新的關聯、再初始化並開始這個新的 Container 的生命週期。最後將這個過程通知感興趣的事件監聽程序。這裏值得注意的地方就是,修改 Container 時要將新的 Container 關聯到每個 Connector,還好 Container 和 Connector 沒有雙向關聯,不然這個關聯關係將會很難維護。

清單 2. StandardService. addConnector
public void addConnector(Connector connector) {
    synchronized (connectors) {
        connector.setContainer(this.container);
        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 (initialized) {
            try {
                connector.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }
        if (started && (connector instanceof Lifecycle)) {
            try {
                ((Lifecycle) connector).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("connector", null, connector);
    }
}

上面是 addConnector 方法,這個方法也很簡單,首先是設置關聯關係,然後是初始化工作,開始新的生命週期。這裏值得一提的是,注意 Connector 用的是數組而不是 List 集合,這個從性能角度考慮可以理解,有趣的是這裏用了數組但是並沒有向我們平常那樣,一開始就分配一個固定大小的數組,它這裏的實現機制是:重新創建一個當前大小的數組對象,然後將原來的數組對象 copy 到新的數組中,這種方式實現了類似的動態數組的功能,這種實現方式,值得我們以後拿來借鑑。

最新的 Tomcat6 中 StandardService 也基本沒有變化,但是從 Tomcat5 開始 Service、Server 和容器類都繼承了 MBeanRegistration 接口,Mbeans 的管理更加合理。

以 Server 爲“居”

前面說一對情侶因爲 Service 而成爲一對夫妻,有了能夠組成一個家庭的基本條件,但是它們還要有個實體的家,這是它們在社會上生存之本,有了家它們就可以安心的爲人民服務了,一起爲社會創造財富。

Server 要完成的任務很簡單,就是要能夠提供一個接口讓其它程序能夠訪問到這個 Service 集合、同時要維護它所包含的所有 Service 的生命週期,包括如何初始化、如何結束服務、如何找到別人要訪問的 Service。還有其它的一些次要的任務,如您住在這個地方要向當地政府去登記啊、可能還有要配合當地公安機關日常的安全檢查什麼的。

Server 的類結構圖如下:

圖 4. Server 的類結構圖
圖 4. Server 的類結構圖

它的標準實現類 StandardServer 實現了上面這些方法,同時也實現了 Lifecycle、MbeanRegistration 兩個接口的所有方法,下面主要看一下 StandardServer 重要的一個方法 addService 的實現:

清單 3. StandardServer.addService
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 (initialized) {
            try {
                service.initialize();
            } catch (LifecycleException e) {
                e.printStackTrace(System.err);
            }
        }
        if (started && (service instanceof Lifecycle)) {
            try {
                ((Lifecycle) service).start();
            } catch (LifecycleException e) {
                ;
            }
        }
        support.firePropertyChange("service", null, service);
    }
}

從上面第一句就知道了 Service 和 Server 是相互關聯的,Server 也是和 Service 管理 Connector 一樣管理它,也是將 Service 放在一個數組中,後面部分的代碼也是管理這個新加進來的 Service 的生命週期。Tomcat6 中也是沒有什麼變化的。

組件的生命線“Lifecycle”

前面一直在說 Service 和 Server 管理它下面組件的生命週期,那它們是如何管理的呢?

Tomcat 中組件的生命週期是通過 Lifecycle 接口來控制的,組件只要繼承這個接口並實現其中的方法就可以統一被擁有它的組件控制了,這樣一層一層的直到一個最高級的組件就可以控制 Tomcat 中所有組件的生命週期,這個最高的組件就是 Server,而控制 Server 的是 Startup,也就是您啓動和關閉 Tomcat。

下面是 Lifecycle 接口的類結構圖:

圖 5. Lifecycle 類結構圖
圖 5. Lifecycle 類結構圖

除了控制生命週期的 Start 和 Stop 方法外還有一個監聽機制,在生命週期開始和結束的時候做一些額外的操作。這個機制在其它的框架中也被使用,如在 Spring 中。關於這個設計模式會在後面介紹。

Lifecycle 接口的方法的實現都在其它組件中,就像前面中說的,組件的生命週期由包含它的父組件控制,所以它的 Start 方法自然就是調用它下面的組件的 Start 方法,Stop 方法也是一樣。如在 Server 中 Start 方法就會調用 Service 組件的 Start 方法,Server 的 Start 方法代碼如下:

清單 4. StandardServer.Start
public void start() throws LifecycleException {
    if (started) {
        log.debug(sm.getString("standardServer.start.started"));
        return;
    }
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;
    synchronized (services) {
        for (int i = 0; i < services.length; i++) {
            if (services[i] instanceof Lifecycle)
                ((Lifecycle) services[i]).start();
        }
    }
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

監聽的代碼會包圍 Service 組件的啓動過程,就是簡單的循環啓動所有 Service 組件的 Start 方法,但是所有 Service 必須要實現 Lifecycle 接口,這樣做會更加靈活。

Server 的 Stop 方法代碼如下:

清單 5. StandardServer.Stop
public void stop() throws LifecycleException {
    if (!started)
        return;
    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;
    for (int i = 0; i < services.length; i++) {
        if (services[i] instanceof Lifecycle)
            ((Lifecycle) services[i]).stop();
    }
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

它所要做的事情也和 Start 方法差不多。


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