1. 程式人生 > >看透SpringMVC原始碼分析與實踐(二)

看透SpringMVC原始碼分析與實踐(二)

一、Tomcat的頂層結構及啟動過程

1.Tomcat的頂層結構

       Tomcat中最頂層的容器叫Server,代表整個伺服器,Server至少包含一個Service用於具體的服務。Service主要包含兩部分,Connector和Container。Connector用於處理連線相關的事情,並提供Socket與request、response的轉換,Container用於封裝和管理Servlet,以及具體處理request的請求。一個Tomcat中只有一個Server,一個Server可以包含多個Service,一個Service只有一個Container,但可以包含多個Connector(因為一個服務可以有多個連線,如同時提供http和https的連線,也可以提供相同協議但不同埠的連線)。結構如下圖所示

       Tomcat裡的Server由org.apache.catalina.startup.Catalina來管理,Catalina是整個Tomcat的管理類,它裡面的三個方法load、start、stop分別來管理整個伺服器的生命週期,load方法用於根據conf/server.xml檔案建立Server並呼叫Server的init方法進行初始化,start方法用於啟動伺服器,stop方法用於停止伺服器。

不過Tomcat的入口main方法並不在Catalina類裡,而是在org.apache.catalina.startup.Bootstrap中。

2.Bootstrap的啟動過程

       Bootstrap是Tomcat的入口,正常情況下啟動Tomcat就是呼叫的Bootstrap的main方法。

       Bootstrap的main方法非常簡單,只有兩部分內容:首先新建了Bootstrap,並執行init方法初始化;然後處理main方法傳入的命令,如果args引數為空,預設執行start。

       在init方法裡初始化了ClassLoader,並用ClassLoader建立了Catalina例項,然後賦給catalinaDaemon變數,後面對命令的操作都要使用catalinaDaemen來具體執行。

       對start命令的處理呼叫了三個方法:setAwait(true)、load(args)和start()。這三個方法內部都呼叫了Catalina的相應方法進行具體執行,只不過是用反射來呼叫的。

3.Catalina的啟動過程

Catalina的啟動主要是呼叫setAwait、load和start方法來完成的。setAwait方法用於設定Server啟動完成後是否進入等待狀態的標誌,如果為true則進入,否則不進入;load方法用於載入配置檔案,建立並初始化Server;start方法用於啟動伺服器。

4.Server的啟動過程

         server介面中提供addService(Service service)、removeService(Service service)來新增和刪除Service,Service的init方法和start方法分別迴圈呼叫了每個service的init方法和start方法來啟動所有service。

         Server的預設實現是org.apache.catalina.core.StandardServer,StandardSever繼承自LifecycleMBeanBase,LifecycleMBeanBase又繼承自LifecycleBase,init和start方法就定義在LifecycleBase中,LifecycleBase裡的init方法和start方法又呼叫initInternal方法和startInternal方法,這兩個方法都是模板方法,由子類具體實現,所以呼叫StandardServer的init和start方法時會執行StandardServer自己的initInternal和startInternal方法,這就是tomcat生命週期的管理方式。StandardServer中的initInternal和startInternal方法分別迴圈呼叫了每一個service的start和init方法。

5.Service的啟動過程

        Service的預設實現是org.apache.catalina.core.StandardService,StandardService也繼承自LifeCycleMBeanBase類,所以init和start方法最終也會呼叫initInternal和startInternal方法。

       StandardService中的initInternal和startInternal方法主要呼叫container、executors、mapperListener、connectors的init和start方法。這裡的container和connectors前面已經介紹過了。mapperListener是Mapper的監聽器,可以監聽container容器的變化,executors是在connectors中管理執行緒的執行緒池,在server.xml配置檔案中有參考用法。

       這樣Connector就配置了一個叫tomcatThreadPool的執行緒池,最多可以同時啟動150個執行緒,至少要有4個可用的執行緒。

       整個Tomcat的啟動流程如下圖所示

二、Tomcat的生命週期管理

1.Lifecycle介面

Tomcat通過org.apache.catalina.Lifecycle介面統一管理生命週期,所有有生命週期的元件都要事先Lifecycle介面。Lifecycle介面一共做了4件事:

  • 定義了13個String型別常量,用於LifecycleEvent事件的type屬性中,作用是區分元件發出的LifecycleEvent事件時的狀態(如初始化前、啟動前、啟動中等)。這種設計方式可以讓多種狀態都發送同一種類型的事件(LifecycleEvent)然後用其中的一個屬性來區分狀態而不用定義多種事件。
  • 定義了三個管理監聽器的方法addLifecycleListener、findLifecycleListeners和removeLifecycleListener,分別用來新增、查詢和刪除LifecycleListener型別的監聽器。
  • 定義了4個生命週期的方法:init、start、stop和destroy,用於執行生命週期的各個階段的操作。
  • 定義了獲取當前狀態的兩個方法getState和getStateName,用來獲取當前的狀態,getState的返回值LifecycleState是列舉型別,裡邊列舉了生命週期的各個節點,getStateName方法返回String型別的狀態的名字,主要用於JMX中。

2.LifecycleBase

Lifecycle的預設實現是org.apache.catalina.util.LifecycleBase,所有實現了生命週期的元件都直接或間接繼承自LifecycleBase,LifecycleBase為Lifecycle裡的介面方法提供了預設實現:監聽器管理是專門使用了一個LifecycleSupport類來完成的,LifecycleSupport中定義了一個LifecycleListener陣列型別的屬性來儲存所有的監聽器,然後定義了新增、刪除、查詢和執行監聽器的方法;生命週期方法中設定了相應的狀態並呼叫了相應的模板方法,ini、start、stop和destroy所對應的模板方法分別是initInternal、startInternal、stopInternal和destroyInternal方法,這四個方法由子類具體實現,所以對於子類來說,執行生命週期處理的方法就是initInternal、startInternal、stopInternal和destroyInternl。

三、Container分析

1.ContainerBase的結構

Container是Tomcat中容器的介面,通常使用的Servlet就封裝在其子介面Wrapper中。

Container一共有四個子介面Engine、Host、Context、Wrapper和一個預設實現類ContainerBase,每個子介面都是一個容器,這四個子容器都有一個對應的StandardXXX實現類,並且這些實現類都繼承ContainerBase類。另外Container還繼承Lifecycle介面,而且ContainerBase間接繼承了LifecycleBase,所以Engine、Host、Context、Wrapper四個子容器都符合前面講過的Tomcat生命週期管理模式。結構圖如下所示:

 

2.Container的四個子容器

Container的子容器Engine、Host、Context、Wrapper是逐層包含的關係,其中Engine是最頂層,每個Service最多隻能有一個Engine,Engine裡面可以有多個Host,每個Host下可以有多個Context,每個Context下可以有多個Wrapper,它們的關係如下圖所示:

四個容器的作用分別是:

Engine:引擎,用來管理多個站點,一個Service最多隻能有一個Engine。

Host:代表一個站點,也可以叫虛擬主機,通過配置Host就可以新增站點。

Context:代表一個應用程式,對應著平時開發的一套程式,或者一個WEB-INF目錄以及下面的web.xml檔案。

Wrapper:每個Wrapper封裝著一個Servlet。

Context和Host的區別是Context表示一個應用。例如,預設配置下webapps下的每個目錄都是一個應用,其中ROOT目錄中存放著主應用,其他目錄存放著別的子應用,而整個webapps是一個站點。假如www.excelib.com域名對應著webapps目錄所代表的的站點,其中的ROOT目錄裡的應用就是主應用,訪問時直接使用域名就可以,而webapps/test目錄存放的是test子應用,訪問時需要用www.excelib.com/test,每一個應用對應一個Context,所有webapps下的應用都屬於www.excelib.com站點,而blog.excelib.com則是另外一個站點。屬於另外一個Host。

3.Container的啟動

Container的啟動是通過init和start方法來完成的,這兩個方法會在Tomcat啟動時被Service呼叫。Container也是按照Tomcat的生命週期來管理的,init和start方法也會呼叫initInternal和startInternal方法來具體處理,不過Container和Tomcat整體結構啟動的過程稍微有點不一樣,主要有三點區別:

  • Container的四個子容器有一個共同的父類ContainerBase,這裡定義了Container容器的initInternal和startInternal方法通用處理內容,具體容器還可以新增自己的內容;
  • 除了最頂層容器的init是被Service呼叫的,子容器的init方法並不是在容器中逐層迴圈呼叫的,而是在執行start方法的時候通過狀態判斷還沒有初始化才呼叫;
  • start方法除了在父容器的startInternal方法中呼叫,還會在父容器的新增子容器的addChild方法中呼叫,這主要是因為Context和Wrapper是動態新增的,我們在站點目錄下放一個應用的資料夾或者war包就可以新增一個Context,在web.xml檔案中配置一個Servlet就可以新增一個Wrapper,所以Context和Wrapper是在容器啟動的過程中才動態查找出來新增到相應的父容器中。

 

四、Pipeline-Value管道

Container處理請求是使用Pipeline-Value管道來處理的。

1.Pipeline-Value處理模式

Pipeline-Value是責任鏈模式,責任鏈模式是指在一個請求處理的過程中有多個處理這一次對請求進行處理,每個處理者負責做自己相應的處理,處理完成後將處理後的請求返回,再讓下一個處理者繼續處理。

Pipeline-Value的管道模型和普通的責任鏈模式有些不同,主要區別有兩點:

  • 每個Pipeline都有特定的Value,而且是在管道的最後一個執行,這個Value叫BaseValue,BaseValue是不可刪除的:
  • 在上層容器的管道的BaseValue中會呼叫下層容器的管道。

四個容器的BaseValue分別是StandardEngineValue、StandardHostValue、StandardContextValue和StandardWrapperValue,整個處理的流程如下圖所示:

在Engine的管道中依次執行Engine的各個Value,最後執行StandEngineValue用於呼叫Host的管道,然後執行Host的Value,這樣依次類推最後執行Wrapper管道中的StandardWrapperValue。

在Filter中用到的FilterChain其實就是這種模式,FilterChain相當於Pipeline,每個Filter都相當於一個Value,Servlet相當於最後的BaseValue。

五、Connector分析

Connector用於接收請求並將請求封裝成Request和Response來具體處理,最底層是使用Socket來進行連線的,Request和Response是按照HTTP協議來封裝的,所以Connector同時實現了TCP/IP協議和HTTP協議,Request和Response封裝完之後交給Container進行處理,Container就是Servlet的容器,Container處理完之後返回給Connector,最後Connector使用Socket將處理結果返回給客戶端。

1.Connector的結構

Connector中具體使用ProtocolHandler來處理請求的,不同的ProtocolHandler代表不同的連線型別,比如,HttpllProtocol使用的是普通Socket來連線的,HttpllNioProtocol使用的是NioSocket來連線的。

ProtocolHandler裡面有三個非常重要的元件:Endpoint、Processor和Adapter。Endpoint用於處理底層Socket的網路連線,Processor用於將Endpoint接收到的Socket封裝成Request,Adapter用於將封裝好的Request交給Container進行具體處理。也就是說Endpoint用來實現TCP/IP協議,Processor用來實現HTTP協議,Adapter將請求適配到Servlet容器進行具體處理。

Endpoint的抽象實現AbstractEndPoint裡面定義的Acceptor和AsyncTimeout兩個內部類和一個Handler介面。Acceptor用於監聽請求,AsyncTimeout用於檢查非同步request的超時,Handler用於處理接收到的Socket,在內部呼叫了Processor進行處理。

Connector的結構如下圖所示: