Tomcat 的心髒是兩個元件:Connector 和 Container,Connector 元件是可以被替換,這樣可以提供給伺服器設計者更多的選擇,因為這個元件是如此重要,不僅跟伺服器的設計的本身,而且和不同的應用場景也十分相關,是以一個 Container 可以選擇對應多個 Connector。多個 Connector 和一個 Container 就形成了一個 Service,有了 Service 就可以對外提供服務了,但是 Service 還要一個生存的環境,必須要有人能夠給她生命、掌握其生死大權,那就非 Server 莫屬了。是以整個 Tomcat 的生命周期由 Server 控制。
Tomcat 中 Connector、Container 作為一個整體比作一對情侶的話,Connector 主要負責對外交流,可以比作為 Boy,Container 主要處理 Connector 接受的請求,主要是處理内部事務,可以比作為 Girl。那麼這個 Service 就是連接配接這對男女的結婚證了。是 Service 将它們連接配接在一起,共同組成一個家庭。當然要組成一個家庭還要很多其它的元素。
Service 隻是在 Connector 和 Container 外面多包一層,把它們組裝在一起,向外面提供服務,一個 Service 可以設定多個 Connector,但是隻能有一個 Container 容器。它主要是為了關聯 Connector 和 Container,同時會初始化它下面的其它元件,注意接口中它并沒有規定一定要控制它下面的元件的生命周期。所有元件的生命周期在一個 Lifecycle 的接口中控制。
Tomcat 中 Service 接口的标準實作類是 StandardService 它不僅實作了 Service 接口同時還實作了 Lifecycle 接口,這樣它就可以控制它下面的元件的生命周期了。除了 Service 接口的方法的實作以及控制元件生命周期的 Lifecycle 接口的實作,還有幾個方法是用于在事件監聽的方法的實作,不僅是這個 Service 元件,Tomcat 中其它元件也同樣有這幾個方法。
先判斷目前的這個 Service 有沒有已經關聯了 Container,如果已經關聯了,那麼去掉這個關聯關系—— oldContainer.setService(null)。如果這個 oldContainer 已經被啟動了,結束它的生命周期。然後再替換新的關聯、再初始化并開始這個新的 Container 的生命周期。最後将這個過程通知感興趣的事件監聽程式。這裡值得注意的地方就是,修改 Container 時要将新的 Container 關聯到每個 Connector,還好 Container 和 Connector 沒有雙向關聯,不然這個關聯關系将會很難維護。
首先是設定關聯關系,然後是初始化工作,開始新的生命周期。這裡值得一提的是,注意 Connector 用的是數組而不是 List 集合,這個從性能角度考慮可以了解,有趣的是這裡用了數組但是并沒有向我們平常那樣,一開始就配置設定一個固定大小的數組,它這裡的實作機制是:重新建立一個目前大小的數組對象,然後将原來的數組對象 copy 到新的數組中,這種方式實作了類似的動态數組的功能。Tomcat6 中 StandardService 也基本沒有變化,但是從 Tomcat5 開始 Service、Server 和容器類都繼承了 MBeanRegistration 接口,Mbeans 的管理更加合理。
一對情侶因為 Service 而成為一對夫妻,有了能夠組成一個家庭的基本條件,但是它們還要有個實體的家,這是它們在社會上生存之本,有了家它們就可以安心的為人民服務了,一起為社會創造财富。Server 要完成的任務很簡單,就是要能夠提供一個接口讓其它程式能夠通路到這個 Service 集合、同時要維護它所包含的所有 Service 的生命周期,包括如何初始化、如何結束服務、如何找到别人要通路的 Service。還有其它的一些次要的任務,如您住在這個地方要向當地政府去登記啊、可能還有要配合當地公安機關日常的安全檢查什麼的。
它的标準實作類 StandardServer 實作了Server接口,同時也實作了 Lifecycle、MbeanRegistration 兩個接口的所有方法。
從上面第一句就知道了 Service 和 Server 是互相關聯的,Server 也是和 Service 管理 Connector 一樣管理它,也是将 Service 放在一個數組中,後面部分的代碼也是管理這個新加進來的 Service 的生命周期。Tomcat6 中也是沒有什麼變化的。
Connector 元件是 Tomcat 中兩個核心元件之一,它的主要任務是負責接收浏覽器的發過來的 tcp 連接配接請求,建立一個 Request 和 Response 對象分别用于和請求端交換資料,然後會産生一個線程來處理這個請求并把産生的 Request 和 Response 對象傳給處理這個請求的線程,處理這個請求的線程就是 Container 元件要做的事了。
Tomcat5 中預設的 Connector 是 Coyote,這個 Connector 是可以選擇替換的。Connector 最重要的功能就是接收連接配接請求然後配置設定線程讓 Container 來處理這個請求,是以這必然是多線程的,多線程的處理是 Connector 設計的核心。Tomcat5 将這個過程更加細化,它将 Connector 劃分成 Connector、Processor、Protocol, 另外 Coyote 也定義自己的 Request 和 Response 對象。
Container 是容器的父接口,所有子容器都必須實作這個接口,Container 容器的設計用的是典型的責任鍊的設計模式,它有四個子容器元件構成,分别是:Engine、Host、Context、Wrapper,這四個元件是父子關系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一個 Servlet class 對應一個 Wrapper,如果有多個 Servlet 就可以定義多個 Wrapper,如果有多個 Wrapper 就要定義一個更高的 Container 了,如 Context,Context 通常就是對應下面這個配置:
Context 還可以定義在父容器 Host 中,Host 不是必須的,但是要運作 war 程式,就必須要 Host,因為 war 中必有 web.xml 檔案,這個檔案的解析就需要 Host 了,如果要有多個 Host 就要定義一個 top 容器 Engine 了。而 Engine 沒有父容器了,一個 Engine 代表一個完整的 Servlet 引擎。
當 Connector 接受到一個連接配接請求時,将請求交給 Container,Container 是如何處理這個請求的?這四個元件是怎麼分工的,怎麼把請求傳給特定的子容器的呢?又是如何将最終的請求交給 Servlet 處理。
Valve 的設計在其他架構中也有用的,同樣 Pipeline 的原理也基本是相似的,它是一個管道,Engine 和 Host 都會執行這個 Pipeline,您可以在這個管道上增加任意的 Valve,Tomcat 會挨個執行這些 Valve,而且四個元件都會有自己的一套 Valve 集合。您怎麼才能定義自己的 Valve 呢?在 server.xml 檔案中可以添加,如給 Engine 和 Host 增加一個 Valve 如下:
StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的預設的 Valve,它們是最後一個 Valve 負責将請求傳給它們的子容器,以繼續往下執行。
前面是 Engine 和 Host 容器的請求過程,下面看 Context 和 Wrapper 容器時如何處理請求的。下面是處理請求的時序圖:
從 Tomcat5 開始,子容器的路由放在了 request(以前放在mapper中) 中,request 中儲存了目前請求正在處理的 Host、Context 和 wrapper。
它的标準實作類是 StandardEngine,這個類注意一點就是 Engine 沒有父容器了,如果調用 setParent 方法時将會報錯。添加子容器也隻能是 Host 類型的。它的初始化方法也就是初始化和它相關聯的元件,以及一些事件的監聽。
Host 是 Engine 的字容器,一個 Host 在 Engine 中代表一個虛拟主機,這個虛拟主機的作用就是運作多個應用,它負責安裝和展開這些應用,并且辨別這個應用以便能夠區分它們。它的子容器通常是 Context,它除了關聯子容器外,還有就是儲存一個主機應該有的資訊。
所有容器都繼承的 ContainerBase ,StandardHost 還實作了 Deployer 接口,Deployer 接口的這些方法都是安裝、展開、啟動和結束每個 web application。Deployer 接口的實作是 StandardHostDeployer,Host 可以調用這些方法完成應用的部署等。
Context 代表 Servlet 的 Context,它具備了 Servlet 運作的基本環境,理論上隻要有 Context 就能運作 Servlet 了。簡單的 Tomcat 可以沒有 Engine 和 Host。
Context 最重要的功能就是管理它裡面的 Servlet 執行個體,Servlet 執行個體在 Context 中是以 Wrapper 出現的,還有一點就是 Context 如何才能找到正确的 Servlet 來執行它呢? Tomcat5 以前是通過一個 Mapper 類來管理的,Tomcat5 以後這個功能被移到了 request 中,在前面的時序圖中就可以發現擷取子容器都是通過 request 來配置設定的。
Context 準備 Servlet 的運作環境是在 Start 方法開始的
Context 的配置檔案中有個 reloadable 屬性
當這個 reloadable 設為 true 時,war 被修改後 Tomcat 會自動的重新加載這個應用。如何做到這點的呢 ? 這個功能是在 StandardContext 的 backgroundProcess 方法中實作的.
//它會調用 reload 方法,而 reload 方法會先調用 stop 方法然後再調用 Start 方法,完成 Context 的一次重新加載。可以看出執行 reload 方法的條件是 reloadable 為 true 和應用被修改,那麼這個 backgroundProcess 方法是怎麼被調用的呢?
//這個方法是在 ContainerBase 類中定義的内部類 ContainerBackgroundProcessor 被周期調用的,這個類是運作在一個背景線程中,它會周期的執行 run 方法,它的 run 方法會周期調用所有容器的 backgroundProcess 方法,因為所有容器都會繼承 ContainerBase 類,是以所有容器都能夠在 backgroundProcess 方法中定義周期執行的事件。
Wrapper 代表一個 Servlet,它負責管理一個 Servlet,包括的 Servlet 的裝載、初始化、執行以及資源回收。Wrapper 是最底層的容器,它沒有子容器了,是以調用它的 addChild 将會報錯。
Wrapper 的實作類是 StandardWrapper,StandardWrapper 還實作了擁有一個 Servlet 初始化資訊的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各種資訊打交道。
Tomcat 還有其它重要的元件,如安全元件 security、logger 日志元件、session、mbeans、naming 等其它元件。這些元件共同為 Connector 和 Container 提供必要的服務。
Tomcat 中元件的生命周期是通過 Lifecycle 接口來控制的,元件隻要繼承這個接口并實作其中的方法就可以統一被擁有它的元件控制了,這樣一層一層的直到一個最進階的元件就可以控制 Tomcat 中所有元件的生命周期,這個最高的元件就是 Server,而控制 Server 的是 Startup,也就是您啟動和關閉 Tomcat。
除了控制生命周期的 Start 和 Stop 方法外還有一個監聽機制,在生命周期開始和結束的時候做一些額外的操作。這個機制在其它的架構中也被使用,Lifecycle 接口的方法的實作都在其它元件中,就像前面中說的,元件的生命周期由包含它的父元件控制,是以它的 Start 方法自然就是調用它下面的元件的 Start 方法,Stop 方法也是一樣。如在 Server 中 Start 方法就會調用 Service 元件的 Start 方法。
監聽的代碼會包圍 Service 元件的啟動和關閉過程,就是簡單的循環啟動所有 Service 元件的 Start 方法,但是所有 Service 必須要實作 Lifecycle 接口,這樣做會更加靈活。
門面設計模式在 Tomcat 中有多處使用,在 Request 和 Response 對象封裝中、StandardWrapper 到 ServletConfig 封裝中、ApplicationContext 到 ServletContext 封裝中等都用到了這種設計模式。
門面設計模式的原理
這麼多場合都用到了這種設計模式,那這種設計模式究竟能有什麼作用呢?顧名思義,就是将一個東西封裝成一個門面好與人家更容易進行交流,就像一個國家的外交部一樣。這種設計模式主要用在一個大的系統中有多個子系統組成時,這多個子系統肯定要涉及到互相通信,但是每個子系統又不能将自己的内部資料過多的暴露給其它系統,不然就沒有必要劃分子系統了。每個子系統都會設計一個門面,把别的系統感興趣的資料封裝起來,通過這個門面來進行通路。這就是門面設計模式存在的意義。
這種設計模式也是常用的設計方法通常也叫釋出 - 訂閱模式,也就是事件監聽機制,通常在某個事件發生的前後會觸發一些操作。
控制元件生命周期的 Lifecycle 就是這種模式的展現,還有對 Servlet 執行個體的建立、Session 的管理、Container 等都是同樣的原理。LifecycleListener 代表的是抽象觀察者,它定義一個 lifecycleEvent 方法,這個方法就是當主題變化時要執行的方法。 ServerLifecycleListener 代表的是具體的觀察者,它實作了 LifecycleListener 接口的方法,就是這個具體的觀察者具體的實作方式。Lifecycle 接口代表的是抽象主題,它定義了管理觀察者的方法和它要所做的其它方法。而 StandardServer 代表的是具體主題,它實作了抽象主題的所有方法。這裡 Tomcat 對觀察者做了擴充,增加了另外兩個類:LifecycleSupport、LifecycleEvent,它們作為輔助類擴充了觀察者的功能。LifecycleEvent 使得可以定義事件類别,不同的事件可差別處理,更加靈活。LifecycleSupport 類代理了主題對多觀察者的管理,将這個管理抽出來統一實作,以後如果修改隻要修改 LifecycleSupport 類就可以了,不需要去修改所有具體主題,因為所有具體主題的對觀察者的操作都被代理給 LifecycleSupport 類了。
LifecycleSupport 調用觀察者的方法代碼如下:
觀察者模式的原理
觀察者模式原理也很簡單,就是你在做事的時候旁邊總有一個人在盯着你,當你做的事情是它感興趣的時候,它就會跟着做另外一些事情。但是盯着你的人必須要到你那去登記,不然你無法通知它。觀察者模式通常包含下面這幾個角色:
Subject 就是抽象主題:它負責管理所有觀察者的引用,同時定義主要的事件操作。
ConcreteSubject 具體主題:它實作了抽象主題的所有定義的接口,當自己發生變化時,會通知所有觀察者。
Observer 觀察者:監聽主題發生變化相應的操作接口。
前面把 Tomcat 中兩個核心元件 Connector 和 Container,比作一對夫妻。男的将接受過來的請求以指令的方式交給女主人。對應到 Connector 和 Container,Connector 也是通過指令模式調用 Container 的。
Tomcat 中指令模式在 Connector 和 Container 元件之間有展現,Tomcat 作為一個應用伺服器,無疑會接受到很多請求,如何配置設定和執行這些請求是必須的功能。Connector 作為抽象請求者,HttpConnector 作為具體請求者。HttpProcessor 作為指令。Container 作為指令的抽象接受者,ContainerBase 作為具體的接受者。用戶端就是應用伺服器 Server 元件了。Server 首先建立指令請求者 HttpConnector 對象,然後建立指令 HttpProcessor 指令對象。再把指令對象交給指令接受者 ContainerBase 容器來處理指令。指令的最終是被 Tomcat 的 Container 執行的。指令可以以隊列的方式進來,Container 也可以以不同的方式來處理請求,如 HTTP1.0 協定和 HTTP1.1 的處理方式就會不同。
指令模式的原理
指令模式主要作用就是封裝指令,把發出指令的責任和執行指令的責任分開。也是一種功能的分工。不同的子產品可以對同一個指令做出不同解釋。
下面是指令模式通常包含下面幾個角色:
Client:建立一個指令,并決定接受者
Command 指令:指令接口定義一個抽象方法
ConcreteCommand:具體指令,負責調用接受者的相應操作
Invoker 請求者:負責調用指令對象執行請求
Receiver 接受者:負責具體實施和執行一次請求
Tomcat 中一個最容易發現的設計模式就是責任鍊模式,這個設計模式也是 Tomcat 中 Container 設計的基礎,整個容器的就是通過一個鍊連接配接在一起,這個鍊一直将請求正确的傳遞給最終處理請求的那個 Servlet。
在 tomcat 中這種設計模式幾乎被完整的使用,tomcat 的容器設定就是責任鍊模式,從 Engine 到 Host 再到 Context 一直到 Wrapper 都是通過一個鍊傳遞請求。對應的責任鍊模式的角色,Container 扮演抽象處理者角色,具體處理者由 StandardEngine 等子容器扮演。與标準的責任鍊不同的是,這裡引入了 Pipeline 和 Valve 接口。他們有什麼作用呢?實際上 Pipeline 和 Valve 是擴充了這個鍊的功能,使得在鍊往下傳遞過程中,能夠接受外界的幹預。Pipeline 就是連接配接每個子容器的管子,裡面傳遞的 Request 和 Response 對象好比管子裡流的水,而 Valve 就是這個管子上開的一個個小口子,讓你有機會能夠接觸到裡面的水,做一些額外的事情。為了防止水被引出來而不能流到下一個容器中,每一段管子最後總有一個節點保證它一定能流到下一個子容器,是以每個容器都有一個 StandardXXXValve。
責任鍊模式的原理
責任鍊模式,就是很多對象有每個對象對其下家的引用而連接配接起來形成一條鍊,請求在這條鍊上傳遞,直到鍊上的某個對象處理此請求,或者每個對象都可以處理請求,并傳給下一家,直到最終鍊上每個對象都處理完。這樣可以不影響用戶端而能夠在鍊上增加任意的處理節點。
通常責任鍊模式包含下面幾個角色:
Handler(抽象處理者):定義一個處理請求的接口
ConcreteHandler(具體處理者):處理請求的具體類,或者傳給下家