【Tomcat源碼閱讀分享】—(4)Tomcat啟動過程簡述(二)

上一篇寫到Bootstrap類的啟動流程,經(jīng)過啟動腳本,經(jīng)歷了一系列的初始化,類加載,最后通過不同的命令,執(zhí)行到不同的操作,由于是啟動過程,所以從執(zhí)行l(wèi)oad()方法開始。接下來我們簡單梳理下load方法的主要流程和一些結(jié)構(gòu)分析,從而了解到其中的啟動流程,衍生出tomcat中主要的幾個組件,以及這些組件之間的大概關(guān)系。為以后每個模塊和組件的研究打下基礎(chǔ)。

我們進入到daemon.load(args)方法,可以看到,也是使用反射調(diào)用了Catalina類的load(String args[])方法:

private void load(String[] arguments)
        throws Exception {

        // Call the load() method 
        String methodName = "load";
        ...
       method.invoke(catalinaDaemon, param);
}

接下來進入到Catalina#load(String args[])
這個方法主要是對一些特殊命令進行處理,接下來便進入到重載的load()方法:

   /**
     * Start a new server instance.
     */
    public void load() {
        long t1 = System.nanoTime();
        ...
        ...
        // Create and execute our Digester
        Digester digester = createStartDigester();
        ...
        ...

        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
        // Start the new server
        try {
            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);
            }
        }
  }

這里只列出部分核心代碼。
首先,使用Digester 工具預(yù)定義了一系列的conf/server.xml配置文件的解析規(guī)則,這個工具會在以后的章節(jié)中細(xì)說,這個工具主要是定義了xml轉(zhuǎn)換為java對象的規(guī)則。
為了簡潔,我將tomcat的默認(rèn)配置文件的注釋刪掉了,如圖:


圖一 tomcat默認(rèn)配置文件

通過Digester ,可以將Catalina中的server屬性創(chuàng)建為默認(rèn)的StandardServer類,并將其中的屬性依賴關(guān)系也通過xml創(chuàng)建了。

從配置文件可以看到,Server中包含了一組Listener,用來監(jiān)聽生命周期內(nèi)的事件,這里用到了觀察者模式來做事件通知操作,后續(xù)的章節(jié)中也會詳細(xì)說明。

Server中又包括了一個Service標(biāo)簽,在使用Digester 時,創(chuàng)建了默認(rèn)的StandardService類,Service中包含了兩個Connector和一個Engine,再往里面就是Realm和Host、Valve。初步就是這么個結(jié)構(gòu),后續(xù)章節(jié)會詳細(xì)解說這些組件間的關(guān)系和作用。

解析完server.xml文件后,將當(dāng)前的Catalina對象的信息傳給server屬性,讓它們互相擁有彼此的引用,然后調(diào)用server的init()方法。

StandardServer中并沒有init方法,所以我根據(jù)其繼承關(guān)系,找到了其父類LifecycleBase的init方法,代碼如下:

    @Override
    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) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }
    }

為了防止其他線程同時調(diào)用這個方法,導(dǎo)致生命周期的狀態(tài)不正常,這個方法加了關(guān)鍵字synchronized 。這個方法其實用了模板方法設(shè)計模式,留了一個initInternal抽象鉤子方法讓子類去實現(xiàn)。
setStateInternal方法設(shè)置了當(dāng)前的state為初始化對應(yīng)的狀態(tài),我們打開期內(nèi)部,會在方法最后看到如下代碼:

       String lifecycleEvent = state.getLifecycleEvent();
        if (lifecycleEvent != null) {
            fireLifecycleEvent(lifecycleEvent, data);
        }
   /**
     * Allow sub classes to fire {@link Lifecycle} events.
     *
     * @param type  Event type
     * @param data  Data associated with event.
     */
    protected void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(this, type, data);
        for (LifecycleListener listener : lifecycleListeners) {
            listener.lifecycleEvent(event);
        }
    }

fireLifecycleEvent方法中其實就是通知所有的監(jiān)聽者本次發(fā)生了初始化的事件,監(jiān)聽者會根據(jù)event的類型進行不同的操作,在這里,各個Listener充當(dāng)了觀察者模式中的監(jiān)聽者,Server是監(jiān)聽目標(biāo),后續(xù)我們將會看到許多類似這樣的寫法。想深入觀察者模式的請點擊這里
接下來我們再進入StandardServer的initInternal方法,代碼如下:

   /**
     * Invoke a pre-startup initialization. This is used to allow connectors
     * to bind to restricted ports under Unix operating environments.
     */
    @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        // Register global String cache
        // Note although the cache is global, if there are multiple Servers
        // present in the JVM (may happen when embedding) then the same cache
        // will be registered under multiple names
        onameStringCache = register(new StringCache(), "type=StringCache");

        // Register the MBeanFactory
        MBeanFactory factory = new MBeanFactory();
        factory.setContainer(this);
        onameMBeanFactory = register(factory, "type=MBeanFactory");

        // Register the naming resources
        globalNamingResources.init();

        // Populate the extension validator with JARs from common and shared
        // class loaders
        if (getCatalina() != null) {
            ClassLoader cl = getCatalina().getParentClassLoader();
            // Walk the class loader hierarchy. Stop at the system class loader.
            // This will add the shared (if present) and common class loaders
            while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                if (cl instanceof URLClassLoader) {
                    URL[] urls = ((URLClassLoader) cl).getURLs();
                    for (URL url : urls) {
                        if (url.getProtocol().equals("file")) {
                            try {
                                File f = new File (url.toURI());
                                if (f.isFile() &&
                                        f.getName().endsWith(".jar")) {
                                    ExtensionValidator.addSystemResource(f);
                                }
                            } catch (URISyntaxException e) {
                                // Ignore
                            } catch (IOException e) {
                                // Ignore
                            }
                        }
                    }
                }
                cl = cl.getParent();
            }
        }
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }

這里前面一大段是JMX中的注冊MBean等操作,最后循環(huán)調(diào)用了它所有service的init方法。
同理進入到init方法,還是跟Server一樣,又跳轉(zhuǎn)到了LifecycleBase的init方法,同樣地,也是進入到StandardService中的initInternal方法,代碼如下:

/**
   * Invoke a pre-startup initialization. This is used to allow connectors
     * to bind to restricted ports under Unix operating environments.
     */
    @Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (engine != null) {
            engine.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize mapper listener
        mapperListener.init();

        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    connector.init();
                } catch (Exception e) {
                    String message = sm.getString(
                            "standardService.connector.initFailed", connector);
                    log.error(message, e);

                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new LifecycleException(message);
                }
            }
        }
    }

首先調(diào)用了engine的init方法,在Digester 解析過程中配置了默認(rèn)的StandardEngine,老規(guī)矩,從繼承關(guān)系就可以肯定又是調(diào)用到了StandardEngine的initInternal這個鉤子方法

@Override
    protected void initInternal() throws LifecycleException {
        // Ensure that a Realm is present before any attempt is made to start
        // one. This will create the default NullRealm if necessary.
        getRealm();
        super.initInternal();
    }

這里是為了避免realm對象為空,如果為null,會新建一個NullRealm對象來初始化。
然后調(diào)用了父類ContainerBase的initInternal方法,初始化了啟動和停止的線程池startStopExecutor。

再回到StandardService中的initInternal方法,這里初始化了executors,等,最后初始化了connectors,也是默認(rèn)的Connector類。

看到這里,我們結(jié)合server.xml,對于tomcat的初始化流程和一些組件的關(guān)系,應(yīng)該有了一個初步的了解了

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

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

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