深入理解 Tomcat(六)源碼剖析Tomcat 啟動過程----生命周期和容器組件

好了,今天我們繼續(xù)分析 tomcat 源碼, 這是第六篇了, 上一篇我們一邊 debug 一邊研究了 tomcat 的類加載體系, 我覺得效果還不錯, 樓主感覺對 tomcat 的類加載體系的理解又加深了一點(diǎn). 所以, 我們今天還是按照之前的方式來繼續(xù)看源碼, 一邊 debug, 一邊看, 今天我們分析的是tomcat 中2個非常重要的組件-------生命周期和容器. tomcat 龐大的架構(gòu), 他是如何管理每個對象的呢? 我們在深入理解 Tomcat (二) 從宏觀上理解 Tomcat 組件及架構(gòu)中說過一段:

基于JMX Tomcat會為每個組件進(jìn)行注冊過程,通過Registry管理起來,而Registry是基于JMX來實(shí)現(xiàn)的,因此在看組件的init和start過程實(shí)際上就是初始化MBean和觸發(fā)MBean的start方法,會大量看到形如: Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false); 這樣的代碼,這實(shí)際上就是通過JMX管理各種組件的行為和生命期。

當(dāng)時大家可能還不是很理解這句話, 覺得這是在扯淡, 聽不懂. 好吧, 今天我們就用代碼說話, 看看 JMX 到底怎么管理 tomcat 的 組件.

1. 什么是 JMX?

我們之前說過:

JMX 即 Java Management Extensions(JMX 規(guī)范), 是用來對 tomcat 進(jìn)行管理的. tomcat 中的實(shí)現(xiàn)是 commons modeler 庫, Catalina 使用這個庫來編寫托管 Bean 的工作. 托管 Bean 就是用來管理 Catalina 中其他對象的 Bean.

簡單來說: 就是一個可以為Java應(yīng)用程序或系統(tǒng)植入遠(yuǎn)程管理功能的框架。

既然是框架, 肯定要有架構(gòu)圖:


這里對上圖中三個分層進(jìn)行介紹:

  • Probe Level:負(fù)責(zé)資源的檢測(獲取信息),包含MBeans,通常也叫做Instrumentation Level。MX管理構(gòu)件(MBean)分為四種形式,分別是標(biāo)準(zhǔn)管理構(gòu)件(Standard MBean)、動態(tài)管理構(gòu)件(Dynamic MBean)、開放管理構(gòu)件(Open Mbean)和模型管理構(gòu)件(Model MBean)。

  • Agent Level:即MBeanServer,是JMX的核心,負(fù)責(zé)連接Mbeans和應(yīng)用程序。

  • Remote Management Level:通過connectors和adaptors來遠(yuǎn)程操作MBeanServer,常用的控制臺,例如JConsole、VisualVM(等會我們就要用這個)等。

2. 我們看看生命周期組件接口是如何設(shè)計(jì)的:

這是一張 IDEA 生成的簡單的 StandardHost(Host 容器的標(biāo)準(zhǔn)實(shí)現(xiàn)) 的 UML類圖, 基本上, tomcat 的容器類都是這樣的繼承結(jié)構(gòu).

因此我們就可以直接看下面這張圖:


這里對上圖中涉及的主要類作個簡單介紹:

  1. Lifecycle:定義了容器生命周期、容器狀態(tài)轉(zhuǎn)換及容器狀態(tài)遷移事件的監(jiān)聽器注冊和移除等主要接口;

  2. LifecycleBase:作為Lifecycle接口的抽象實(shí)現(xiàn)類,運(yùn)用抽象模板模式將所有容器的生命周期及狀態(tài)轉(zhuǎn)換銜接起來,此外還提供了生成LifecycleEvent事件的接口;

  3. LifecycleSupport:提供有關(guān)LifecycleEvent事件的監(jiān)聽器注冊、移除,并且使用經(jīng)典的監(jiān)聽器模式,實(shí)現(xiàn)事件生成后觸達(dá)監(jiān)聽器的實(shí)現(xiàn);

  4. MBeanRegistration:Java JMX框架提供的注冊MBean的接口,引入此接口是為了便于使用JMX提供的管理功能;

  5. LifecycleMBeanBase:Tomcat提供的對MBeanRegistration的抽象實(shí)現(xiàn)類,運(yùn)用抽象模板模式將所有容器統(tǒng)一注冊到JMX;

  6. 此外,ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都繼承了LifecycleMBeanBase,因此這些容器都具有了同樣的生命周期并可以通過JMX進(jìn)行管理。

3. 再看看我們的容器結(jié)構(gòu)

我們之前說, 如果從宏觀上講容器, 畫畫圖, 講講就好了, 就可以在腦海里形成一個映象, 今天, 我們要好好的講講容器, 從代碼層面去理解他們. 這樣一來, 也順便把我們的容器組件也講了, 等于又講了生命周期組件, 還有容器組件. 一舉兩得. 哈哈哈. 好吧, 不扯了, 回來, 我們繼續(xù)講容器. 還是先來一張圖吧:

從上圖中我們可以看到: StandardServer、StandardService、Connector、StandardContext這些容器,彼此之間都有父子關(guān)系,每個容器都可能包含零個或者多個子容器,這些子容器可能存在不同類型或者相同類型的多個. 所以他們都包含的關(guān)系, 如果讓你來設(shè)計(jì)這些容器的生命周期, 你會用什么設(shè)計(jì)模式呢?

4. 容器初始化, 開始 Debug

首先我們啟動 main 方法:


    public static void main(String args[]) {
        try {
            // 命令
            String command = "start";
            // 如果命令行中輸入了參數(shù)
            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);// bootstrap 和 Catalina 一脈相連, 這里設(shè)置, 方法內(nèi)部設(shè)置 Catalina 實(shí)例setAwait方法
                daemon.load(args);// args 為 空,方法內(nèi)部調(diào)用 Catalina 的 load 方法.
                daemon.start();// 相同, 反射調(diào)用 Catalina 的 start 方法 ,至此,啟動結(jié)束
            } 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 t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }
    }

熟悉這個方法或者看過我們上篇文章的同學(xué)都知道, 我已經(jīng)把類加載那部分代碼去除了, 因?yàn)槲覀兘裉觳谎芯款惣虞d. 所以 ,我們看邏輯, 首先, 判斷命令是什么, 我們現(xiàn)在的命令肯定是 start 啊, 所以進(jìn)入 else if 塊, 調(diào)用 load 方法 , 進(jìn)入 load 方法, 可以看到, 該方法實(shí)際上就是 Catalina 類的 load 方法, 那么我們進(jìn)入 Catalina 類的 load 方法看看(方法很長, 樓主去除了和今天的模塊無關(guān)的代碼):

public void load() {
        // 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);
            }
        }
 }

可以看到, 這里有一個我們今天感興趣的方法, getServer.init(), 這個方法看名字是啟動 Server 的初始化, 而 Server 是我們上面圖中最外層的容器. 因此, 我們?nèi)タ纯丛摲椒? 也就是LifecycleBase.init() 方法. 該方法是一個模板方法, 只是定義了一個算法的骨架, 將一些細(xì)節(jié)算法延遲到了子類中. 看, 我們又學(xué)到了一個設(shè)計(jì)模式. 我們看看該方法:

 @Override
    public final synchronized void init() throws LifecycleException {
        // 1
        if (!state.equals(LifecycleState.NEW)) {
            invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
        }
        // 2
        setStateInternal(LifecycleState.INITIALIZING, null, false);

        try {
            // 模板方法
            /**
             * 采用模板方法模式來對所有支持生命周期管理的組件的生命周期各個階段進(jìn)行了總體管理,
             * 每個需要生命周期管理的組件只需要繼承這個基類,
             * 然后覆蓋對應(yīng)的鉤子方法即可完成相應(yīng)的聲明周期階段的管理工作
             */
            initInternal();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.initFail",toString()), t);
        }

        // 3
        setStateInternal(LifecycleState.INITIALIZED, null, false);
    }

我們看看該方法, 這應(yīng)該就是容器啟動的邏輯了, 先前我們定義了那么多狀態(tài), 現(xiàn)在用上了. 首先判斷該方法的狀態(tài), 如果不是 NEW, 則拋出異常, 否則則設(shè)置狀態(tài)為 INITIALIZING, 然后調(diào)用一個抽象方法 initInternal , 該方法由子類具體實(shí)現(xiàn). 執(zhí)行完則修改狀態(tài)為 INITIALIZED. 這里應(yīng)該是使用了狀態(tài)模式. 依賴狀態(tài)時,同步該方法, 防止并發(fā)錯誤. tomcat 可以的.

5. 那么我們來看看 StandardServer 是如何實(shí)現(xiàn) initInternal 方法的:

    @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();
        }
    }

6. LifecycleMBeanBase.initInternal() 實(shí)現(xiàn)

首先調(diào)用父類的 super.initInternal() 方法,此initInternal方法用于將容器托管到JMX,便于運(yùn)維管理:

    @Override
    protected void initInternal() throws LifecycleException {
       
        // If oname is not null then registration has already happened via
        // preRegister().
        if (oname == null) {
            mserver = Registry.getRegistry(null, null).getMBeanServer();
            oname = register(this, getObjectNameKeyProperties());
        }
    }

7. LifecycleMBeanBase.register 方法實(shí)現(xiàn)

LifecycleMBeanBase 會調(diào)用自身的 register 方法, 該方法會將容器注冊到 MBeanServer:

    protected final ObjectName register(Object obj,
            String objectNameKeyProperties) {
        
        // Construct an object name with the right domain
        StringBuilder name = new StringBuilder(getDomain());
        name.append(':');
        name.append(objectNameKeyProperties);

        ObjectName on = null;

        try {
            on = new ObjectName(name.toString());
            // 核心實(shí)現(xiàn):registerComponent
            Registry.getRegistry(null, null).registerComponent(obj, on, null);
        } catch (MalformedObjectNameException e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
                    e);
        } catch (Exception e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),
                    e);
        }

        return on;
    }
    

該方法內(nèi)部核心方法是 Registry. registerComponent, 在org.apache.catalina.util 包下, 我們看看該方法實(shí)現(xiàn)。

8. Registry.registerComponent 方法實(shí)現(xiàn)

  public void registerComponent(Object bean, ObjectName oname, String type)
           throws Exception
    {
        if( log.isDebugEnabled() ) {
            log.debug( "Managed= "+ oname);
        }

        if( bean ==null ) {
            log.error("Null component " + oname );
            return;
        }

        try {
            if( type==null ) {
                type=bean.getClass().getName();
            }

            ManagedBean managed = findManagedBean(bean.getClass(), type);

            // The real mbean is created and registered
            DynamicMBean mbean = managed.createMBean(bean);

            if(  getMBeanServer().isRegistered( oname )) {
                if( log.isDebugEnabled()) {
                    log.debug("Unregistering existing component " + oname );
                }
                getMBeanServer().unregisterMBean( oname );
            }

            getMBeanServer().registerMBean( mbean, oname);
        } catch( Exception ex) {
            log.error("Error registering " + oname, ex );
            throw ex;
        }
    }

該方法會為當(dāng)前容器創(chuàng)建一個 DynamicMBean , 并且注冊到MBeanServer。調(diào)用 MBeanServer.registerMBean() 方法。而 MBeanServer 在 javax.management, 也就是 rt.jar 中,該包由 java 的 BootStrap 啟動類加載器加載。

注冊進(jìn)MBeanServer 的 key 是什么呢? 相信細(xì)心的同學(xué)會注意到 LifecycleMBeanBase.getObjectNameKeyProperties 和 LifecycleMBeanBase.getDomain 方法 和
LifecycleMBeanBase.getDomainInternal 方法, 這三個方法由具體子類實(shí)現(xiàn),會生成一個專屬于容器的key。格式為:Catalina:type=Server, 這是 Server 容器的 key, debug 可以看出來:

9. JMX 如何管理 組件?

至此, 我們已經(jīng)知道 Tomcat 是如何將容器注冊到 MBeanServer 中的。 那么注冊到 MBeanServer 中后是什么樣子呢?我們看圖:

這是 JDK 自帶的 JvisualVM 工具, 添加了 MBeans 插件, 就可以遠(yuǎn)程操作容器中的 組件了, 可以看到 Service 容器暴漏了很多接口, 用于運(yùn)維人員管理容器和組件。

10. 回到 StandardServer.initInternal 方法

好了, 我們回到 StandardServer.initInternal 方法, 回到我們夢最開始的地方,super.initInternal 方法就是將容器注冊到 JMX 中。 那下面的邏輯是做什么的呢? 在執(zhí)行完父類的 super.initInternal 的方法后, 該方法又注冊個兩個 JMX 。然后尋啟動子容器的 init 方法:

    // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }

而子容器的 init 方法和 Server 的 init 方法的邏輯基本一致,所以不再贅述。

11. 執(zhí)行完 getServer().init() 方法后做什么------容器啟動

Bootstrap 的 load 方法調(diào)用了 Catalina 的 load 方法 ,該方法調(diào)用了Server 的init方法,執(zhí)行完初始化過程,當(dāng)然就是要執(zhí)行 start 方法了, 那么如何執(zhí)行呢?

Bootstrap 調(diào)用了 Catalina 的 start 方法,該方法也同樣執(zhí)行了 Server 的 start 方法, 該方法的具體實(shí)現(xiàn)也在LifecycleBase 中:

@Override
    public final synchronized void start() throws LifecycleException {
        
        if (LifecycleState.STARTING_PREP.equals(state) ||
                LifecycleState.STARTING.equals(state) ||
                LifecycleState.STARTED.equals(state)) {
            
            if (log.isDebugEnabled()) {
                Exception e = new LifecycleException();
                log.debug(sm.getString("lifecycleBase.alreadyStarted",
                        toString()), e);
            } else if (log.isInfoEnabled()) {
                log.info(sm.getString("lifecycleBase.alreadyStarted",
                        toString()));
            }
            
            return;
        }
        
        if (state.equals(LifecycleState.NEW)) {
            init();
        } else if (state.equals(LifecycleState.FAILED)){
            stop();
        } else if (!state.equals(LifecycleState.INITIALIZED) &&
                !state.equals(LifecycleState.STOPPED)) {
            invalidTransition(Lifecycle.BEFORE_START_EVENT);
        }

        setStateInternal(LifecycleState.STARTING_PREP, null, false);

        try {
            startInternal();
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(
                    sm.getString("lifecycleBase.startFail",toString()), t);
        }

        if (state.equals(LifecycleState.FAILED) ||
                state.equals(LifecycleState.MUST_STOP)) {
            stop();
        } else {
            // Shouldn't be necessary but acts as a check that sub-classes are
            // doing what they are supposed to.
            if (!state.equals(LifecycleState.STARTING)) {
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            }
            
            setStateInternal(LifecycleState.STARTED, null, false);
        }
    }

12. StandardServer.startInternal 啟動容器方法實(shí)現(xiàn)

可以看到該方法對狀態(tài)的判斷特別多,我們感興趣的是 try 塊中的 startInternal() 方法, 同樣, 該方法也是個抽象方法,需要子類去具體實(shí)現(xiàn)自己的啟動邏輯。我們看看Server 的啟動邏輯:

 @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);//將自身狀態(tài)更改為LifecycleState.STARTING;
        globalNamingResources.start();
        // Start our defined Services
        synchronized (services) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();// 啟動所有子容器
            }
        }
    }

13. LifecycleSupport.fireLifecycleEvent()方法實(shí)現(xiàn)

該方法首先執(zhí)行自己的fireLifecycleEvent方法, 該方法內(nèi)部是LifecycleSupport.fireLifecycleEvent()方法, 我們進(jìn)入該方法看個究竟:

public void fireLifecycleEvent(String type, Object data) {
        // 事件監(jiān)聽,觀察者模式的另一種方式
        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = listeners;// 監(jiān)聽器數(shù)組 關(guān)注 事件(啟動或者關(guān)閉事件)
        // 循環(huán)通知所有生命周期時間偵聽器????
        for (int i = 0; i < interested.length; i++)
            // 每個監(jiān)聽器都有自己的邏輯
            interested[i].lifecycleEvent(event);
    }

該方法很簡單, 樓主沒有刪一行代碼, 首先, 創(chuàng)建一個事件對象, 然通知所有的監(jiān)聽器發(fā)生了該事件.并做響應(yīng).那么 Server 有哪些監(jiān)聽器呢?

這些監(jiān)聽器將根據(jù)這個事件的類型做出響應(yīng).

14. 我們回到 startInternal 方法, 啟動所有容器

事件監(jiān)聽結(jié)束之后, 調(diào)用 setState(LifecycleState.STARTING); 表明狀態(tài)時開始中, 并且循環(huán)啟動子容器, 這里的 Server 啟動的是Service 數(shù)組, 循環(huán)啟動他們的 start 方法. 以此類推. 啟動所有的容器:

    synchronized (services) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();// 啟動所有子容器
            }
        }

現(xiàn)在我們關(guān)注的是Server 容器, 因此, Server 會啟動 services 數(shù)組中的所有 Service 組件。該方法就完成了通知所有監(jiān)聽器發(fā)送了啟動事件,然后使用觀察者模式,啟動所有子容器,然后子容器繼續(xù)遞歸啟動。最后修改自己的狀態(tài)并告訴監(jiān)聽器。

15. 總結(jié)

其實(shí)樓主在啃代碼最深的感觸就是設(shè)計(jì)模式, 真的很牛逼,不知道同學(xué)們發(fā)現(xiàn)了幾個設(shè)計(jì)模式,樓主在本篇文章中發(fā)現(xiàn)了狀態(tài)模式, 觀察者模式,模板方法, 事件監(jiān)聽,代理模式。真的收益良多。不枉樓主每天看代碼。

還有就是對 Tomcat 生命周期組件的總結(jié)。我們再看看我們的類圖:

tomcat 的主要容器都繼承了 LifecycleMBeanBase 抽象類,該類中關(guān)于 init 和 start 兩個模板方法。定義了主要算法骨架,而方法中又都有抽象方法,需要子類自己去實(shí)現(xiàn)。而 LifecycleBase 中又定義了如何實(shí)現(xiàn)事件監(jiān)聽代理,LifecycleBase 依賴 LifecycleSupport 去完成真正的事件監(jiān)聽。對了,監(jiān)聽器是如何添加進(jìn) LifecycleSupport 的呢?LifecycleSupport 中含有
addLifecycleListener 方法。該方法也是被LifecycleBase代理的。而每個容器下面的子容器也是使用相同的邏輯完成初始化和啟動。父容器和子容器使用了聚合的方式設(shè)計(jì)。

可以說, tomcat的容器的生命周期組件設(shè)計(jì)是非常牛逼的。我們閱讀源碼不僅能了解他的設(shè)計(jì)原理,也能同大師交流,學(xué)會更多。

好了, 今天的深入理解 Tomcat(六)源碼剖析Tomcat 啟動過程----生命周期和容器組件就到這里,謝謝大家的耐心,再這個世界,耐心和堅(jiān)持是無比珍貴的。尤其是程序員。

good luck !!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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