Tomcat源碼學習第3篇 - Bootstrap的啟動過程

上一篇我們看了Tomcat中各個組件的init過程,初始化賦值好了瞄崇,那么接下來就該輪到start了呻粹,話不多說,馬上進入主題~

1. Bootstrap.start()

通過start方法我們可以看到他是通過反射調(diào)用了 Catalina.start()方法苏研,按F7跳到這個類里面看一下等浊。

public void start() throws Exception {
    if (catalinaDaemon == null) {
        init();
    }
    // 通過反射調(diào)用 Catalina.start() 方法
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
    method.invoke(catalinaDaemon, (Object [])null);
}
image

2. Catalina.start()

進來一看,又是熟悉的套娃風格摹蘑,還得繼續(xù)往里面跳轉(zhuǎn)

image

3. LifecycleBase.start()

還是這個熟悉的組件生命周期類筹燕,看到這里就仿佛看到了結(jié)局,肯定是跟initInternal這個方法一樣一層層嵌套進去衅鹿,讓我們走起~

image

4. StandardServer.startInternal()

之前我們有所過在一個Tomcat中是可以有多個service的撒踪,所以這里需要遍歷所有的service分別讓各自啟動起來

image

server.xml文件中再給他配置一個<Service>標簽即可,如圖所示:

image

5. StandardService.startInternal()

開始進入正題了大渤,在這個方法中我們可以看到它分別做了以下操作:

  1. 容器的啟動:engine.start()
  2. 連接器的啟動:connector.start()
protected void startInternal() throws LifecycleException {
    if(log.isInfoEnabled())
        log.info(sm.getString("standardService.start.name", this.name));
    setState(LifecycleState.STARTING);

    // 這里首先啟動我們定義的容器 engine
    if (engine != null) {
        synchronized (engine) {
            // 啟動 engine 子容器
            engine.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // 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);
            }
        }
    }
}

6. StandardEngine.startInternal()

這里直接調(diào)用的ContainerBase.startInternal方法實現(xiàn)下屬組件的啟動

protected synchronized void startInternal() throws LifecycleException {
    // Log our server identification information
    if(log.isInfoEnabled())
        log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
    // Standard container startup
    super.startInternal();
}

7. ContainerBase.startInternal()

這里使用的是線程池的方式制妄,如果有多個Host,那么就可以多個線程并行實例化Host泵三,加快Tomcat啟動速度

protected synchronized void startInternal() throws LifecycleException {
    // 啟動下屬容器
    logger = null;
    getLogger();
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    // 查找并啟動子容器耕捞,Host 在初始化階段后還是不完整的衔掸,需要繼續(xù)封裝,把容器關(guān)系維護完整
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (Container child : children) {
        results.add(startStopExecutor.submit(new StartChild(child)));
    }

    MultiThrowable multiThrowable = null;

    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Throwable e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            if (multiThrowable == null) {
                multiThrowable = new MultiThrowable();
            }
            multiThrowable.add(e);
        }

    }
    if (multiThrowable != null) {
        throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                                     multiThrowable.getThrowable());
    }

    // Start the Valves in our pipeline (including the basic), if any
    if (pipeline instanceof Lifecycle) {
        ((Lifecycle) pipeline).start();
    }

    // 設(shè)置容器生命周期狀態(tài)
    setState(LifecycleState.STARTING);

    // Start our thread
    threadStart();
}

[圖片上傳失敗...(image-bc6c2d-1616772543941)]

image

8. StandardHost.startInternal()

protected synchronized void startInternal() throws LifecycleException {

    // Set error report valve
    String errorValve = getErrorReportValveClass();
    if ((errorValve != null) && (!errorValve.equals(""))) {
        try {
            boolean found = false;
            Valve[] valves = getPipeline().getValves();
            for (Valve valve : valves) {
                if (errorValve.equals(valve.getClass().getName())) {
                    found = true;
                    break;
                }
            }
            if(!found) {
                Valve valve =
                    (Valve) Class.forName(errorValve).getConstructor().newInstance();
                getPipeline().addValve(valve);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error(sm.getString(
                    "standardHost.invalidErrorReportValveClass",
                    errorValve), t);
        }
    }
    super.startInternal();
}

Host的實例化俺抽,是通過設(shè)置生命周期狀態(tài)來進行促發(fā)生命周期事件fireLifecycleEvent來執(zhí)行后續(xù)工作的敞映。

  1. deployApps:處理 host下多個應用
  2. deployDirectories:處理host下面以目錄方式部署的(results.add(),這里也是以多線程的方式并行執(zhí)行的)
  3. host.addChild():這時猜觸發(fā)context實例核心內(nèi)容
  4. context:具體讀取web.xml封裝wrapper過程使用事件驅(qū)動交給ContextConfig(它也是一個事件監(jiān)聽器)
image
image
image
image
image

9. MapperListener.startInternal()

public void startInternal() throws LifecycleException {

        setState(LifecycleState.STARTING);
        // 得到 engine 容器
        Engine engine = service.getContainer();
        if (engine == null) {
            return;
        }
        // 找到默認主機
        findDefaultHost();
        // 為當前容器以及子容器添加監(jiān)聽器
        addListeners(engine);
        // 注冊engine下所有的host
        Container[] conHosts = engine.findChildren();
        for (Container conHost : conHosts) {
            Host host = (Host) conHost;
            if (!LifecycleState.NEW.equals(host.getState())) {
                // 注冊上下文和包裝器
                registerHost(host);
            }
        }
    }

10. Connector.startInternal()

截至到這里磷斧,容器的注冊與啟動已經(jīng)完成了振愿,接下來到連接器的啟動了

protected void startInternal() throws LifecycleException {
    // Validate settings before starting
    if (getPort() < 0) {
        throw new LifecycleException(sm.getString(
            "coyoteConnector.invalidPort", Integer.valueOf(getPort())));
    }

    setState(LifecycleState.STARTING);

    try {
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException(
            sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

11. AbstractProtocol.start()

這里對 EndPoint進行初始化

public void start() throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
    }

    endpoint.start();

    // Start timeout thread
    asyncTimeout = new AsyncTimeout();
    Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
    int priority = endpoint.getThreadPriority();
    if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
        priority = Thread.NORM_PRIORITY;
    }
    timeoutThread.setPriority(priority);
    timeoutThread.setDaemon(true);
    timeoutThread.start();
}
image

12. AbstractEndpoint.start()

public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

13. NioEndpoint.bind()

public void bind() throws Exception {
    if (!getUseInheritedChannel()) {
        // 獲取 nio 通道 channel
        serverSock = ServerSocketChannel.open();
        socketProperties.setProperties(serverSock.socket());
        InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
        // 綁定端口,但尚未使用 accept 獲取客戶端連接
        serverSock.socket().bind(addr,getAcceptCount());
    } else {
        // Retrieve the channel provided by the OS
        Channel ic = System.inheritedChannel();
        if (ic instanceof ServerSocketChannel) {
            serverSock = (ServerSocketChannel) ic;
        }
        if (serverSock == null) {
            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
        }
    }
    serverSock.configureBlocking(true); //mimic APR behavior

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    setStopLatch(new CountDownLatch(pollerThreadCount));

    // Initialize SSL if needed
    initialiseSsl();

    selectorPool.open();
}

14. NioEndpoint.startInternal()

這里通過startAcceptorThreads啟動Accepter線程瞳抓,該線程用于接收新的Socket連接

image
image
image

總結(jié)

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末埃疫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子孩哑,更是在濱河造成了極大的恐慌栓霜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件横蜒,死亡現(xiàn)場離奇詭異胳蛮,居然都是意外死亡,警方通過查閱死者的電腦和手機丛晌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門仅炊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人澎蛛,你說我怎么就攤上這事抚垄。” “怎么了谋逻?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵呆馁,是天一觀的道長。 經(jīng)常有香客問我毁兆,道長浙滤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任气堕,我火速辦了婚禮纺腊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茎芭。我一直安慰自己揖膜,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布梅桩。 她就那樣靜靜地躺著次氨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摘投。 梳的紋絲不亂的頭發(fā)上煮寡,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音犀呼,去河邊找鬼幸撕。 笑死,一個胖子當著我的面吹牛外臂,可吹牛的內(nèi)容都是我干的坐儿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宋光,長吁一口氣:“原來是場噩夢啊……” “哼貌矿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起罪佳,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逛漫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后赘艳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酌毡,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年蕾管,在試婚紗的時候發(fā)現(xiàn)自己被綠了枷踏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掰曾,死狀恐怖旭蠕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旷坦,我是刑警寧澤掏熬,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站塞蹭,受9級特大地震影響孽江,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜番电,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一岗屏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漱办,春花似錦这刷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洞辣,卻和暖如春咐刨,著一層夾襖步出監(jiān)牢的瞬間昙衅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工定鸟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留而涉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓联予,卻偏偏與公主長得像啼县,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子沸久,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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