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

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

Tomcat啟動(dòng)流程圖.jpg
  1. Tomcat本質(zhì)上是一個(gè)Java程序,因此startup.sh腳本會(huì)啟動(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組件,它會(huì)負(fù)責(zé)調(diào)用Service的start方法。
  5. Service組件的職責(zé)就是管理連接器和頂層容器組件Engine,因此它會(huì)調(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中注冊(cè)了一個(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)建并注冊(cè)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)聽停止請(qǐng)求
        if (await) {
            await();
            stop();
        }
    }

那什么是”關(guān)閉鉤子“,它又是做什么的呢?如果我們需要在JVM關(guān)閉時(shí)做一些清理工作,比如將緩存數(shù)據(jù)刷到磁盤上,或者清理一些臨時(shí)文件,可以向JVM注冊(cè)一個(gè)”關(guān)閉鉤子“,”關(guān)閉鉤子“其實(shí)就是一個(gè)線程,JVM在停止之前會(huì)嘗試執(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方法會(huì)釋放和清理所有的資源。

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方法里會(huì)創(chuàng)建一個(gè)Socket監(jiān)聽8005端口,并在一個(gè)死循環(huán)里接收Socket上的連接請(qǐng)求,如果有新的連接到來就新建連接,然后從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();
    
    // 對(duì)應(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會(huì)啟動(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)連接器,連接器會(huì)啟動(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)好了才能對(duì)外提供服務(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會(huì)用專門的線程池來啟動(dòng)子容器。

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

所以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),而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
        // 拿到請(qǐng)求中的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)非常簡單,就是把請(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ì)象中。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容