死磕Tomcat系列(5)——容器

死磕Tomcat系列(5)——容器

回顧

死磕Tomcat系列(1)——總體架構中咱們簡單介紹了容器的概念,而且說了在容器中全部子容器的父接口是Container。在死磕Tomcat系列(2)——EndPoint源碼解析中,咱們知道了鏈接器將請求過來的數據解析成Tomcat須要的ServletRequest對象給容器。那麼容器又是如何將這個對象準確的分到到對應的請求上去的呢?html

容器的總體設計

Container是容器的父接口,全部子容器都須要實現此接口,咱們首先看一下Container接口的設計。java

public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

Tomcat是如何管理這些容器的呢?咱們能夠經過接口的設計能夠了解到是經過設置父子關係,造成一個樹形的結構(一父多子)、鏈式結構(一父一子)來管理的。一想到樹形的結構咱們應該就立馬可以聯想到設計模式中的組合模式,而鏈式結構咱們應該可以想到設計模式中的責任鏈設計模式。不管這兩種的哪種咱們都知道這種關係是上下層級的關係。用圖來表示就是以下。web

既然是父子的結構,那麼鏈接器是如何將轉換好的ServletRequest給到容器的呢?咱們能夠看CoyoteAdapter中的service方法。由於在鏈接器中最後一環是將解析過的Request給到Adapter運用適配器設計模式解析爲ServletRequest對象。在service方法中咱們看到有這麼一句。設計模式

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

而其中的getContainer方法,返回的是Engine對象tomcat

public Engine getContainer();

這裏看到了PipelinePipeline應該你們有所熟悉,是管道的概念,那麼管道里面裝的是什麼呢?咱們看其定義的方法架構

public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

能夠看到Pipeline 管道里面裝的是Valve,那麼Valve是如何組織起來的呢?咱們也能夠看它的代碼定義app

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

能夠知道每一個Valve都是一個處理點,它的invoke就是相對應的處理邏輯。能夠看到有setNext的方法,所以咱們大概可以猜到是經過鏈表將Valve組織起來的。而後將此Valve裝入Pipeline中。所以每一個容器都有一個Pipeline,裏面裝入系統定義或者自定義的一些攔截節點來作一些相應的處理。所以只要得到了容器中Pipeline管道中的第一個Valve對象,那麼後面一系列鏈條都會執行到。框架

可是不一樣容器之間Pipeline之間是如何進行觸發的呢?即例如EnginePipeline處理完了最後一個Valve,那麼如何調用HostPipeLine管道中的Valve呢?咱們能夠看到每一個Pipeline中還有一個方法。setBasic 這個方法設置的就是Valve鏈條的末端節點是什麼,它負責調用底層容器的Pipeline第一個Valve節點。用圖表示就是這樣的。webapp

Engine容器

Engine容器比較簡單,只是定義了一些基本的關聯關係。它的實現類是StandardEngineide

@Override
    public void addChild(Container child) {
        if (!(child instanceof Host))
            throw new IllegalArgumentException
                (sm.getString("standardEngine.notHost"));
        super.addChild(child);
    }
    @Override
    public void setParent(Container container) {
        throw new IllegalArgumentException
            (sm.getString("standardEngine.notParent"));

    }

須要注意Engine容器是沒有父容器的。若是添加是會報錯。添加子容器也只是能添加Host容器。

Host 容器

Host容器是Engine的子容器,一個Host在Engine中表明一個虛擬主機,這個虛擬主機的做用就是運行多個應用,它負責安裝和展開這個應用,而且標識這個應用以便可以區分它們。它的子容器一般是Context容器。咱們能夠看配置文件中也可以看出Host文件的做用。

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

那麼Host容器在啓動時具體幹了什麼呢?咱們看它的startInternal方法看不出來什麼,只是啓動了相應的Valve,是由於在Tomcat的設計中引入了生命週期的概念,即每一個模塊都有本身相應的生命週期,模塊的生命週期定義有NEW、INITIALIZING、INITIALIZED、SSTARTING_PREP、STARTING、STARTED,每一個模塊狀態的變化都會引起一系列的動做,那麼這些動做的執行是直接寫在startInternal 中嗎?這樣會違反開閉原則,那麼如何解決這個問題呢?開閉原則說的是爲了擴展性系統的功能,你不能修改系統中現有的類,可是你能夠定義新的類。

因而每一個模塊狀態的變化至關於一個事件的發生,而事件是有相應的監聽器的。在監聽器中實現具體的邏輯,監聽器也能夠方便的增長和刪除。這就是典型的觀察者模式。

那麼Host容器在啓動的時候須要掃描webapps目錄下面的全部Web應用,建立相應的Context容器。那麼Host的監聽器就是HostConfig,它實現了LifecycleListener接口

public interface LifecycleListener {

    public void lifecycleEvent(LifecycleEvent event);
}

接口中只定義了一個方法,即監聽到相應事件的處理邏輯。能夠看到在setState方法中調用了監聽器的觸發。

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    for (LifecycleListener listener : lifecycleListeners) {
        listener.lifecycleEvent(event);
    }
}

因此容器中各組件的具體處理邏輯是在監聽器中實現的。

Context 容器

一個Context對應一個Web應用

Context表明的是Servlet的Context,它具有了Servlet的運行的基本環境。Context最重要的功能就是管理它裏面的Servlet實例,Servlet實例在Context中是以Wrapper出現的。Context準備運行環境是在ContextConfiglifecycleEvent方法準備的。

@Override
public void lifecycleEvent(LifecycleEvent event) {

    // Identify the context we are associated with
    try {
        context = (Context) event.getLifecycle();
    } catch (ClassCastException e) {
        log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
        configureStart();
    } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
        beforeStart();
    } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
        // Restore docBase for management tools
        if (originalDocBase != null) {
            context.setDocBase(originalDocBase);
        }
    } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
        configureStop();
    } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
        init();
    } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
        destroy();
    }
}

Wrapper容器

Wrapper容器表明一個Servlet,包括Servlet的裝載、初始化、執行以及資源的回收。Wrapper是最底層的容器,它沒有子容器。

Wrapper的實現類是StandardWrapper,主要任務是載入Servlet類,並進行實例化。可是StandardWrapper類並不會調用Servletservice方法。而是StandardWrapperValue 類經過調用StandardWrpper allocate 方法得到相應的servlet,而後經過攔截器的過濾以後纔會調用相應的Servlet的service方法

總結

Tomcat的容器中有許多值得咱們學習的設計思想,例如將不變的抽取出來,而後變化的子類來實現的模板設計模式、維護一堆父子關係的組合設計模式、事件的發生伴隨監聽者的相應動做執行的觀察者設計模式等等。

在學習框架的時候,有時不必深究裏面一行一行的代碼,而要學習它的思想。知道它是如何運行,隨後若是查找問題,或者是對框架進行相應擴展。這時候再深刻學習裏面的代碼將會事半功倍。

參考文章

往期文章

如何斷點調試Tomcat源碼

死磕Tomcat系列(1)——總體架構

死磕Tomcat系列(2)——EndPoint源碼解析

死磕Tomcat系列(3)——Tomcat如何作到一鍵式啓停的

死磕Tomcat系列(4)——Tomcat中的類加載器

一次奇怪的StackOverflowError問題查找之旅

徒手擼一個簡單的RPC框架

徒手擼一個簡單的RPC框架(2)——項目改造

相關文章
相關標籤/搜索