架構(gòu)篇:Tomcat?高層組件構(gòu)建一個(gè)商業(yè)帝國
Tomcat 實(shí)現(xiàn)的 2 個(gè)核心功能:
- 處理
Socket
連接,負(fù)責(zé)網(wǎng)絡(luò)字節(jié)流與Request
和Response
對(duì)象的轉(zhuǎn)化。 - 加載并管理
Servlet
,以及處理具體的Request
請(qǐng)求。
startup.sh -> catalina.sh start ->java -jar org.apache.catalina.startup.Bootstrap.main()
Bootstrap、Catalina、Server、Service、 Engine 都承擔(dān)了什么責(zé)任?單獨(dú)寫一篇介紹他們是因?yàn)槟憧梢钥吹竭@些啟動(dòng)類或者組件不處理具體請(qǐng)求,它們的任務(wù)主要是管理,管理下層組件的生命周期,并且給下層組件分配任務(wù),也就是把請(qǐng)求路由到負(fù)責(zé)干活兒的組件。他們就像一個(gè)公司的高層,管理整個(gè)公司的運(yùn)作,將任務(wù)分配給專業(yè)的人。我們?cè)谠O(shè)計(jì)軟件系統(tǒng)中,不可避免的會(huì)遇到需要一些管理作用的組件,就可以學(xué)習(xí)和借鑒 Tomcat 是如何抽象和管理這些組件的。因此我把它們比作 Tomcat 的高層,同時(shí)愿干活的不再 996。Bootstrap
當(dāng)執(zhí)行startup.sh
腳本的時(shí)候,就會(huì)啟動(dòng)一個(gè) JVM 運(yùn)行 Tomcat 的啟動(dòng)類 Bootstrap
的 main
方法。先看下他的成員變量窺探核心功能:public?final?class?Bootstrap?{
????ClassLoader?commonLoader?=?null;
????ClassLoader?catalinaLoader?=?null;
????ClassLoader?sharedLoader?=?null;
}
它的主要任務(wù)就是初始化 Tomcat 定義的類加載器,同時(shí)創(chuàng)建 Catalina 對(duì)象。Bootstrap 就像一個(gè)大神,初始化了類加載器,加載萬物。關(guān)于為何自定義各種類加載器詳情請(qǐng)查看碼哥的 Tomcat 架構(gòu)設(shè)計(jì)解析 類加載器部分。初始化類加載器
WebAppClassLoader
假如我們?cè)?Tomcat 中運(yùn)行了兩個(gè) Web 應(yīng)用程序,兩個(gè) Web 應(yīng)用中有同名的Servlet
,但是功能不同,Tomcat 需要同時(shí)加載和管理這兩個(gè)同名的 Servlet
類,保證它們不會(huì)沖突,因此 Web 應(yīng)用之間的類需要隔離。Tomcat 的解決方案是自定義一個(gè)類加載器 WebAppClassLoader
, 并且給每個(gè) Web 應(yīng)用創(chuàng)建一個(gè)類加載器實(shí)例。我們知道,Context 容器組件對(duì)應(yīng)一個(gè) Web 應(yīng)用,因此,每個(gè) Context
容器負(fù)責(zé)創(chuàng)建和維護(hù)一個(gè) WebAppClassLoader
加載器實(shí)例。這背后的原理是,不同的加載器實(shí)例加載的類被認(rèn)為是不同的類,即使它們的類名相同。Tomcat 的自定義類加載器 WebAppClassLoader
打破了雙親委托機(jī)制,它首先自己嘗試去加載某個(gè)類,如果找不到則通過 ExtClassLoader 加載 JRE 核心類防止黑客攻擊,無法加載再代理給 AppClassLoader 加載器,其目的是優(yōu)先加載 Web 應(yīng)用自己定義的類。具體實(shí)現(xiàn)就是重寫 ClassLoader
的兩個(gè)方法:findClass
和 loadClass
。SharedClassLoader
假如兩個(gè) Web 應(yīng)用都依賴同一個(gè)第三方的 JAR 包,比如Spring
,那 Spring
的 JAR 包被加載到內(nèi)存后,Tomcat
要保證這兩個(gè) Web 應(yīng)用能夠共享,也就是說 Spring
的 JAR 包只被加載一次。SharedClassLoader 就是 Web 應(yīng)用共享的類庫的加載器,專門加載 Web 應(yīng)用共享的類。如果 ?WebAppClassLoader
自己沒有加載到某個(gè)類,就會(huì)委托父加載器 SharedClassLoader
去加載這個(gè)類,SharedClassLoader
會(huì)在指定目錄下加載共享類,之后返回給 WebAppClassLoader
,這樣共享的問題就解決了。CatalinaClassloader
如何隔離 Tomcat 本身的類和 Web 應(yīng)用的類?要共享可以通過父子關(guān)系,要隔離那就需要兄弟關(guān)系了。兄弟關(guān)系就是指兩個(gè)類加載器是平行的,它們可能擁有同一個(gè)父加載器,基于此 Tomcat 又設(shè)計(jì)一個(gè)類加載器CatalinaClassloader
,專門來加載 Tomcat 自身的類。這樣設(shè)計(jì)有個(gè)問題,那 Tomcat 和各 Web 應(yīng)用之間需要共享一些類時(shí)該怎么辦呢?老辦法,還是再增加一個(gè) CommonClassLoader
,作為 CatalinaClassloader
和 ?SharedClassLoader
的父加載器。CommonClassLoader
能加載的類都可以被 ?CatalinaClassLoader
和 SharedClassLoader
使用。Catalina
Tomcat 是一個(gè)公司,Catalina 就好像是一個(gè)創(chuàng)始人。因?yàn)樗?fù)責(zé)組建團(tuán)隊(duì),創(chuàng)建 Server 以及所有子組件。Catalina 的主要任務(wù)就是創(chuàng)建 Server,解析 server.xml 把里面配置的各個(gè)組件創(chuàng)建出來,并調(diào)用每個(gè)組件的init
和 start
方法,將整個(gè) Tomcat 啟動(dòng),這樣整個(gè)公司就在正常運(yùn)作了。我們可以根據(jù) Tomcat 配置文件來直觀感受下:<Server?port="8005"?shutdown="SHUTDOWN">?//?頂層組件,可包含多個(gè)?Service,代表一個(gè)?Tomcat?實(shí)例
??<Service?name="Catalina">??//?頂層組件,包含一個(gè)?Engine?,多個(gè)連接器
????<Connector?port="8080"?protocol="HTTP/1.1"
???????????????connectionTimeout="20000"
???????????????redirectPort="8443"?/>
????
????<Connector?port="8009"?protocol="AJP/1.3"?redirectPort="8443"?/>??//?連接器
?//?容器組件:一個(gè) Engine 處理 Service 所有請(qǐng)求,包含多個(gè) Host
????<Engine?name="Catalina"?defaultHost="localhost">
???//?容器組件:處理指定Host下的客戶端請(qǐng)求,?可包含多個(gè) Context
??????<Host?name="localhost"??appBase="webapps"
????????????unpackWARs="true"?autoDeploy="true">
???//?容器組件:處理特定 Context Web應(yīng)用的所有客戶端請(qǐng)求
???<Context>Context>
??????Host>
????Engine>
??Service>
Server>
作為創(chuàng)始人,Catalina 還需要處理公司的各種異常情況,比如有人搶公章(執(zhí)行了 Ctrl C 關(guān)閉 Tomcat)。Tomcat 要如何清理資源呢?通過向 JVM 注冊(cè)一個(gè)「關(guān)閉鉤子」,具體關(guān)鍵邏輯詳見org.apache.catalina.startup.Catalina#start
源碼:- Server 不存在則解析
server.xml
創(chuàng)建; - 創(chuàng)建失敗則報(bào)錯(cuò);
- 啟動(dòng) Server;
- 創(chuàng)建并注冊(cè)「關(guān)閉鉤子」;
- await 方法監(jiān)聽停止請(qǐng)求。
???/**
?????*?Start?a?new?server?instance.
?????*/
????public?void?start()?{
????????//?如果?Catalina?持有的?Server?為空則解析?server.xml?創(chuàng)建
????????if?(getServer()?==?null)?{
????????????load();
????????}
????????if?(getServer()?==?null)?{
????????????log.fatal("Cannot?start?server.?Server?instance?is?not?configured.");
????????????return;
????????}
????????//?Start?the?new?server
????????try?{
????????????getServer().start();
????????}?catch?(LifecycleException?e)?{
????????????//?省略部分代碼
????????}
????????//?創(chuàng)建鉤子并注冊(cè)
????????if?(useShutdownHook)?{
????????????if?(shutdownHook?==?null)?{
????????????????shutdownHook?=?new?CatalinaShutdownHook();
????????????}
????????????Runtime.getRuntime().addShutdownHook(shutdownHook);
????????}
????????//?監(jiān)聽停止請(qǐng)求,內(nèi)部調(diào)用?Server?的?stop
????????if?(await)?{
????????????await();
????????????stop();
????????}
????}
當(dāng)我們需要在 JVM 關(guān)閉做一些清理工作,比如將緩存數(shù)據(jù)刷到磁盤或者清理一些文件,就可以向 JVM 注冊(cè)一個(gè)「關(guān)閉鉤子」。它其實(shí)就是一個(gè)線程,當(dāng) JVM 停止前嘗試執(zhí)行這個(gè)線程的 run 方法。org.apache.catalina.startup.Catalina.CatalinaShutdownHook????protected?class?CatalinaShutdownHook?extends?Thread?{
????????@Override
????????public?void?run()?{
????????????try?{
????????????????if?(getServer()?!=?null)?{
????????????????????Catalina.this.stop();
????????????????}
????????????}?catch?(Throwable?ex)?{
??????????????//?省略部分代碼....
????????????}
????????}
????}
其實(shí)就是執(zhí)行了 Catalina 的 stop 方法,通過它將整個(gè) Tomcat 停止。Server
Server 組件的職責(zé)就是管理 Service 組件,負(fù)責(zé)調(diào)用持有的Service
的 start
方法。他就像是公司的 CEO,負(fù)責(zé)管理多個(gè)事業(yè)部,每個(gè)事業(yè)部就是一個(gè) Service
。它管理兩個(gè)部門:- Connector 連接器:對(duì)外市場(chǎng)營銷部,推廣吹牛寫 PPT 的。
- Container 容器:研發(fā)部門,沒有性生活的 996 。
org.apache.catalina.core.StandardServer
,Server 繼承 org.apache.catalina.util.LifecycleMBeanBase,所以他的生命周期也被統(tǒng)一管理,Server 的子組件是 Service,所以還需要管理 Service 的生命周期。也就是說在啟動(dòng)和關(guān)閉 Server 的時(shí)候會(huì)分別先調(diào)用 Service
的 啟動(dòng)和停止方法。這就是設(shè)計(jì)思想呀,抽象出生命周期 Lifecycle
接口,體現(xiàn)出接口隔離原則,將生命周期的相關(guān)功能內(nèi)聚。我們接著看 Server 如何管理 Service 的,核心源碼如下org.apache.catalina.core.StandardServer#addService:public?void?addService(Service?service)?{
????????service.setServer(this);
????????synchronized?(servicesLock)?{
????????????//?創(chuàng)建?長(zhǎng)度? 1?的數(shù)組
????????????Service?results[]?=?new?Service[services.length? ?1];
????????????//?將舊的數(shù)據(jù)復(fù)制到新數(shù)組
????????????System.arraycopy(services,?0,?results,?0,?services.length);
????????????results[services.length]?=?service;
????????????services?=?results;
????????????//?啟動(dòng)?Service?組件
????????????if?(getState().isAvailable())?{
????????????????try?{
????????????????????service.start();
????????????????}?catch?(LifecycleException?e)?{
????????????????????//?Ignore
????????????????}
????????????}
????????????//?發(fā)送事件
????????????support.firePropertyChange("service",?null,?service);
????????}
????}
在添加 Service 過程中動(dòng)態(tài)拓展數(shù)組長(zhǎng)度,為了節(jié)省內(nèi)存。除此之外,Server 組件還有一個(gè)重要的任務(wù)是啟動(dòng)一個(gè) Socket 來監(jiān)聽停止端口,這就是為什么你能通過 shutdown 命令來關(guān)閉 Tomcat。不知道你留意到?jīng)]有,上面 Caralina 的啟動(dòng)方法的最后一行代碼就是調(diào)用了 Server 的 await 方法。在 await 方法里會(huì)創(chuàng)建一個(gè) Socket 監(jiān)聽 8005 端口,并在一個(gè)死循環(huán)里接收 Socket 上的連接請(qǐng)求,如果有新的連接到來就建立連接,然后從 Socket 中讀取數(shù)據(jù);如果讀到的數(shù)據(jù)是停止命令“SHUTDOWN”,就退出循環(huán),進(jìn)入 stop 流程。Service
他的職責(zé)就是管理Connector 連接器
和 頂層容器 Engine
,會(huì)分別調(diào)用他們的 start 方法。至此,整個(gè) Tomcat 就算啟動(dòng)完成了。Service 就是事業(yè)部的話事人,管理兩個(gè)職能部門對(duì)外推廣部(連接器),對(duì)內(nèi)研發(fā)部(容器)。Service 組件的實(shí)現(xiàn)類是org.apache.catalina.core.StandardService
,直接看關(guān)鍵的成員變量。public?class?StandardService?extends?LifecycleMBeanBase?implements?Service?{
????//?名字
????private?String?name?=?null;
????
????//?所屬的?Server?實(shí)例
????private?Server?server?=?null;
?
????//?連接器數(shù)組
????protected?Connector?connectors[]?=?new?Connector[0];
????private?final?Object?connectorsLock?=?new?Object();
?
????//?對(duì)應(yīng)的?Engine?容器
????private?Engine?engine?=?null;
????
????//?映射器及其監(jiān)聽器
????protected?final?Mapper?mapper?=?new?Mapper();
????protected?final?MapperListener?mapperListener?=?new?MapperListener(this);
}
繼承 LifecycleMBeanBase
而 ?LifecycleMBeanBase 又繼承 LifecycleBase
,這里實(shí)際上是模板方法模式的運(yùn)用,org.apache.catalina.util.LifecycleBase#init
,org.apache.catalina.util.LifecycleBase#start
,org.apache.catalina.util.LifecycleBase#stop
分別是對(duì)應(yīng)的模板方法,內(nèi)部定義了整個(gè)算法流程,子類去實(shí)現(xiàn)自己內(nèi)部具體變化部分,將變與不變抽象出來實(shí)現(xiàn)開閉原則設(shè)計(jì)思路。那為什么還有一個(gè) MapperListener?這是因?yàn)?Tomcat 支持熱部署,當(dāng) Web 應(yīng)用的部署發(fā)生變化時(shí),Mapper 中的映射信息也要跟著變化,MapperListener 就是一個(gè)監(jiān)聽器,它監(jiān)聽容器的變化,并把信息更新到 Mapper 中,這是典型的觀察者模式。作為“管理”角色的組件,最重要的是維護(hù)其他組件的生命周期。此外在啟動(dòng)各種組件時(shí),要注意它們的依賴關(guān)系,也就是說,要注意啟動(dòng)的順序。我們來看看 Service 啟動(dòng)方法:protected?void?startInternal()?throws?LifecycleException?{
?
????//1.?觸發(fā)啟動(dòng)監(jiān)聽器
????setState(LifecycleState.STARTING);
?
????//2.?先啟動(dòng)?Engine,Engine?會(huì)啟動(dòng)它子容器
????if?(engine?!=?null)?{
????????synchronized?(engine)?{
????????????engine.start();
????????}
????}
????
????//3.?再啟動(dòng)?Mapper?監(jiān)聽器
????mapperListener.start();
?
????//4.?最后啟動(dòng)連接器,連接器會(huì)啟動(dòng)它子組件,比如?Endpoint
????synchronized?(connectorsLock)?{
????????for?(Connector?connector:?connectors)?{
????????????if?(connector.getState()?!=?LifecycleState.FAILED)?{
????????????????connector.start();
????????????}
????????}
????}
}
這里啟動(dòng)順序也很講究,Service 先啟動(dòng)了 Engine 組件,再啟動(dòng) Mapper 監(jiān)聽器,最后才是啟動(dòng)連接器。這很好理解,因?yàn)閮?nèi)層組件啟動(dòng)好了才能對(duì)外提供服務(wù),產(chǎn)品沒做出來,市場(chǎng)部也不能瞎忽悠,研發(fā)好了才能啟動(dòng)外層的連接器組件。而 Mapper 也依賴容器組件,容器組件啟動(dòng)好了才能監(jiān)聽它們的變化,因此 Mapper 和 MapperListener 在容器組件之后啟動(dòng)。組件停止的順序跟啟動(dòng)順序正好相反的,也是基于它們的依賴關(guān)系。Engine
他就是一個(gè)研發(fā)部的頭頭,是最頂層的容器組件。繼承Container
,所有的容器組件都繼承 Container,這里實(shí)際上運(yùn)用了組合模式統(tǒng)一管理。他的實(shí)現(xiàn)類是 org.apache.catalina.core.StandardEngine
,繼承 ContainerBase
。public?class?StandardEngine?extends?ContainerBase?implements?Engine?{
}
他的子容器是 Host
,所以持有 Host 容器數(shù)組,這個(gè)屬性每個(gè)容器都會(huì)存在,所以放在抽象類中protected?final?HashMap?children?=?new?HashMap<>();
ContainerBase 用 HashMap 保存了它的子容器,并且 ContainerBase 還實(shí)現(xiàn)了子容器的“增刪改查”,甚至連子組件的啟動(dòng)和停止都提供了默認(rèn)實(shí)現(xiàn),比如 ContainerBase 會(huì)用專門的線程池來啟動(dòng)子容器。org.apache.catalina.core.ContainerBase#startInternal//?Start?our?child?containers,?if?any
Container?children[]?=?findChildren();
List>?results?=?new?ArrayList<>();
for?(Container?child?:?children)?{
??results.add(startStopExecutor.submit(new?StartChild(child)));
}
Engine 在啟動(dòng) Host 子容器時(shí)就直接重用了這個(gè)方法。容器組件最重要的功能是處理請(qǐng)求,而 Engine 容器對(duì)請(qǐng)求的“處理”,其實(shí)就是把請(qǐng)求轉(zhuǎn)發(fā)給某一個(gè) Host 子容器來處理,具體是通過 Valve 來實(shí)現(xiàn)的。每一個(gè)容器組件都有一個(gè) Pipeline,而 Pipeline 中有一個(gè)基礎(chǔ)閥(Basic Valve),透過構(gòu)造方法創(chuàng)建 Pipeline。public?StandardEngine()?{
????super();
????pipeline.setBasic(new?StandardEngineValve());
????//?省略部分代碼
}
Engine 容器的基礎(chǔ)閥定義如下:final?class?StandardEngineValve?extends?ValveBase?{
?
????public?final?void?invoke(Request?request,?Response?response)
??????throws?IOException,?ServletException?{
??
??????//?拿到請(qǐng)求中的?Host?容器
??????Host?host?=?request.getHost();
??????if?(host?==?null)?{
??????????return;
??????}
??
??????//?調(diào)用?Host?容器中的?Pipeline?中的第一個(gè)?Valve
??????host.getPipeline().getFirst().invoke(request,?response);
??}
??
}
這個(gè)基礎(chǔ)閥實(shí)現(xiàn)非常簡(jiǎn)單,就是把請(qǐng)求轉(zhuǎn)發(fā)到 Host 容器。從代碼中可以看到,處理請(qǐng)求的 Host 容器對(duì)象是從請(qǐng)求中拿到的,請(qǐng)求對(duì)象中怎么會(huì)有 Host 容器呢?這是因?yàn)檎?qǐng)求到達(dá) Engine 容器中之前,Mapper 組件已經(jīng)對(duì)請(qǐng)求進(jìn)行了路由處理,Mapper 組件通過請(qǐng)求的 URL 定位了相應(yīng)的容器,并且把容器對(duì)象保存到了請(qǐng)求對(duì)象中。