死磕tomcat源碼(四)之生命周期管理

前言

從server.xml文件解析出來的各個對象都是容器,比如:Server氧秘、Service年鸳、Connector等。這些容器都具有新建丸相、初始化完成搔确、啟動、停止灭忠、失敗膳算、銷毀等狀態(tài)。Tomcat的實現(xiàn)提供了對這些容器的生命周期管理更舞,本文將通過對Tomcat7.0的源碼閱讀畦幢,深入剖析這一過程。

Tomcat生命周期類接口設(shè)計

我們先閱讀圖1缆蝉,從中了解Tomcat涉及生命周期管理的主要類宇葱。

圖1  Tomcat生命周期類接口設(shè)計

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

  • Lifecycle:定義了容器生命周期、容器狀態(tài)轉(zhuǎn)換及容器狀態(tài)遷移事件的監(jiān)聽器注冊和移除等主要接口刊头;
  • LifecycleBase:作為Lifecycle接口的抽象實現(xiàn)類黍瞧,運用抽象模板模式將所有容器的生命周期及狀態(tài)轉(zhuǎn)換銜接起來,此外還提供了生成LifecycleEvent事件的接口原杂;
  • LifecycleSupport:提供有關(guān)LifecycleEvent事件的監(jiān)聽器注冊印颤、移除,并且使用經(jīng)典的監(jiān)聽器模式穿肄,實現(xiàn)事件生成后觸達監(jiān)聽器的實現(xiàn)年局;
  • MBeanRegistration:Java JMX框架提供的注冊MBean的接口咸产,引入此接口是為了便于使用JMX提供的管理功能矢否;
  • LifecycleMBeanBase:Tomcat提供的對MBeanRegistration的抽象實現(xiàn)類,運用抽象模板模式將所有容器統(tǒng)一注冊到JMX脑溢;

此外僵朗,ContainerBase、StandardServer屑彻、StandardService验庙、WebappLoader、Connector社牲、StandardContext粪薛、StandardEngine、StandardHost搏恤、StandardWrapper等容器都繼承了LifecycleMBeanBase违寿,因此這些容器都具有了同樣的生命周期并可以通過JMX進行管理让禀。

什么是JMX?

java管理程序擴展(java management extensions陨界,簡稱JMX),是一個可以為Java應(yīng)用程序或系統(tǒng)植入遠程管理功能的框架痛阻。為便于講解菌瘪,我從網(wǎng)絡(luò)上找了一張JMX的架構(gòu),如圖2所示阱当。

圖2  JMX架構(gòu)

這里對圖2中三個分層進行介紹:

  • Probe Level:負責資源的檢測(獲取信息)俏扩,包含MBeans,通常也叫做Instrumentation Level弊添。MX管理構(gòu)件(MBean)分為四種形式录淡,分別是標準管理構(gòu)件(Standard MBean)、動態(tài)管理構(gòu)件(Dynamic MBean)油坝、開放管理構(gòu)件(Open Mbean)和模型管理構(gòu)件(Model MBean)嫉戚。
  • Agent Level:即MBeanServer,是JMX的核心澈圈,負責連接Mbeans和應(yīng)用程序彬檀。
  • Remote Management Level:通過connectors和adaptors來遠程操作MBeanServer,常用的控制臺瞬女,例如JConsole窍帝、VisualVM等。

容器

Tomcat容器組成

StandardServer诽偷、StandardService坤学、Connector、StandardContext這些容器报慕,彼此之間都有父子關(guān)系深浮,每個容器都可能包含零個或者多個子容器,這些子容器可能存在不同類型或者相同類型的多個卖子,如圖3所示略号。

圖3  Tomcat容器組成

Tomcat容器狀態(tài)

目前,Tomcat的容器具有以下狀態(tài):

  • NEW:容器剛剛創(chuàng)建時洋闽,即在LifecycleBase實例構(gòu)造完成時的狀態(tài)玄柠。
  • INITIALIZED:容器初始化完成時的狀態(tài)。
  • STARTING_PREP:容器啟動前的狀態(tài)诫舅。
  • STARTING:容器啟動過程中的狀態(tài)羽利。
  • STARTED:容器啟動完成的狀態(tài)。
  • STOPPING_PREP:容器停止前的狀態(tài)刊懈。
  • STOPPING:容器停止過程中的狀態(tài)这弧。
  • STOPPED:容器停止完成的狀態(tài)娃闲。
  • DESTROYED:容器銷毀后的狀態(tài)。
  • FAILED:容器啟動匾浪、停止過程中出現(xiàn)異常的狀態(tài)皇帮。
  • MUST_STOP:此狀態(tài)未使用。
  • MUST_DESTROY:此狀態(tài)未使用蛋辈。

這些狀態(tài)都定義在枚舉類LifecycleState中属拾。
事件與監(jiān)聽

每個容器由于繼承自LifecycleBase,當容器狀態(tài)發(fā)生變化時冷溶,都會調(diào)用fireLifecycleEvent方法渐白,生成LifecycleEvent,并且交由此容器的事件監(jiān)聽器處理逞频。LifecycleBase的fireLifecycleEvent方法的實現(xiàn)見代碼:

/**
     * 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) {
        lifecycle.fireLifecycleEvent(type, data);
    }

lifecycle的定義如下:

/**
     * Used to handle firing lifecycle events.
     * TODO: Consider merging LifecycleSupport into this class.
     */
    private LifecycleSupport lifecycle = new LifecycleSupport(this);

LifecycleSupport的fireLifecycleEvent方法的實現(xiàn)

/**
     * Notify all lifecycle event listeners that a particular event has
     * occurred for this Container.  The default implementation performs
     * this notification synchronously using the calling thread.
     *
     * @param type Event type
     * @param data Event data
     */
    public void fireLifecycleEvent(String type, Object data) {

        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = listeners;
        for (int i = 0; i < interested.length; i++)
            interested[i].lifecycleEvent(event);

    }

事件通知給所有監(jiān)聽當前容器的生命周期監(jiān)聽器LifecycleListener纯衍,并調(diào)用LifecycleListener的lifecycleEvent方法。

容器生命周期

每個容器都會有自身的生命周期苗胀,其中也涉及狀態(tài)的遷移襟诸,以及伴隨的事件生成,本節(jié)詳細介紹Tomcat中的容器生命周期實現(xiàn)基协。所有容器的轉(zhuǎn)態(tài)轉(zhuǎn)換(如新疆励堡、初始化、啟動堡掏、停止等)都是由外到內(nèi)应结,由上到下進行,即先執(zhí)行父容器的狀態(tài)轉(zhuǎn)換及相關(guān)操作泉唁,然后再執(zhí)行子容器的轉(zhuǎn)態(tài)轉(zhuǎn)換鹅龄,這個過程是層層迭代執(zhí)行的。

容器新建

所有容器在構(gòu)造的過程中亭畜,都會首先對父類LifecycleBase進行構(gòu)造扮休。LifecycleBase中定義了所有容器的起始狀態(tài)為LifecycleState.NEW,代碼如下:

    /**
     * The current state of the source component.
     */
    private volatile LifecycleState state = LifecycleState.NEW;

容器初始化

每個容器的init方法是自身初始化的入口拴鸵,其初始化過程如圖4所示玷坠。


圖4  容器初始化時序圖

圖4中所說的具體容器,實際就是LifecycleBase的具體實現(xiàn)類劲藐,目前LifecycleBase的類繼承體系如圖5所示八堡。

圖5  LifecycleBase的類繼承體系

根據(jù)圖4所示的初始化過程,我們對Tomcat的源碼進行分析聘芜,其處理步驟如下:

  1. 調(diào)用方調(diào)用容器父類LifecycleBase的init方法兄渺,LifecycleBase的init方法主要完成一些所有容器公共抽象出來的動作;
  2. LifecycleBase的init方法調(diào)用具體容器的initInternal方法實現(xiàn)汰现,此initInternal方法用于對容器本身真正的初始化挂谍;
  3. 具體容器的initInternal方法調(diào)用父類LifecycleMBeanBase的initInternal方法實現(xiàn)叔壤,此initInternal方法用于將容器托管到JMX,便于運維管理口叙;
  4. LifecycleMBeanBase的initInternal方法調(diào)用自身的register方法炼绘,將容器作為MBean注冊到MBeanServer;
  5. 容器如果有子容器妄田,會調(diào)用子容器的init方法饭望;
  6. 容器初始化完畢,LifecycleBase會將容器的狀態(tài)更改為初始化完畢形庭,即LifecycleState.INITIALIZED。

現(xiàn)在對容器初始化的源碼進行分析厌漂,init方法的實現(xiàn)

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

只有狀態(tài)為LifecycleState.NEW時才會初始化萨醒,真正執(zhí)行初始化的方法是initInternal,當初始化完畢苇倡,當前容器的狀態(tài)會被更改為LifecycleState.INITIALIZED富纸。為了簡便起見,我們還是以StandardServer這個容器為例旨椒,StandardServer的initInternal方法的實現(xiàn)

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

通過分析StandardServer的initInternal方法晓褪,其處理過程如下:
步驟一 將當前容器注冊到JMX

調(diào)用父類LifecycleBase的initInternal方法(見代碼清單8),為當前容器創(chuàng)建DynamicMBean综慎,并注冊到JMX中涣仿。

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

StandardServer實現(xiàn)的getObjectNameKeyProperties方法如下:

    @Override
    protected final String getObjectNameKeyProperties() {
        return "type=Server";
    }

LifecycleBase的register方法會為當前容器創(chuàng)建對應(yīng)的注冊名稱,以StandardServer為例示惊,getDomain默認返回Catalina好港,因此StandardServer的JMX注冊名稱默認為Catalina:type=Server,真正的注冊在registerComponent方法中實現(xiàn)米罚。

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

Registry的registerComponent方法會為當前容器(如StandardServer)創(chuàng)建DynamicMBean钧汹,并且注冊到MBeanServer

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

步驟二 將StringCache、MBeanFactory录择、globalNamingResources注冊到JMX

其中StringCache的注冊名為Catalina:type=StringCache拔莱,MBeanFactory的注冊名為Catalina:type=MBeanFactory,globalNamingResources的注冊名為Catalina:type=NamingResources隘竭。

步驟三 初始化子容器

StandardServer主要對Service子容器進行初始化塘秦,默認是StandardService。

注意:個別容器并不完全遵循以上的初始化過程动看,比如ProtocolHandler作為Connector的子容器嗤形,其初始化過程并不是由Connector的initInternal方法調(diào)用的,而是與啟動過程一道被Connector的startInternal方法所調(diào)用弧圆。

容器啟動

每個容器的start方法是自身啟動的入口赋兵,其啟動過程如圖6所示笔咽。

圖6  容器啟動時序圖

根據(jù)圖6所示的啟動過程,我們對Tomcat的源碼進行分析霹期,其處理步驟如下:

  1. 調(diào)用方調(diào)用容器父類LifecycleBase的start方法叶组,LifecycleBase的start方法主要完成一些所有容器公共抽象出來的動作;
  2. LifecycleBase的start方法先將容器狀態(tài)改為LifecycleState.STARTING_PREP历造,然后調(diào)用具體容器的startInternal方法實現(xiàn)甩十,此startInternal方法用于對容器本身真正的初始化;
  3. 具體容器的startInternal方法會將容器狀態(tài)改為LifecycleState.STARTING吭产,容器如果有子容器侣监,會調(diào)用子容器的start方法啟動子容器;
  4. 容器啟動完畢臣淤,LifecycleBase會將容器的狀態(tài)更改為啟動完畢橄霉,即LifecycleState.STARTED。

現(xiàn)在對容器啟動的源碼進行分析邑蒋,start方法的實現(xiàn)

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

        try {
            setStateInternal(LifecycleState.STARTING_PREP, null, false);
            startInternal();
            if (state.equals(LifecycleState.FAILED)) {
                // This is a 'controlled' failure. The component put itself into the
                // FAILED state so call stop() to complete the clean-up.
                stop();
            } else if (!state.equals(LifecycleState.STARTING)) {
                // Shouldn't be necessary but acts as a check that sub-classes are
                // doing what they are supposed to.
                invalidTransition(Lifecycle.AFTER_START_EVENT);
            } else {
                setStateInternal(LifecycleState.STARTED, null, false);
            }
        } catch (Throwable t) {
            // This is an 'uncontrolled' failure so put the component into the
            // FAILED state and throw an exception.
            ExceptionUtils.handleThrowable(t);
            setStateInternal(LifecycleState.FAILED, null, false);
            throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
        }
    }

在真正啟動容器之前需要做2種檢查:

  1. 如果當前容器已經(jīng)處于啟動過程(即容器狀態(tài)為LifecycleState.STARTING_PREP姓蜂、LifecycleState.STARTING、LifecycleState.STARTED)中医吊,則會產(chǎn)生并且用日志記錄
  2. LifecycleException異常并退出钱慢。
    如果容器依然處于LifecycleState.NEW狀態(tài),則在啟動之前卿堂,首先確保初始化完畢束莫。

啟動容器完畢后,需要做1種檢查草描,即如果容器啟動異常導(dǎo)致容器進入LifecycleState.FAILED或者LifecycleState.MUST_STOP狀態(tài)麦箍,則需要調(diào)用stop方法停止容器。

現(xiàn)在我們重點分析startInternal方法陶珠,還是以StandardServer為例挟裂,其startInternal的實現(xiàn)

@Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();
        
        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }

上面代碼發(fā)現(xiàn)看到StandardServer的啟動由以下步驟組成:

  1. 產(chǎn)生CONFIGURE_START_EVENT事件;
  2. 將自身狀態(tài)更改為LifecycleState.STARTING揍诽;
  3. 調(diào)用子容器Service(默認為StandardService)的start方法啟動子容器诀蓉。
    除了初始化、啟動外暑脆,各個容器還有停止和銷毀的生命周期渠啤,其原理與初始化、啟動類似添吗,本文不再贅述沥曹,有興趣的讀者可以自行研究。

總結(jié)

Tomcat通過將內(nèi)部所有組件都抽象為容器,為容器提供統(tǒng)一的生命周期管理妓美,各個子容器只需要關(guān)心各自的具體實現(xiàn)僵腺,這便于Tomcat以后擴展更多的容器,對于研究或者學習Tomcat的人來說壶栋,其設(shè)計清晰易懂辰如。

鏈接:https://blog.csdn.net/beliefer/article/details/51473807

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贵试,隨后出現(xiàn)的幾起案子琉兜,更是在濱河造成了極大的恐慌,老刑警劉巖毙玻,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豌蟋,死亡現(xiàn)場離奇詭異,居然都是意外死亡桑滩,警方通過查閱死者的電腦和手機梧疲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來施符,“玉大人,你說我怎么就攤上這事擂找〈亮撸” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵贯涎,是天一觀的道長听哭。 經(jīng)常有香客問我,道長塘雳,這世上最難降的妖魔是什么陆盘? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮败明,結(jié)果婚禮上隘马,老公的妹妹穿的比我還像新娘。我一直安慰自己妻顶,他們只是感情好酸员,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讳嘱,像睡著了一般幔嗦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沥潭,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天邀泉,我揣著相機與錄音,去河邊找鬼。 笑死汇恤,一個胖子當著我的面吹牛庞钢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播屁置,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼焊夸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蓝角?” 一聲冷哼從身側(cè)響起阱穗,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎使鹅,沒想到半個月后揪阶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡患朱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年鲁僚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裁厅。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡冰沙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出执虹,到底是詐尸還是另有隱情拓挥,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布袋励,位于F島的核電站侥啤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏茬故。R本人自食惡果不足惜盖灸,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磺芭。 院中可真熱鬧赁炎,春花似錦、人聲如沸钾腺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垮庐。三九已至松邪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哨查,已是汗流浹背逗抑。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邮府。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓荧关,卻偏偏與公主長得像,于是被迫代替她去往敵國和親褂傀。 傳聞我的和親對象是個殘疾皇子忍啤,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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