【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
- /**
- * 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方法的實現,見程式碼清單2。
程式碼清單2
- /**
- * 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. gja
- *
- * @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);
- }
程式碼清單2將事件通知給所有監聽當前容器的生命週期監聽器LifecycleListener,並呼叫LifecycleListener的lifecycleEvent方法。每個容器都維護這一個監聽器快取,其實現如下:
- /**
- * The set of registered LifecycleListeners for event notifications.
- */
- private LifecycleListener listeners[] = new LifecycleListener[0];
那麼listeners中的監聽器是何時新增進來的呢?每個容器在新建、初始化、啟動,銷燬,被新增到父容器的過程中都會呼叫父類LifecycleBase的addLifecycleListener方法,addLifecycleListener的實現見程式碼清單3。
程式碼清單3
- @Override
- public void addLifecycleListener(LifecycleListener listener) {
- lifecycle.addLifecycleListener(listener);
- }
從程式碼清單3看到,LifecycleBase的addLifecycleListener方法實際是對LifecycleSupport的addLifecycleListener方法的簡單代理,LifecycleSupport的addLifecycleListener方法的實現,見程式碼清單4。
程式碼清單4
- /**
- * Add a lifecycle event listener to this component.
- *
- * @param listener The listener to add
- */
- public void addLifecycleListener(LifecycleListener listener) {
- synchronized (listenersLock) {
- LifecycleListener results[] =
- new LifecycleListener[listeners.length + 1];
- for (int i = 0; i < listeners.length; i++)
- results[i] = listeners[i];
- results[listeners.length] = listener;
- listeners = results;
- }
- }
在程式碼清單2中,我們講過容器會最終呼叫每個對此容器感興趣的LifecycleListener的lifecycleEvent方法,那麼LifecycleListener的lifecycleEvent方法會做些什麼呢?為了簡單起見,我們以監聽器AprLifecycleListener為例,AprLifecycleListener的lifecycleEvent方法的實現,見程式碼清單5。
程式碼清單5
- /**
- * Primary entry point for startup and shutdown events.
- *
- * @param event The event that has occurred
- */
- public void lifecycleEvent(LifecycleEvent event) {
- if (Lifecycle.INIT_EVENT.equals(event.getType())) {
- synchronized (lock) {
- init();
- if (aprAvailable) {
- try {
- initializeSSL();
- } catch (Throwable t) {
- log.info(sm.getString("aprListener.sslInit"));
- }
- }
- }
- } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {
- synchronized (lock) {
- if (!aprAvailable) {
- return;
- }
- try {
- terminateAPR();
- } catch (Throwable t) {
- log.info(sm.getString("aprListener.aprDestroy"));
- }
- }
- }
- }
容器生命週期
每個容器都會有自身的生命週期,其中也涉及狀態的遷移,以及伴隨的事件生成,本節詳細介紹Tomcat中的容器生命週期實現。所有容器的轉態轉換(如新疆、初始化、啟動、停止等)都是由外到內,由上到下進行,即先執行父容器的狀態轉換及相關操作,然後再執行子容器的轉態轉換,這個過程是層層迭代執行的。
容器新建
所有容器在構造的過程中,都會首先對父類LifecycleBase進行構造。LifecycleBase中定義了所有容器的起始狀態為LifecycleState.NEW,程式碼如下:
- /**
- * The current state of the source component.
- */
- private volatile LifecycleState state = LifecycleState.NEW;
容器初始化
每個容器的init方法是自身初始化的入口,其初始化過程如圖4所示。
圖4 容器初始化時序圖
圖4中所說的具體容器,實際就是LifecycleBase的具體實現類,目前LifecycleBase的類繼承體系如圖5所示。
圖5 LifecycleBase的類繼承體系
根據圖4所示的初始化過程,我們對Tomcat的原始碼進行分析,其處理步驟如下:
- 呼叫方呼叫容器父類LifecycleBase的init方法,LifecycleBase的init方法主要完成一些所有容器公共抽象出來的動作;
- LifecycleBase的init方法呼叫具體容器的initInternal方法實現,此initInternal方法用於對容器本身真正的初始化;
- 具體容器的initInternal方法呼叫父類LifecycleMBeanBase的initInternal方法實現,此initInternal方法用於將容器託管到JMX,便於運維管理;
- LifecycleMBeanBase的initInternal方法呼叫自身的register方法,將容器作為MBean註冊到MBeanServer;
- 容器如果有子容器,會呼叫子容器的init方法;
- 容器初始化完畢,LifecycleBase會將容器的狀態更改為初始化完畢,即LifecycleState.INITIALIZED。
現在對容器初始化的原始碼進行分析,init方法的實現見程式碼清單6。
程式碼清單6
- public synchronized final void init() throws LifecycleException {
- if (!state.equals(LifecycleState.NEW)) {
- invalidTransition(Lifecycle.INIT_EVENT);
- }
- initInternal();
- setState(LifecycleState.INITIALIZED);
- }
程式碼清單6說明,只有當前容器的狀態處於LifecycleState.NEW的才可以被初始化,真正執行初始化的方法是initInternal,當初始化完畢,當前容器的狀態會被更改為LifecycleState.INITIALIZED。為了簡便起見,我們還是以StandardServer這個容器為例,StandardServer的initInternal方法的實現見程式碼清單7。
程式碼清單7
- @Override
- protected void initInternal() throws LifecycleException {
- super.initInternal();
- // Register global String cache geng
- // 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
- onameMBeanFactory = register(new MBeanFactory(), "type=MBeanFactory");
- // Register the naming resources
- onameNamingResoucres = register(globalNamingResources,
- "type=NamingResources");
- // Initialize our defined Services
- for (int i = 0; i < services.length; i++) {
- services[i].init();
- }
- }
通過分析StandardServer的initInternal方法,其處理過程如下:
步驟一 將當前容器註冊到JMX
呼叫父類LifecycleBase的initInternal方法(見程式碼清單8),為當前容器建立DynamicMBean,並註冊到JMX中。
程式碼清單8
- @Override
- protected void initInternal() throws LifecycleException {
- // If oname is not null then registration has already happened via jiaan
- // preRegister().
- if (oname == null) {
- mserver = Registry.getRegistry(null, null).getMBeanServer();
- oname = register(this, getObjectNameKeyProperties());
- }
- }
StandardServer實現的getObjectNameKeyProperties方法如下:
- @Override
- protected final String getObjectNameKeyProperties() {
- return "type=Server";
- }
LifecycleBase的register方法(見程式碼清單9)會為當前容器建立對應的註冊名稱,以StandardServer為例,getDomain預設返回Catalina,因此StandardServer的JMX註冊名稱預設為Catalina:type=Server,真正的註冊在registerComponent方法中實現。
程式碼清單9
- 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)建立DynamicMBean,並且註冊到MBeanServer,見程式碼清單10。
程式碼清單10
- /** Register a component
- * XXX make it private
- *
- * @param bean
- * @param oname
- * @param type
- * @throws Exception
- */
- 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
從程式碼清單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的原始碼進行分析,其處理步驟如下:
- 呼叫方呼叫容器父類LifecycleBase的start方法,LifecycleBase的start方法主要完成一些所有容器公共抽象出來的動作;
- LifecycleBase的start方法先將容器狀態改為LifecycleState.STARTING_PREP,然後呼叫具體容器的startInternal方法實現,此startInternal方法用於對容器本身真正的初始化;
- 具體容器的startInternal方法會將容器狀態改為LifecycleState.STARTING,容器如果有子容器,會呼叫子容器的start方法啟動子容器;
- 容器啟動完畢,LifecycleBase會將容器的狀態更改為啟動完畢,即LifecycleState.STARTED。
現在對容器啟動的原始碼進行分析,start方法的實現見程式碼清單11。
程式碼清單11
- @Override
- public synchronized final 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.INITIALIZED) &&
- !state.equals(LifecycleState.STOPPED)) {
- invalidTransition(Lifecycle.BEFORE_START_EVENT);
- }
- setState(LifecycleState.STARTING_PREP);
- try {
- startInternal();
- } catch (LifecycleException e) {
- setState(LifecycleState.FAILED);
- throw e;
- }
- 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);
- }
- setState(LifecycleState.STARTED);
- }
- }
程式碼清單11說明在真正啟動容器之前需要做2種檢查:
- 如果當前容器已經處於啟動過程(即容器狀態為LifecycleState.STARTING_PREP、LifecycleState.STARTING、LifecycleState.STARTED)中,則會產生並且用日誌記錄LifecycleException異常並退出。
- 如果容器依然處於LifecycleState.NEW狀態,則在啟動之前,首先確保初始化完畢。
程式碼清單11還說明啟動容器完畢後,需要做1種檢查,即如果容器啟動異常導致容器進入LifecycleState.FAILED或者LifecycleState.MUST_STOP狀態,則需要呼叫stop方法停止容器。
現在我們重點分析startInternal方法,還是以StandardServer為例,其startInternal的實現見程式碼清單12所示。
程式碼清單12
- @Override
- protected void startInternal() throws LifecycleException {
- fireLifecycleEvent(CONFIGURE_START_EVENT, null);
- setState(LifecycleState.STARTING);
- // Start our defined Services
- synchronized (services) {
- for (int i = 0; i < services.length; i++) {
- services[i].start();
- }
- }
- }
從程式碼清單12看到StandardServer的啟動由以下步驟組成:
- 產生CONFIGURE_START_EVENT事件;
- 將自身狀態更改為LifecycleState.STARTING;
- 呼叫子容器Service(預設為StandardService)的start方法啟動子容器。
除了初始化、啟動外,各個容器還有停止和銷燬的生命週期,其原理與初始化、啟動類似,本文不再贅述,有興趣的讀者可以自行研究。
Tomcat啟動完畢後,開啟Java visualVM,開啟Tomcat程序監控,給visualVM安裝MBeans外掛後,選擇MBeans標籤頁可以對Tomcat所有註冊到JMX中的物件進行管理,比如StandardService就向JMX暴露了start和stop等方法,這樣管理員就可以動態管理Tomcat,如圖7所示。
圖7 使用JMX動態管理Tomcat
總結
Tomcat通過將內部所有元件都抽象為容器,為容器提供統一的生命週期管理,各個子容器只需要關心各自的具體實現,這便於Tomcat以後擴充套件更多的容器,對於研究或者學習Tomcat的人來說,其設計清晰易懂。
歡迎關注公眾號: