通過Tomcat”高層“看Tomcat的啟動(dòng)過程

我們可以通過Tomcat的/bin目錄下的腳本startup.sh來啟動(dòng)Tomcat,執(zhí)行了這個(gè)腳本會發(fā)生什么呢? 通過下面這張流程圖了解一下衫嵌。

Tomcat啟動(dòng)流程圖.jpg
  1. Tomcat本質(zhì)上是一個(gè)Java程序浸锨,因此startup.sh腳本會啟動(dòng)一個(gè)JVM來運(yùn)行Tomcat的啟動(dòng)類Bootstrap。
  2. Bootstrap的主要任務(wù)是初始化Tomcat的類加載器并創(chuàng)建Catalina介却。
  3. Catalina是一個(gè)啟動(dòng)類,它通過解析server.xml块茁、創(chuàng)建相應(yīng)的組件齿坷,并調(diào)用Server的start方法。
  4. Server組件的職責(zé)就是管理Service組件数焊,它會負(fù)責(zé)調(diào)用Service的start方法永淌。
  5. Service組件的職責(zé)就是管理連接器和頂層容器組件Engine,因此它會調(diào)用連接器和Engine的start方法佩耳。

Catalina

Catalina的主要任務(wù)就是創(chuàng)建Server遂蛀,需要解析出server.xml,把在server.xml里配置的各種組件一一創(chuàng)建出來干厚,接著調(diào)用Server組件的init方法和start方法李滴,這樣整個(gè)Tomcat就啟動(dòng)起來了。作為”管理者“蛮瞄,Catalina還需要處理各種異常情況所坯,比如我們通過”Ctrl + C“關(guān)閉Tomcat時(shí),Tomcat將如何優(yōu)雅的停止并且清理資源呢挂捅?因此Catalina在JVM中注冊了一個(gè)”關(guān)閉鉤子“芹助。

    public void start() {
        // 如果持有的Server實(shí)例為空,就解析server.xml創(chuàng)建一個(gè)
        if (getServer() == null) {
            load();
        }
        // 如果創(chuàng)建失敗 報(bào)錯(cuò)退出
        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // 啟動(dòng)Server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

        // 創(chuàng)建并注冊JVM關(guān)閉鉤子
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }
        // 用await方法監(jiān)聽停止請求
        if (await) {
            await();
            stop();
        }
    }

那什么是”關(guān)閉鉤子“闲先,它又是做什么的呢状土?如果我們需要在JVM關(guān)閉時(shí)做一些清理工作,比如將緩存數(shù)據(jù)刷到磁盤上伺糠,或者清理一些臨時(shí)文件蒙谓,可以向JVM注冊一個(gè)”關(guān)閉鉤子“,”關(guān)閉鉤子“其實(shí)就是一個(gè)線程退盯,JVM在停止之前會嘗試執(zhí)行這個(gè)線程的run方法彼乌。下面是Tomcat的”關(guān)閉鉤子“CatalinaShutdownHook:

    protected class CatalinaShutdownHook extends Thread {

        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }

可以看出,Tomcat的“關(guān)閉鉤子”實(shí)際上就是執(zhí)行了Server的stop方法渊迁,Server組件的stop方法會釋放和清理所有的資源慰照。

Server組件

Server組件的具體實(shí)現(xiàn)類是StandardServer,Server繼承了LifecycleBase琉朽,它的生命周期被統(tǒng)一管理毒租,并且它的子組件是Service,因此它還要管理Service的生命周期箱叁,也就是說在啟動(dòng)時(shí)調(diào)用Service組件的啟動(dòng)方法墅垮,在停止時(shí)調(diào)用它們的停止方法惕医。Server在內(nèi)部維護(hù)了若干Service組件,它是以數(shù)組來保存的算色,下面是Server添加一個(gè)Service到數(shù)組中的方法:

public void addService(Service service) {
        service.setServer(this);
        synchronized (servicesLock) {
            // 創(chuàng)建一個(gè)長度加一的數(shù)組
            Service results[] = new Service[services.length + 1];
            // 將老的數(shù)據(jù)復(fù)制過去
            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ā)監(jiān)聽事件
            // Report this property change to interested listeners
            support.firePropertyChange("service", null, service);
        }
    }

除此之外抬伺,Server組件還有一個(gè)重要的任務(wù)是啟動(dòng)一個(gè)Socket類監(jiān)聽停止端口,這就是為什么你能通過shutdown命令來關(guān)閉Tomcat灾梦。上面Caralina的啟動(dòng)方法的最后一行代碼就是調(diào)用了Server的await方法峡钓。在await方法里會創(chuàng)建一個(gè)Socket監(jiān)聽8005端口,并在一個(gè)死循環(huán)里接收Socket上的連接請求若河,如果有新的連接到來就新建連接能岩,然后從Socket中讀取數(shù)據(jù);如果讀到的數(shù)據(jù)是停止命令”SUTDOWN“萧福,就退出循環(huán)拉鹃,進(jìn)入stop流程。

Service組件

Service組件的具體實(shí)現(xiàn)類是StandardService鲫忍,我們西拿來看看它的定義以及關(guān)鍵的成員變量膏燕。

public class StandardService extends LifecycleMBeanBase implements Service {
    /**
     * The name of this service. 
     * Service的名字
     */
    private String name = null;
    
    /**
     * The <code>Server</code> that owns this Service, if any.
     * Server實(shí)例
     */
    private Server server = null;
    
    /**
     * The set of Connectors associated with this Service.
     * 連接器數(shù)組
     */
    protected Connector connectors[] = new Connector[0];
    private final Object connectorsLock = new Object();
    
    // 對應(yīng)的Engine容器
    private Engine engine = null;
    
    /**
     * Mapper.
     * 映射器
     */
    protected final Mapper mapper = new Mapper();
    
    /**
     * Mapper listener.
     * 映射器的監(jiān)聽器
     */
    protected final MapperListener mapperListener = new MapperListener(this);
}

為什么要有一個(gè)MapperListener?這是因?yàn)門omcat支持熱部署饲窿,當(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 {
        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
            
        // 觸發(fā)啟動(dòng)監(jiān)聽器
        setState(LifecycleState.STARTING);
        
        // 先啟動(dòng)engine, Engine會啟動(dòng)它的子容器
        // Start our defined Container first
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
   
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }
        
        // 啟動(dòng)Mapper容器
        mapperListener.start();
        
        // 啟動(dòng)連接器炕桨,連接器會啟動(dòng)它的子組件 比如Endpoint
        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

從啟動(dòng)方法可以看到饭尝,Service先啟動(dòng)了Engine組件,再啟動(dòng)Mapper監(jiān)聽器献宫,最后才是啟動(dòng)連接器钥平,內(nèi)層組件啟動(dòng)好了才能對外提供服務(wù),才能啟動(dòng)外層的連接器組件姊途。而Mapper也依賴容器組件涉瘾,容器組件啟動(dòng)好了才能監(jiān)聽它們的變化知态,因此Mapper和MapperListener在容器組件之后啟動(dòng)。組件停止的順序和啟動(dòng)的順序正好相反的立叛,也是基于它們的依賴關(guān)系负敏。

Engine組件

再來看看頂層容器組件Engine是如何實(shí)現(xiàn)的,Engine本質(zhì)是一個(gè)容器秘蛇,因此它繼承了ContainerBase基類原在,并且實(shí)現(xiàn)了Engine接口。

public class StandardEngine extends ContainerBase implements Engine {
    ...
}

Engine的子容器是Host彤叉,所以它持有了一個(gè)Host容器的數(shù)組庶柿,在抽象類ContainerBase中,ContainerBase中有這樣一個(gè)數(shù)據(jù)結(jié)構(gòu):

protected final HashMap<String, Container> children = new HashMap<>();

ContainerBase用HashMap保存了它的子容器秽浇,并且ContainerBase還實(shí)現(xiàn)了子容器的”增刪改查“浮庐,甚至連子容器的啟動(dòng)和停止都提供了默認(rèn)實(shí)現(xiàn),比如ContainerBase會用專門的線程池來啟動(dòng)子容器柬焕。

        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

所以Engine在啟動(dòng)Host子容器時(shí)就直接重用了這個(gè)方法审残。

我們知道容器最重要的功能是處理請求,而Engine容器對請求的”處理“斑举,其實(shí)就是把請求轉(zhuǎn)發(fā)給某一個(gè)Host子容器來處理搅轿,具體是通過Valve來實(shí)現(xiàn)的。

我們知道每一個(gè)容器組件都有一個(gè)Pipeline富玷,而Pipeline中有一個(gè)基礎(chǔ)閥(Basic Valve)璧坟,而Engine容器的基礎(chǔ)閥定義如下:

final class StandardEngineValve extends ValveBase {

    public final void invoke(Request request, Response response)
        throws IOException, ServletException {

        // Select the Host to be used for this Request
        // 拿到請求中的Host容器
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost",
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        // 調(diào)用Host容器中的Pipeline中的第一個(gè)Valve
        host.getPipeline().getFirst().invoke(request, response);
    }
}

這個(gè)基礎(chǔ)閥實(shí)現(xiàn)非常簡單,就是把請求轉(zhuǎn)發(fā)到Host容器赎懦。我們可以看到處理請求的Host容器對象是從請求中拿到的雀鹃,請求對象中怎么會有Host容器呢?這是因?yàn)檎埱蟮竭_(dá)Engine容器之前励两,Mapper組件已經(jīng)對請求進(jìn)行了路由處理黎茎,Mapper組件通過請求的URL定位了相應(yīng)的容器,并且把容器對象保存到了請求對象中当悔。

Tomcat的啟動(dòng)過程傅瞻,具體是由啟動(dòng)類和”高層“組件來完成的,它們都承擔(dān)著”管理“的角色盲憎,負(fù)責(zé)將子組件創(chuàng)建出來嗅骄,并把它們拼裝在一起,同時(shí)也掌握子組件的”生殺大權(quán)“焙畔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掸读,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌儿惫,老刑警劉巖澡罚,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肾请,居然都是意外死亡留搔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門铛铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隔显,“玉大人,你說我怎么就攤上這事饵逐±撸” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵倍权,是天一觀的道長掷豺。 經(jīng)常有香客問我,道長薄声,這世上最難降的妖魔是什么当船? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮默辨,結(jié)果婚禮上德频,老公的妹妹穿的比我還像新娘。我一直安慰自己缩幸,他們只是感情好壹置,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桌粉,像睡著了一般蒸绩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铃肯,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音传蹈,去河邊找鬼押逼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惦界,可吹牛的內(nèi)容都是我干的挑格。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沾歪,長吁一口氣:“原來是場噩夢啊……” “哼漂彤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤挫望,失蹤者是張志新(化名)和其女友劉穎立润,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體媳板,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桑腮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛉幸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片破讨。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奕纫,靈堂內(nèi)的尸體忽然破棺而出提陶,到底是詐尸還是另有隱情,我是刑警寧澤匹层,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布搁骑,位于F島的核電站,受9級特大地震影響又固,放射性物質(zhì)發(fā)生泄漏仲器。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一仰冠、第九天 我趴在偏房一處隱蔽的房頂上張望乏冀。 院中可真熱鬧,春花似錦洋只、人聲如沸辆沦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肢扯。三九已至,卻和暖如春担锤,著一層夾襖步出監(jiān)牢的瞬間蔚晨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工肛循, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铭腕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓多糠,卻偏偏與公主長得像累舷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子夹孔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容