面試官:來說說Tomcat的啟動過程是什么樣子的
阿粉最近在瘋狂的研究各種用的工具里面的源碼實現(xiàn),之前給大家都專門的去扣了一下 JDK 里面自帶的exe程序,這次阿粉開始更加無聊,直接開始搞Tomcat。
1.Tomcat分析
阿粉知道作為一個 Java 資深開發(fā)人員,對 Tomcat 那是再熟悉不過了,bin目錄、conf目錄、webapps目錄,對這些目錄熟悉的簡直不能再熟悉了。一言不合就是一個shutdown.sh,或者來個shutdown.bat,但是你知道你的啟動startup.bat,和startup.sh他們的啟動過程是什么過程么?接下來我們就開始進入分析吧。
2.Tomcat的整體結構圖
這個整體結構圖可不是大家想的目錄結構圖,目錄結構圖阿粉就不給大家展示了,自己去打開你的 Tomcat,里面就有你想看到目錄結構圖,那么整體結構圖是什么樣子的呢?

給大家解釋一下這個圖的意思,
Server:整個服務器。
Service:具體的服務。
Connector:提供Socket和request,response的連接。
Container:用于封裝和管理Servlet,以及具體的處理請求。
這個圖就把里面的包含關系說的是明明白白了,為什么這么說呢?因為一個Server中可以存在多個服務,也就是多個Service,而一個Service中可以存在多個Connector,但是只能存在一個Container,是不是就非常的清晰了呢?
3.Tomcat的啟動過程
接下來我們就來看看源碼里面的啟動過程吧,Bootstrap類中的啟動過程。
這個類的位置是在tomcat的catalina的包里面,大家看一下主方法也就是所謂的main方法,
??public?static?void?main(String[]?args)?{
????????//對象初始化
????????if?(daemon?==?null)?{
????????????Bootstrap?bootstrap?=?new?Bootstrap();
????????????try?{
????????????????bootstrap.init();
????????????}?catch?(Throwable?var3)?{
????????????????handleThrowable(var3);
????????????????var3.printStackTrace();
????????????????return;
????????????}
????????????daemon?=?bootstrap;
????????}?else?{
????????????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?var4)?{
????????????Throwable?t?=?var4;
????????????if?(var4?instanceof?InvocationTargetException?&&?var4.getCause()?!=?null)?{
????????????????t?=?var4.getCause();
????????????}
????????????handleThrowable(t);
????????????t.printStackTrace();
????????????System.exit(1);
????????}
????}
main方法里面的存在也很簡單,先進行init的操作,然后再執(zhí)行start,也就是說,啟動過程中,首先要進行初始化,然后接下來再進行啟動,最后階段在來個stop,這樣才算完整嘛。
load方法:其實說白了load方法就是根據(jù)server.xml文件創(chuàng)建Server并且調用Server的init方法進行初始化。
start方法:start方法很直白,啟動服務器。
stop方法:stop方法同樣,停止服務器。
在這里的start方法和stop方法調用的分別就是調用了Server內部的start和stop方法,而這三個方法都是按照圖中的層級結構來的,先從Server的load,start,stop,然后Server的start再調用Service的start,而Service的start調用的就是Connector和Container的start方法了,這從外到內的啟動,就可以把Tomcat完整的啟動起來了。
我們在接下來就繼續(xù)從外到內的啟動開始分析一波。
3.1 Catalina啟動過程
上面的啟動入口我們已經成功找到了,那么我們就繼續(xù)來,對象初始化完成后,執(zhí)行了init的方法
??Bootstrap?bootstrap?=?new?Bootstrap();
????????????try?{
????????????????bootstrap.init();
????????????}?catch?(Throwable?var3)?{
就是上面的這個,如果參數(shù)為空了,那么就開始調用start了,那么start方法是什么呢?
?public?void?start()?throws?Exception?{
????????if?(this.catalinaDaemon?==?null)?{
????????????this.init();
????????}
????????Method?method?=?this.catalinaDaemon.getClass().getMethod("start",?(Class[])null);
????????method.invoke(this.catalinaDaemon,?(Object[])null);
????}
上面的start方法就是直接使用invoke的方法映射到了catalinaDaemon,也就是到了Catalina的start的方法上,
而這個Catalina的啟動無非也就是調用了同樣的方法,setAwait方法,load方法,start方法,
setAwait方法:用于設置Server啟動完成時,是否進入等待,如果是true,那就等待,如果不是false,那就不進入等待。
load方法:創(chuàng)建并且初始化Server,
start方法:同樣是啟動服務器
同樣的setAwait方法比較少,阿粉就不給大家看了,無非就是個判斷,而load方法一定得看,
if?(!this.loaded)?{
????????????this.loaded?=?true;
????????????long?t1?=?System.nanoTime();
????????????????????try?{
????????????????????????inputSource.setByteStream((InputStream)inputStream);
????????????????????????digester.push(this);
????????????????????????digester.parse(inputSource);
????????????????????????break?label242;
????????????????????}?catch?(SAXParseException?var28)?{
????????????????????????log.warn("Catalina.start?using?"?+?this.getConfigFile()?+?":?"?+?var28.getMessage());
????????????????????????return;
????????????????????}?catch?(Exception?var29)?{
????????????????????????log.warn("Catalina.start?using?"?+?this.getConfigFile()?+?":?",?var29);
????????????????????}
????????????????}?finally?{
????????????????????if?(inputStream?!=?null)?{
????????????????????????try?{
????????????????????????????((InputStream)inputStream).close();
????????????????????????}?catch?(IOException?var23)?{
????????????????????????????;
????????????????????????}
????????????????????}
????????????????}
????????????????return;
????????????}
?try?{
????//此處同樣調用的Server的init方法,
????????????????this.getServer().init();
????????????}?catch?(LifecycleException?var24)?{
????????????????if?(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))?{
????????????????????throw?new?Error(var24);
????????????????}
????????????????log.error("Catalina.start",?var24);
????????????}
????????????long?t2?=?System.nanoTime();
????????????if?(log.isInfoEnabled())?{
????????????????log.info("Initialization?processed?in?"?+?(t2?-?t1)?/?1000000L?+?"?ms");
????????????}
????????}
而從這里就開始進入下一步了,Server的啟動過程,因為從Catalina里面已經找到了getServer的初始化方法,接下來就是要進行Server的初始化,然后加載,然后啟動的過程了。
3.2 Server的啟動過程
Server是Tomcat里面的接口,而不是類,那么我們就只能去找實現(xiàn)它的子類來于是就找到了StandardServer extends LifecycleMBeanBase implements Server。
阿粉一看有繼承,還有實現(xiàn),那就先看看LifecycleMBeanBase這個被繼承的類,于是再次去看了它,
public?abstract?class?LifecycleMBeanBase?extends?LifecycleBase?implements?JmxEnabled?{
嗯?還有繼承?繼續(xù)往下扒拉,
public?abstract?class?LifecycleBase?implements?Lifecycle?{
終于算是找到了,


阿粉一看這init方法和start方法又調用了initInternal()和startInternal(),找來找去又回去了,而阿粉也從這里知道了,模板方法,是有自己的子類具體實現(xiàn)
于是回到了StandardServer自己的init和start方法,
?protected?void?startInternal()?throws?LifecycleException?{
????????this.fireLifecycleEvent("configure_start",?(Object)null);
????????this.setState(LifecycleState.STARTING);
????????this.globalNamingResources.start();
????????Object?var1?=?this.servicesLock;
????????synchronized(this.servicesLock)?{
????????????for(int?i?=?0;?i?????????????????this.services[i].start();
????????????}
????????}
????}

總得來說就是,StandardServer繼承自LifecycleMBeanBase,而LifecycleMBeanBase繼承自LifecycleBase,而LifecycleBase類中的模板方法,又讓自己的子類去進行具體的實現(xiàn),但是我們要知道他的Tomcat生命周期中存在這些內容才行。
圖中都說了,Server里面有Service,那么一定就有,我們得去找找看,于是阿粉再次去找并且去看它到底是個什么意思,
????public?void?addService(Service?service)?{
????????service.setServer(this);
????????Object?var2?=?this.servicesLock;
????????synchronized(this.servicesLock)?{
????????????Service[]?results?=?new?Service[this.services.length?+?1];
????????????System.arraycopy(this.services,?0,?results,?0,?this.services.length);
????????????results[this.services.length]?=?service;
????????????this.services?=?results;
????????????if?(this.getState().isAvailable())?{
????????????????try?{
????????????????????service.start();
????????????????}?catch?(LifecycleException?var6)?{
????????????????????;
????????????????}
????????????}
????????????this.support.firePropertyChange("service",?(Object)null,?service);
????????}
????}
位置是在Server的接口中出現(xiàn)了增加和刪除Service的方法,Server的init方法和start方法循環(huán)去調用每個Service的init方法和start方法。
接下來我們看看Service的具體實現(xiàn),找到StandardService:
??protected?void?initInternal()?throws?LifecycleException?{
????????super.initInternal();
????????if?(this.engine?!=?null)?{
????????????this.engine.init();
????????}
????????Executor[]?arr$?=?this.findExecutors();
????????int?len$?=?arr$.length;
????????int?len$;
????????for(len$?=?0;?len$?????????????Executor?executor?=?arr$[len$];
????????????if?(executor?instanceof?JmxEnabled)?{
????????????????((JmxEnabled)executor).setDomain(this.getDomain());
????????????}
????????????executor.init();
????????}
????????this.mapperListener.init();
????????Object?var11?=?this.connectorsLock;
????????synchronized(this.connectorsLock)?{
????????????Connector[]?arr$?=?this.connectors;
????????????len$?=?arr$.length;
????????????for(int?i$?=?0;?i$?????????????????Connector?connector?=?arr$[i$];
????????????????try?{
????????????????????connector.init();
????????????????}?catch?(Exception?var9)?{
????????????????????String?message?=?sm.getString("standardService.connector.initFailed",?new?Object[]{connector});
????????????????????log.error(message,?var9);
????????????????????if?(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))?{
????????????????????????throw?new?LifecycleException(message);
????????????????????}
????????????????}
????????????}
????????}
????}
而在方法中主要調用Executor,mapperListener,executor的init方法。
connector之前已經有了,而這個mapperListener就是Mapper的監(jiān)聽器,用來堅挺container容器的變化。

文獻參考
《深入剖析Tomcat》?
《Tomcat架構解析》?
《Servlet/JSP深入詳解》
PS:公號內回復「Python」即可進入Python 新手學習交流群,一起?100 天計劃!
老規(guī)矩,兄弟們還記得么,右下角的 “在看” 點一下,如果感覺文章內容不錯的話,記得分享朋友圈讓更多的人知道!


【神秘禮包獲取方式】
