Tomcat 第三篇:總體架構(gòu)設(shè)計

Tomcat 總體架構(gòu)設(shè)計
在開始這篇文章的時候,忽然發(fā)現(xiàn)上一篇內(nèi)容的題目不是很合適,不應(yīng)該叫啟動流程,更確切的應(yīng)該是叫啟動腳本。
在最開始,先介紹下 Tomcat 的總體設(shè)計,先有一個大概的印象,對 Tomcat 不至于那么陌生。
先介紹下 Tomcat 的一些基礎(chǔ)組件(以下內(nèi)容來自劉光瑞老師的「tomcat 架構(gòu)解析」):
| 組件名稱 | 介紹 |
|---|---|
| Server | 這個其實就是 Servlet 容器,一個 Tomcat 中只能有一個 Server |
| Service | Service 表示一個或多個 Connector 的集合,這些 Connector 共享同一個 Container 來處理其請求。在同一個 Tomcat 實例內(nèi)可以包含任意多個 Service 實例,它們彼此獨立 |
| Connector | 這個是 Tomcat 的連接器,用于監(jiān)聽并轉(zhuǎn)化 Servlet 的請求,同時將讀取的 Socket 請求轉(zhuǎn)交由 Container 處理,并且支持不同的協(xié)議以及不同的 I/O 方式。 |
| Container | 表示能夠執(zhí)行客戶端請求并返回響應(yīng)的一類對象。在 Tomcat 中存在不同級別的容器 Engine, Host, Context, Wrapper |
| Engine | 表示整個 Servlet 引擎。在 Tomcat 中, Engine 為最高層級的容器對象。盡管 Engine 不是直接處理請求的容器,卻是獲取目標容器的入口。 |
| Host | Host 作為一類容器,表示 Servlet 引擎(即 Engine 中的虛擬機,與一個服務(wù)器的網(wǎng)絡(luò)名有關(guān),如域名等??蛻舳丝梢允褂眠@個網(wǎng)絡(luò)名連接服務(wù)器,這個名稱必須要在 DNS 服務(wù)器上注冊 |
| Context | Context 作為一類容器,用于表示 Servletcontext ,在 Servlet 規(guī)范中,一個 Servletcontext 即表示一個獨立的 Web 應(yīng)用。 |
| Wapper | Wapper 作為一類容器,用于表示 Web 應(yīng)用中定義的 Servlet。 |
| Executor | 表示 Tomcat 組件間可以共享的線程池。 |
這個表格大致看一下,了解下 Tomcat 的一些組件,無需記憶,先有個印象,后面的內(nèi)容會慢慢的聊到每一個組件。
作為一款 Web 容器, Tomcat 大體上實現(xiàn)了兩個核心功能:
處理 Socket連接,負責(zé)網(wǎng)絡(luò)字節(jié)流與Request和Response對象的轉(zhuǎn)化。加載并管理 Servlet,以及處理具體的Request請求。
「所以 Tomcat 設(shè)計了兩個核心組件連接器(Connector)和容器(Container)。連接器負責(zé)對外交流,容器負責(zé)內(nèi)部處理。」
Tomcat 為了實現(xiàn)多種支持多種 I/O 模型和應(yīng)用層協(xié)議,將 連接器(Connector) 和 容器(Container) 進行了分離,

在 Tomcat 中,總會有一個 Service ,一個 Service 包含著多個 Connector 和一個 Container(或者說是它的頂層容器 Engine ) 。
而一個 Container 可以包含多個 Host ,一個 Host 內(nèi)部又可以有多個 Context ,一個 Context 內(nèi)部又會有多個 Servlet 。
Tomcat 的設(shè)計感覺上和 「俄羅斯套娃」 很像,一層包著一層。
Tomcat 啟動初始化流程
先放一張啟動流程圖,然后我們再從源碼的角度來驗證這個啟動流程。

從上面這張圖可以清晰的了解到 Tomcat 的初始化的核心過程如下:
「BootStrap -> Catalina -> Server -> Service -> Excutor -> Container (Engine -> Host -> Context -> Container)」
1 BootStrap.main()
首先,整個程序是從 BootStrap 開始啟動的,執(zhí)行的是 BootStrap.main() :
public?static?void?main(String?args[])?{
????synchronized?(daemonLock)?{
????????if?(daemon?==?null)?{
????????????//?Don't?set?daemon?until?init()?has?completed
????????????Bootstrap?bootstrap?=?new?Bootstrap();
????????????try?{
????????????????bootstrap.init();
????????????}?catch?(Throwable?t)?{
????????????????handleThrowable(t);
????????????????t.printStackTrace();
????????????????return;
????????????}
????????????daemon?=?bootstrap;
????????}?else?{
????????????//?When?running?as?a?service?the?call?to?stop?will?be?on?a?new
????????????//?thread?so?make?sure?the?correct?class?loader?is?used?to
????????????//?prevent?a?range?of?class?not?found?exceptions.
????????????Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
????????}
????}
????try?{
????????String?command?=?"start";
????????if?(args.length?>?0)?{
????????????command?=?args[args.length?-?1];
????????}
????????if?(command.equals("startd"))?{
????????????args[args.length?-?1]?=?"start";
????????????daemon.load(args);
????????????daemon.start();
????????}?else?if?(command.equals("stopd"))?{
????????????args[args.length?-?1]?=?"stop";
????????????daemon.stop();
????????}?else?if?(command.equals("start"))?{
????????????daemon.setAwait(true);
????????????daemon.load(args);
????????????daemon.start();
????????????if?(null?==?daemon.getServer())?{
????????????????System.exit(1);
????????????}
????????}?else?if?(command.equals("stop"))?{
????????????daemon.stopServer(args);
????????}?else?if?(command.equals("configtest"))?{
????????????daemon.load(args);
????????????if?(null?==?daemon.getServer())?{
????????????????System.exit(1);
????????????}
????????????System.exit(0);
????????}?else?{
????????????log.warn("Bootstrap:?command?\""?+?command?+?"\"?does?not?exist.");
????????}
????}?catch?(Throwable?t)?{
????????//?Unwrap?the?Exception?for?clearer?error?reporting
????????if?(t?instanceof?InvocationTargetException?&&
????????????????t.getCause()?!=?null)?{
????????????t?=?t.getCause();
????????}
????????handleThrowable(t);
????????t.printStackTrace();
????????System.exit(1);
????}
}
在最開始,執(zhí)行的是一段 synchronized 的同步代碼,這里執(zhí)行代碼 bootstrap.init() 初始化了 bootstrap 對象,具體做了什么跟進去看下:
public?void?init()?throws?Exception?{
????//?初始化類加載器
????initClassLoaders();
????//?設(shè)置線程類加載器,將容器的加載器傳入
????Thread.currentThread().setContextClassLoader(catalinaLoader);
????//?加載安全類
????SecurityClassLoad.securityClassLoad(catalinaLoader);
????//?初始化日志
????//?Load?our?startup?class?and?call?its?process()?method
????if?(log.isDebugEnabled())
????????log.debug("Loading?startup?class");
????//?注意這里,通過反射加載了?Catalina
????Class>?startupClass?=?catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
????Object?startupInstance?=?startupClass.getConstructor().newInstance();
????//?Set?the?shared?extensions?class?loader
????if?(log.isDebugEnabled())
????????log.debug("Setting?startup?class?properties");
????//?調(diào)用了上面加載的?Catalina?中的?setParentClassLoader?方法
????String?methodName?=?"setParentClassLoader";
????Class>?paramTypes[]?=?new?Class[1];
????//?將類加載器賦值成一個參數(shù)
????paramTypes[0]?=?Class.forName("java.lang.ClassLoader");
????Object?paramValues[]?=?new?Object[1];
????//?看名字意思是一個共享加載器
????paramValues[0]?=?sharedLoader;
????Method?method?=
????????startupInstance.getClass().getMethod(methodName,?paramTypes);
????//?調(diào)用了?Catalina?內(nèi)的?setParentClassLoader?方法對?Catalina?類內(nèi)的類加載器賦值
????method.invoke(startupInstance,?paramValues);
????catalinaDaemon?=?startupInstance;
}
注釋都加好了,就不多解釋了,我們接著回到之前的 main ,接下來還有一句是 daemon = bootstrap ,就是把我們剛才初始化過的 bootstrap 賦值給了 daemon 這個變量。
接下來是一大段的判斷,主要是用來判斷輸入?yún)?shù),這里面我們關(guān)注的就中間那一小段,當輸入?yún)?shù)為 start 的時候,總共做了兩件大事:
daemon.load(args) daemon.start()
先跟進去看下 daemon.load(args) 都干了點啥:
private?void?load(String[]?arguments)?throws?Exception?{
????//?Call?the?load()?method
????String?methodName?=?"load";
????Object?param[];
????Class>?paramTypes[];
????if?(arguments==null?||?arguments.length==0)?{
????????paramTypes?=?null;
????????param?=?null;
????}?else?{
????????paramTypes?=?new?Class[1];
????????paramTypes[0]?=?arguments.getClass();
????????param?=?new?Object[1];
????????param[0]?=?arguments;
????}
????//?這里的?catalinaDaemon?剛才在?init()?方法里面進行了初始化,所以這里調(diào)用的實際上是?Catalina?的?load?方法
????Method?method?=
????????catalinaDaemon.getClass().getMethod(methodName,?paramTypes);
????if?(log.isDebugEnabled())?{
????????log.debug("Calling?startup?class?"?+?method);
????}
????method.invoke(catalinaDaemon,?param);
}
這里實際上是開啟了整個鏈式調(diào)用,接著往下追,看下 Catalina 里面的 load 方法。
在這里,會有一個方法的重載 load(String args[]) 和 load() ,不過關(guān)系不大,最終都是會調(diào)用到那個無參的方法上的。
public?void?load()?{
????if?(loaded)?{
????????return;
????}
????loaded?=?true;
????long?t1?=?System.nanoTime();
????initDirs();
????//?Before?digester?-?it?may?be?needed
????initNaming();
????//?Create?and?execute?our?Digester
????Digester?digester?=?createStartDigester();
????InputSource?inputSource?=?null;
????InputStream?inputStream?=?null;
????File?file?=?null;
????try?{
????????try?{
????????????file?=?configFile();
????????????inputStream?=?new?FileInputStream(file);
????????????inputSource?=?new?InputSource(file.toURI().toURL().toString());
????????}?catch?(Exception?e)?{
????????????if?(log.isDebugEnabled())?{
????????????????log.debug(sm.getString("catalina.configFail",?file),?e);
????????????}
????????}
????????if?(inputStream?==?null)?{
????????????try?{
????????????????inputStream?=?getClass().getClassLoader()
????????????????????.getResourceAsStream(getConfigFile());
????????????????inputSource?=?new?InputSource
????????????????????(getClass().getClassLoader()
????????????????????????.getResource(getConfigFile()).toString());
????????????}?catch?(Exception?e)?{
????????????????if?(log.isDebugEnabled())?{
????????????????????log.debug(sm.getString("catalina.configFail",
????????????????????????????getConfigFile()),?e);
????????????????}
????????????}
????????}
????????//?This?should?be?included?in?catalina.jar
????????//?Alternative:?don't?bother?with?xml,?just?create?it?manually.
????????if?(inputStream?==?null)?{
????????????try?{
????????????????inputStream?=?getClass().getClassLoader()
????????????????????????.getResourceAsStream("server-embed.xml");
????????????????inputSource?=?new?InputSource
????????????????(getClass().getClassLoader()
????????????????????????.getResource("server-embed.xml").toString());
????????????}?catch?(Exception?e)?{
????????????????if?(log.isDebugEnabled())?{
????????????????????log.debug(sm.getString("catalina.configFail",
????????????????????????????"server-embed.xml"),?e);
????????????????}
????????????}
????????}
????????if?(inputStream?==?null?||?inputSource?==?null)?{
????????????if??(file?==?null)?{
????????????????log.warn(sm.getString("catalina.configFail",
????????????????????????getConfigFile()?+?"]?or?[server-embed.xml]"));
????????????}?else?{
????????????????log.warn(sm.getString("catalina.configFail",
????????????????????????file.getAbsolutePath()));
????????????????if?(file.exists()?&&?!file.canRead())?{
????????????????????log.warn("Permissions?incorrect,?read?permission?is?not?allowed?on?the?file.");
????????????????}
????????????}
????????????return;
????????}
????????try?{
????????????inputSource.setByteStream(inputStream);
????????????digester.push(this);
????????????digester.parse(inputSource);
????????}?catch?(SAXParseException?spe)?{
????????????log.warn("Catalina.start?using?"?+?getConfigFile()?+?":?"?+
????????????????????spe.getMessage());
????????????return;
????????}?catch?(Exception?e)?{
????????????log.warn("Catalina.start?using?"?+?getConfigFile()?+?":?"?,?e);
????????????return;
????????}
????}?finally?{
????????if?(inputStream?!=?null)?{
????????????try?{
????????????????inputStream.close();
????????????}?catch?(IOException?e)?{
????????????????//?Ignore
????????????}
????????}
????}
????getServer().setCatalina(this);
????getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
????getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
????//?前面都是在初始化加載各種配置文件,使用了?Digester
????//?Stream?redirection
????initStreams();
????//?Start?the?new?server
????try?{
????????//?開始調(diào)用的?Server?的初始化方法
????????//?Server?是一個接口,并且繼承了?Lifecycle?,進行生命周期的管理
????????getServer().init();
????}?catch?(LifecycleException?e)?{
????????if?(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))?{
????????????throw?new?java.lang.Error(e);
????????}?else?{
????????????log.error("Catalina.start",?e);
????????}
????}
????long?t2?=?System.nanoTime();
????if(log.isInfoEnabled())?{
????????log.info("Initialization?processed?in?"?+?((t2?-?t1)?/?1000000)?+?"?ms");
????}
}
這一大坨大多數(shù)都是在加載各種配置文件,在這里,會完成對 server.xml 文件的解析。
我們關(guān)心的其實就只有最后那段 try...catch 里面的那一句 getServer().init() ,開始調(diào)用 Server 的初始化方法,這個方法就是開啟一系列容器組件的加載方法。
不過看到最后一句的 log 打印,忽然有印象了,之前在啟動 Tomcat 的時候,在日志的最后部分,都會看到這句打印,而且可以看到的是,這里并不是我之前以為的是使用毫秒數(shù)算出來的,而是取的納秒數(shù)通過除 1000000 計算得出的,難道是這樣算的更準?
getServer().init() 實際上是調(diào)用了 org.apache.catalina.Server 中的 init() 方法,而 org.apache.catalina.Server 則是一個接口,還繼承了 org.apache.catalina.Lifecycle 進行容器生命周期的管理。
而抽象類 org.apache.catalina.util.LifecycleBase 則是實現(xiàn)了 org.apache.catalina.Lifecycle 接口,我們在 org.apache.catalina.Lifecycle 中打開 init() 方法:
public?final?synchronized?void?init()?throws?LifecycleException?{
????if?(!state.equals(LifecycleState.NEW))?{
????????invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
????}
????try?{
????????setStateInternal(LifecycleState.INITIALIZING,?null,?false);
????????initInternal();
????????setStateInternal(LifecycleState.INITIALIZED,?null,?false);
????}?catch?(Throwable?t)?{
????????handleSubClassException(t,?"lifecycleBase.initFail",?toString());
????}
}
這里我們的關(guān)注點是 initInternal() 這個方法,從名字上來看就是一個初始化方法,點過去一看,結(jié)果是一個抽象方法,實現(xiàn)類如下:

我們到 org.apache.catalina.core.StandardServer 中去看下其中的 initInternal() 方法,在這個方法的最后,看到了循環(huán) Service 并且調(diào)用了 service.init():
for?(Service?service?:?services)?{
????service.init();
}
因為一個 Server 是可以有多個 Service 的,所以這里用了一個循環(huán),接下來就是一個順序的容器初始化的調(diào)用過程:
「StandardServer -> StandardService -> StandardEngine -> Connector」
每個容器都在初始化自身相關(guān)設(shè)置的同時,將子容器初始化。
Tomcat 在啟動初始化的時候,是通過鏈條式的調(diào)用,每初始化一個完成一個組件,就會在組件內(nèi)調(diào)用下一個組件的初始化方法。
同樣的操作在啟動的時候也是一樣的,不知道各位是否還記得我們在 Bootstrap.main() 中還有另一句代碼 daemon.start() ,
public?void?start()?throws?Exception?{
????if?(catalinaDaemon?==?null)?{
????????init();
????}
????Method?method?=?catalinaDaemon.getClass().getMethod("start",?(Class?[])null);
????method.invoke(catalinaDaemon,?(Object?[])null);
}
操作嘛都是一樣的操作,從這段代碼開始,調(diào)用了 Catalina.start() 的方法,然后開啟了另一個鏈式調(diào)用。
我就不多說了,留給各位讀者自己去翻翻看源碼吧。
參考
https://segmentfault.com/a/1190000023475177
https://zhuanlan.zhihu.com/p/75723328

