1. 程式人生 > >【Web容器】Tomcat原始碼分析(3)-生命週期管理

【Web容器】Tomcat原始碼分析(3)-生命週期管理

前言

  從server.xml檔案解析出來的各個物件都是容器,比如:Server、Service、Connector等。這些容器都具有新建、初始化完成、啟動、停止、失敗、銷燬等狀態。tomcat的實現提供了對這些容器的生命週期管理,本文將通過對Tomcat7.0的原始碼閱讀,深入剖析這一過程。

Tomcat生命週期類介面設計

  我們先閱讀圖1,從中瞭解Tomcat涉及生命週期管理的主要類。

圖1  Tomcat生命週期類介面設計


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

  • Lifecycle:定義了容器生命週期、容器狀態轉換及容器狀態遷移事件的監聽器註冊和移除等主要介面;
  • LifecycleBase:作為Lifecycle介面的抽象實現類,運用抽象模板模式將所有容器的生命週期及狀態轉換銜接起來,此外還提供了生成LifecycleEvent事件的介面;
  • LifecycleSupport:提供有關LifecycleEvent事件的監聽器註冊、移除,並且使用經典的監聽器模式,實現事件生成後觸打監聽器的實現;
  • MBeanRegistration:Java jmx框架提供的註冊MBean的介面,引入此介面是為了便於使用JMX提供的管理功能;
  • LifecycleMBeanBase:Tomcat提供的對MBeanRegistration的抽象實現類,運用抽象模板模式將所有容器統一註冊到JMX;

此外,ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都繼承了LifecycleMBeanBase,因此這些容器都具有了同樣的生命週期並可以通過JMX進行管理。

什麼是JMX?

  java管理程式擴充套件(java management extensions,簡稱JMX),是一個可以為Java應用程式或系統植入遠端管理功能的框架。為便於講解,我從網路上找了一張JMX的架構,如圖2所示。

圖2  JMX架構

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

  • Probe Level:負責資源的檢測(獲取資訊),包含MBeans,通常也叫做Instrumentation Level。MX管理構件(MBean)分為四種形式,分別是標準管理構件(Standard MBean)、動態管理構件(Dynamic MBean)、開放管理構件(Open Mbean)和模型管理構件(Model MBean)。
  • Agent Level:即MBeanServer,是JMX的核心,負責連線Mbeans和應用程式。 
  • Remote Management Level:通過connectors和adaptors來遠端操作MBeanServer,常用的控制檯,例如JConsole、VisualVM等。

容器

Tomcat容器組成

  StandardServer、StandardService、Connector、StandardContext這些容器,彼此之間都有父子關係,每個容器都可能包含零個或者多個子容器,這些子容器可能存在不同型別或者相同型別的多個,如圖3所示。

圖3  Tomcat容器組成

Tomcat容器狀態

  目前,Tomcat的容器具有以下狀態:

  • NEW:容器剛剛建立時,即在LifecycleBase例項構造完成時的狀態。
  • INITIALIZED:容器初始化完成時的狀態。
  • STARTING_PREP:容器啟動前的狀態。
  • STARTING:容器啟動過程中的狀態。
  • STARTED:容器啟動完成的狀態。
  • STOPPING_PREP:容器停止前的狀態。
  • STOPPING:容器停止過程中的狀態。
  • STOPPED:容器停止完成的狀態。
  • DESTROYED:容器銷燬後的狀態。
  • FAILED:容器啟動、停止過程中出現異常的狀態。
  • MUST_STOP:此狀態未使用。
  • MUST_DESTROY:此狀態未使用。

這些狀態都定義在列舉類LifecycleState中。

事件與監聽

  每個容器由於繼承自LifecycleBase,當容器狀態發生變化時,都會呼叫fireLifecycleEvent方法,生成LifecycleEvent,並且交由此容器的事件監聽器處理。LifecycleBase的fireLifecycleEvent方法的實現見程式碼清單1。

程式碼清單1

  1. /** 
  2.  * Allow sub classes to fire {@link Lifecycle} events. 
  3.  *  
  4.  * @param type  Event type 
  5.  * @param data  Data associated with event. 
  6.  */  
  7. protected void fireLifecycleEvent(String type, Object data) {  
  8.     lifecycle.fireLifecycleEvent(type, data);  
  9. }  

lifecycle的定義如下:

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

LifecycleSupport的fireLifecycleEvent方法的實現,見程式碼清單2。

程式碼清單2

  1. /** 
  2.  * Notify all lifecycle event listeners that a particular event has 
  3.  * occurred for this Container.  The default implementation performs 
  4.  * this notification synchronously using the calling thread. gja 
  5.  * 
  6.  * @param type Event type 
  7.  * @param data Event data 
  8.  */  
  9. public void fireLifecycleEvent(String type, Object data) {  
  10.     LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);  
  11.     LifecycleListener interested[] = listeners;  
  12.     for (int i = 0; i < interested.length; i++)  
  13.         interested[i].lifecycleEvent(event);  
  14. }  

程式碼清單2將事件通知給所有監聽當前容器的生命週期監聽器LifecycleListener,並呼叫LifecycleListener的lifecycleEvent方法。每個容器都維護這一個監聽器快取,其實現如下:

  1. /** 
  2.  * The set of registered LifecycleListeners for event notifications. 
  3.  */  
  4. private LifecycleListener listeners[] = new LifecycleListener[0];  

那麼listeners中的監聽器是何時新增進來的呢?每個容器在新建、初始化、啟動,銷燬,被新增到父容器的過程中都會呼叫父類LifecycleBase的addLifecycleListener方法,addLifecycleListener的實現見程式碼清單3。

程式碼清單3

  1. @Override  
  2. public void addLifecycleListener(LifecycleListener listener) {  
  3.     lifecycle.addLifecycleListener(listener);  
  4. }  

從程式碼清單3看到,LifecycleBase的addLifecycleListener方法實際是對LifecycleSupport的addLifecycleListener方法的簡單代理,LifecycleSupport的addLifecycleListener方法的實現,見程式碼清單4。

 程式碼清單4

  1. /** 
  2.  * Add a lifecycle event listener to this component. 
  3.  * 
  4.  * @param listener The listener to add 
  5.  */  
  6. public void addLifecycleListener(LifecycleListener listener) {  
  7.   synchronized (listenersLock) {  
  8.       LifecycleListener results[] =  
  9.         new LifecycleListener[listeners.length + 1];  
  10.       for (int i = 0; i < listeners.length; i++)  
  11.           results[i] = listeners[i];  
  12.       results[listeners.length] = listener;  
  13.       listeners = results;  
  14.   }  
  15. }  

在程式碼清單2中,我們講過容器會最終呼叫每個對此容器感興趣的LifecycleListener的lifecycleEvent方法,那麼LifecycleListener的lifecycleEvent方法會做些什麼呢?為了簡單起見,我們以監聽器AprLifecycleListener為例,AprLifecycleListener的lifecycleEvent方法的實現,見程式碼清單5。

程式碼清單5

  1. /** 
  2.  * Primary entry point for startup and shutdown events. 
  3.  * 
  4.  * @param event The event that has occurred 
  5.  */  
  6. public void lifecycleEvent(LifecycleEvent event) {  
  7.     if (Lifecycle.INIT_EVENT.equals(event.getType())) {  
  8.         synchronized (lock) {  
  9.             init();  
  10.             if (aprAvailable) {  
  11.                 try {  
  12.                     initializeSSL();  
  13.                 } catch (Throwable t) {  
  14.                     log.info(sm.getString("aprListener.sslInit"));  
  15.                 }  
  16.             }  
  17.         }  
  18.     } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {  
  19.         synchronized (lock) {  
  20.             if (!aprAvailable) {  
  21.                 return;  
  22.             }  
  23.             try {  
  24.                 terminateAPR();  
  25.             } catch (Throwable t) {  
  26.                 log.info(sm.getString("aprListener.aprDestroy"));  
  27.             }  
  28.         }  
  29.     }  
  30. }  

容器生命週期

  每個容器都會有自身的生命週期,其中也涉及狀態的遷移,以及伴隨的事件生成,本節詳細介紹Tomcat中的容器生命週期實現。所有容器的轉態轉換(如新疆、初始化、啟動、停止等)都是由外到內,由上到下進行,即先執行父容器的狀態轉換及相關操作,然後再執行子容器的轉態轉換,這個過程是層層迭代執行的。

容器新建

  所有容器在構造的過程中,都會首先對父類LifecycleBase進行構造。LifecycleBase中定義了所有容器的起始狀態為LifecycleState.NEW,程式碼如下:

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

容器初始化

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

圖4  容器初始化時序圖

  圖4中所說的具體容器,實際就是LifecycleBase的具體實現類,目前LifecycleBase的類繼承體系如圖5所示。

圖5  LifecycleBase的類繼承體系


  根據圖4所示的初始化過程,我們對Tomcat的原始碼進行分析,其處理步驟如下:

  1. 呼叫方呼叫容器父類LifecycleBase的init方法,LifecycleBase的init方法主要完成一些所有容器公共抽象出來的動作;
  2. LifecycleBase的init方法呼叫具體容器的initInternal方法實現,此initInternal方法用於對容器本身真正的初始化;
  3. 具體容器的initInternal方法呼叫父類LifecycleMBeanBase的initInternal方法實現,此initInternal方法用於將容器託管到JMX,便於運維管理;
  4. LifecycleMBeanBase的initInternal方法呼叫自身的register方法,將容器作為MBean註冊到MBeanServer;
  5. 容器如果有子容器,會呼叫子容器的init方法;
  6. 容器初始化完畢,LifecycleBase會將容器的狀態更改為初始化完畢,即LifecycleState.INITIALIZED。

  現在對容器初始化的原始碼進行分析,init方法的實現見程式碼清單6。

程式碼清單6

  1. public synchronized final void init() throws LifecycleException {  
  2.     if (!state.equals(LifecycleState.NEW)) {  
  3.         invalidTransition(Lifecycle.INIT_EVENT);  
  4.     }  
  5.     initInternal();  
  6.     setState(LifecycleState.INITIALIZED);  
  7. }  


程式碼清單6說明,只有當前容器的狀態處於LifecycleState.NEW的才可以被初始化,真正執行初始化的方法是initInternal,當初始化完畢,當前容器的狀態會被更改為LifecycleState.INITIALIZED。為了簡便起見,我們還是以StandardServer這個容器為例,StandardServer的initInternal方法的實現見程式碼清單7。

程式碼清單7

  1. @Override  
  2. protected void initInternal() throws LifecycleException {  
  3.     super.initInternal();  
  4.     // Register global String cache geng  
  5.     // Note although the cache is global, if there are multiple Servers  
  6.     // present in the JVM (may happen when embedding) then the same cache   
  7.     // will be registered under multiple names  
  8.     onameStringCache = register(new StringCache(), "type=StringCache");  
  9.     // Register the MBeanFactory  
  10.     onameMBeanFactory = register(new MBeanFactory(), "type=MBeanFactory");  
  11.     // Register the naming resources  
  12.     onameNamingResoucres = register(globalNamingResources,  
  13.             "type=NamingResources");  
  14.     // Initialize our defined Services  
  15.     for (int i = 0; i < services.length; i++) {  
  16.         services[i].init();  
  17.     }  
  18. }  


通過分析StandardServer的initInternal方法,其處理過程如下:

步驟一 將當前容器註冊到JMX

  呼叫父類LifecycleBase的initInternal方法(見程式碼清單8),為當前容器建立DynamicMBean,並註冊到JMX中。

 程式碼清單8

  1. @Override  
  2. protected void initInternal() throws LifecycleException {  
  3.     // If oname is not null then registration has already happened via jiaan  
  4.     // preRegister().  
  5.     if (oname == null) {  
  6.         mserver = Registry.getRegistry(nullnull).getMBeanServer();  
  7.         oname = register(this, getObjectNameKeyProperties());  
  8.     }  
  9. }  


StandardServer實現的getObjectNameKeyProperties方法如下:

  1. @Override  
  2. protected final String getObjectNameKeyProperties() {  
  3.     return "type=Server";  
  4. }  


LifecycleBase的register方法(見程式碼清單9)會為當前容器建立對應的註冊名稱,以StandardServer為例,getDomain預設返回Catalina,因此StandardServer的JMX註冊名稱預設為Catalina:type=Server,真正的註冊在registerComponent方法中實現。

程式碼清單9

  1. protected final ObjectName register(Object obj,  
  2.         String objectNameKeyProperties) {  
  3.     // Construct an object name with the right domain  
  4.     StringBuilder name = new StringBuilder(getDomain());  
  5.     name.append(':');  
  6.     name.append(objectNameKeyProperties);  
  7.     ObjectName on = null;  
  8.     try {  
  9.         on = new ObjectName(name.toString());  
  10.         Registry.getRegistry(nullnull).registerComponent(obj, on, null);  
  11.     } catch (MalformedObjectNameException e) {  
  12.         log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),  
  13.                 e);  
  14.     } catch (Exception e) {  
  15.         log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name),  
  16.                 e);  
  17.     }  
  18.     return on;  
  19. }  


Registry的registerComponent方法會為當前容器(如StandardServer)建立DynamicMBean,並且註冊到MBeanServer,見程式碼清單10。

程式碼清單10

  1. /** Register a component  
  2.  * XXX make it private  
  3.  *  
  4.  * @param bean 
  5.  * @param oname 
  6.  * @param type 
  7.  * @throws Exception 
  8.  */   
  9. public void registerComponent(Object bean, ObjectName oname, String type)  
  10.        throws Exception  
  11. {  
  12.     if( log.isDebugEnabled() ) {  
  13.         log.debug( "Managed= "+ oname);  
  14.     }  
  15.     if( bean ==null ) {  
  16.         log.error("Null component " + oname );  
  17.         return;  
  18.     }  
  19.     try {  
  20.         if( type==null ) {  
  21.             type=bean.getClass().getName();  
  22.         }  
  23.         ManagedBean managed = findManagedBean(bean.getClass(), type);  
  24.         // The real mbean is created and registered  
  25.         DynamicMBean mbean = managed.createMBean(bean);  
  26.         if(  getMBeanServer().isRegistered( oname )) {  
  27.             if( log.isDebugEnabled()) {  
  28.                 log.debug("Unregistering existing component " + oname );  
  29.             }  
  30.             getMBeanServer().unregisterMBean( oname );  
  31.         }  
  32.         getMBeanServer().registerMBean( mbean, oname);  
  33.     } catch( Exception ex) {  
  34.         log.error("Error registering " + oname, ex );  
  35.         throw ex;  
  36.     }  
  37. }  

步驟二 將StringCache、MBeanFactory、globalNamingResources註冊到JMX

  從程式碼清單7中已經列出。其中StringCache的註冊名為Catalina:type=StringCache,MBeanFactory的註冊名為Catalina:type=MBeanFactory,globalNamingResources的註冊名為Catalina:type=NamingResources。

步驟三 初始化子容器

  從程式碼清單7中看到StandardServer主要對Service子容器進行初始化,預設是StandardService。

注意:個別容器並不完全遵循以上的初始化過程,比如ProtocolHandler作為Connector的子容器,其初始化過程並不是由Connector的initInternal方法呼叫的,而是與啟動過程一道被Connector的startInternal方法所呼叫。

容器啟動

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

圖6  容器啟動時序圖


  根據圖6所示的啟動過程,我們對Tomcat的原始碼進行分析,其處理步驟如下:

  1. 呼叫方呼叫容器父類LifecycleBase的start方法,LifecycleBase的start方法主要完成一些所有容器公共抽象出來的動作;
  2. LifecycleBase的start方法先將容器狀態改為LifecycleState.STARTING_PREP,然後呼叫具體容器的startInternal方法實現,此startInternal方法用於對容器本身真正的初始化;
  3. 具體容器的startInternal方法會將容器狀態改為LifecycleState.STARTING,容器如果有子容器,會呼叫子容器的start方法啟動子容器;
  4. 容器啟動完畢,LifecycleBase會將容器的狀態更改為啟動完畢,即LifecycleState.STARTED。

  現在對容器啟動的原始碼進行分析,start方法的實現見程式碼清單11。

程式碼清單11

  1. @Override  
  2. public synchronized final void start() throws LifecycleException {  
  3.     if (LifecycleState.STARTING_PREP.equals(state) ||  
  4.             LifecycleState.STARTING.equals(state) ||  
  5.             LifecycleState.STARTED.equals(state)) {  
  6.         if (log.isDebugEnabled()) {  
  7.             Exception e = new LifecycleException();  
  8.             log.debug(sm.getString("lifecycleBase.alreadyStarted",  
  9.                     toString()), e);  
  10.         } else if (log.isInfoEnabled()) {  
  11.             log.info(sm.getString("lifecycleBase.alreadyStarted",  
  12.                     toString()));  
  13.         }  
  14.         return;  
  15.     }  
  16.     if (state.equals(LifecycleState.NEW)) {  
  17.         init();  
  18.     } else if (!state.equals(LifecycleState.INITIALIZED) &&  
  19.             !state.equals(LifecycleState.STOPPED)) {  
  20.         invalidTransition(Lifecycle.BEFORE_START_EVENT);  
  21.     }  
  22.     setState(LifecycleState.STARTING_PREP);  
  23.     try {  
  24.         startInternal();  
  25.     } catch (LifecycleException e) {  
  26.         setState(LifecycleState.FAILED);  
  27.         throw e;  
  28.     }  
  29.     if (state.equals(LifecycleState.FAILED) ||  
  30.             state.equals(LifecycleState.MUST_STOP)) {  
  31.         stop();  
  32.     } else {  
  33.         // Shouldn't be necessary but acts as a check that sub-classes are  
  34.         // doing what they are supposed to.  
  35.         if (!state.equals(LifecycleState.STARTING)) {  
  36.             invalidTransition(Lifecycle.AFTER_START_EVENT);  
  37.         }  
  38.         setState(LifecycleState.STARTED);  
  39.     }  
  40. }  


程式碼清單11說明在真正啟動容器之前需要做2種檢查:

  1. 如果當前容器已經處於啟動過程(即容器狀態為LifecycleState.STARTING_PREP、LifecycleState.STARTING、LifecycleState.STARTED)中,則會產生並且用日誌記錄LifecycleException異常並退出。
  2. 如果容器依然處於LifecycleState.NEW狀態,則在啟動之前,首先確保初始化完畢。

程式碼清單11還說明啟動容器完畢後,需要做1種檢查,即如果容器啟動異常導致容器進入LifecycleState.FAILED或者LifecycleState.MUST_STOP狀態,則需要呼叫stop方法停止容器。
  現在我們重點分析startInternal方法,還是以StandardServer為例,其startInternal的實現見程式碼清單12所示。

程式碼清單12

  1. @Override  
  2. protected void startInternal() throws LifecycleException {  
  3.     fireLifecycleEvent(CONFIGURE_START_EVENT, null);  
  4.     setState(LifecycleState.STARTING);  
  5.     // Start our defined Services  
  6.     synchronized (services) {  
  7.         for (int i = 0; i < services.length; i++) {  
  8.             services[i].start();  
  9.         }  
  10.     }  
  11. }  


  從程式碼清單12看到StandardServer的啟動由以下步驟組成:

  1. 產生CONFIGURE_START_EVENT事件;
  2. 將自身狀態更改為LifecycleState.STARTING;
  3. 呼叫子容器Service(預設為StandardService)的start方法啟動子容器。

除了初始化、啟動外,各個容器還有停止和銷燬的生命週期,其原理與初始化、啟動類似,本文不再贅述,有興趣的讀者可以自行研究。


  Tomcat啟動完畢後,開啟Java visualVM,開啟Tomcat程序監控,給visualVM安裝MBeans外掛後,選擇MBeans標籤頁可以對Tomcat所有註冊到JMX中的物件進行管理,比如StandardService就向JMX暴露了start和stop等方法,這樣管理員就可以動態管理Tomcat,如圖7所示。

圖7  使用JMX動態管理Tomcat

總結

  Tomcat通過將內部所有元件都抽象為容器,為容器提供統一的生命週期管理,各個子容器只需要關心各自的具體實現,這便於Tomcat以後擴充套件更多的容器,對於研究或者學習Tomcat的人來說,其設計清晰易懂。

歡迎關注公眾號: