本文主要結合觀察者模式,講述Tomcat的生命周期管理。Tomcat的生命周期管理機制設計的非常優雅,在Tomcat啟動時,只需要啟動一個Server組件,就會啟動所有的容器及對應的組件,並且觸發這些容器的監聽者,完成啟動過程的設置。可以說是“一鍵式”啟動的。停止過程也是一樣。
本文首先簡單介紹Tomcat中容器,組件及監聽類的功能。因為Tomcat的生命周期管理應用了觀察者模式,所以接下來會分析觀察者模式,最后結合觀察者模式及Tomcat源代碼,詳細說明Tomcat的生命周期管理。
一、幾種基本的容器,組件及事件監聽類(Listener)
1. Tomcat中有四種不同的容器:
-
Engine:代表整個Catalina servle引擎
-
Host:代表虛擬主機
-
Context:代表某個web應用
-
Wrapper:代表某個應用中的servlet
這些容器都擴展了Container接口(譯為:容器,這也是為什么一般都稱tomcat為容器而不是服務器的原因之一吧~)。更重要的是,這些容器都是父子的關系,Engine位於最頂層,一個Engine包含多個Host,一個Host(虛擬主機)包含多個Context(web應用),一個Context(web 應用)包含多個Wrapper(servlet),Wrapper位於最底層,沒有孩子。當父容器啟動時,相應的子容器也應該啟動,子容器的子容器也啟動。如此,只需要啟動最頂層的容器,就會啟動所有的子容器。
2. Tomcat的容器中有很多組件,如:
-
Logger:負責記錄各種事件
-
Loader:負責加載類文件,如加載應用程序中的Servlet
-
Manager:負責管理session
-
Realm: 負責用戶驗證與授權
-
Pipeline:負責完成容器invoke方法的調用,對請求進行處理(責任鏈模式的經典應用)。
當tomcat容器啟動時,這些組件也要啟動,進行初始化。當容器停止時,這些組件也應該停止,進行相應的清理工作。比如管理用戶session的Manager組件,在tomcat停止時,會將這些session序列化到sessions.ser文件中(位於%tomcat_home%/work/web appcation/sessions.ser)。下一次啟動tomcat時,manager會將這個文件中的session反序列化到內存,將刪除sessions.ser文件。這也是為什么重啟tomcat時,正在訪問網站的用戶不用重新登錄的原因。
3. 另外,還有一些類,它們並不像容器的基本組件(如Logger, Loader, Manager)一樣,為容器的整個生命周期所調用,而僅僅對容器的某幾個特定事件感興趣。如:
-
ContextConfig: Context的收聽者,在Context(web 應用)啟動時,ContextConfig對web應用程序的配置文件web.xml進行分析,為Context生成Wrapper等對象,並與Context關聯。在Context停止時,為Context清除這些關聯的對象。
-
HostConfig: Host的收聽者,在Host(虛擬主機)啟動時,HostConfig會自動的為Host部署放置在webapps中的web應用程序。在Host停止時,為Host清除這些關聯的對象。
-
EngineConfig:Engine的收聽者,這個對比較簡單,僅僅是在Engine啟動與停止時做一些簡單的記錄。
這些監聽類如何監聽容器的特定事件呢?如何在特定事件發現時,調用監聽類的特定方法以完成某些設置呢?如果理解了觀察者模式,便能輕易的理解Tomcat的整個生命周期管理了。
二、觀察者模式:
觀察者模式又叫做發布-訂閱(Publish/Subscribe)模式、源-監聽(Source/Listener)模式。它定義了一種一對多的依賴關系,一個主題,多個觀察者,當主題發生變化的時候,會主動的通知觀察者,這樣觀察者便能針對主題發生的變化,執行某些對應的動作。觀察者模式的應用非常廣泛,如Java AWT事件模型,Servlet的監聽器,Spring事件處理機制以及本文所講述的Tomcat生命周期管理機制等等;應用如此的廣泛,以至於Java直接提供API的支持,java.util.Observable和java.util.Observer;
觀察者模式的結構:
觀察者模式包括以下角色:
抽象主題(Observable):定義了管理觀察者的添加,刪除和通知方法。
抽象觀察者(Observer):定義了主題發生變化時,具體觀察者必須執行的方法。
具體主題(Container):對觀察者進行管理,並在自身發生變化時,通知觀察者。
具體觀察者(ContainerConfig):實現了當主題發生變化時,應該執行的動作。
下面是觀察者模式的示例代碼:
package com.scnulh.observer;
抽象主題:
- /**
- * 抽象主題
- */
- public interface Observable {
- //添加觀察者
- void addObserver(Observer observer);
- //刪除觀察者
- void deleteObserver(Observer observer);
- //在事件發生時,通知觀察者
- void notifyObservers();
- }
抽象觀察者:
- package com.scnulh.observer;
- /**
- * 抽象觀察者
- */
- public interface Observer {
- /**
- * 更新方法,觀察者根據傳入的主題對象獲取主題的上下文
- * 根據傳入的Object對象判斷發生了何種事件
- * @param observable
- * @param arg
- */
- void update(Observable observable,Object arg);
- }
具體主題:
- package com.scnulh.observer;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * 具體主題,假設這是一個Tomcat的容器基類
- * 有一個start方法,代表容器的啟動,在啟動過程中
- * 通知所有觀察者
- */
- public class ContainerBase implements Observable{
- //持有觀察者的List
- private List<Observer> observers=new ArrayList<Observer>();
- //添加觀察者
- @Override
- public void addObserver(Observer observer) {
- observers.add(observer);
- }
- //刪除觀察者
- @Override
- public void deleteObserver(Observer observer) {
- observers.remove(observer);
- }
- //通知所有觀察者
- @Override
- public void notifyObservers() {
- for(Observer observer:observers)
- {
- observer.update(this, "start");
- }
- }
- //容器的啟動方法,啟動容器並調用notifyObservers方法通知所有觀察者
- public void start()
- {
- System.out.println("container start");
- notifyObservers();
- }
- public static void main(String[] args) {
- ContainerBase container=new ContainerBase();//聲明一個容器
- Observer observer=new ContainerConfig(); //聲明一個監聽類
- container.addObserver(observer); //為容器添加監聽類
- container.start(); //啟動容器
- }
- }
具體觀察者:
- package com.scnulh.observer;
- /**
- * 具體觀察者,假設這是一個容器的監聽類,
- * 在tomcat容器啟動時,處理tomcat的配置
- */
- public class ContainerConfig implements Observer{
- @Override
- public void update(Observable observable, Object arg) {
- String event=(String) arg;
- if(event.equals("start"))
- {
- System.out.println("container starting, do container configs");
- }
- }
- }
上述便是觀察者模式的簡單示例,之所以用ContainerBase和ContainerConfig作為具體主題和觀察者,是因為后面要分析tomcat的容器(Container)和監聽類(Config)的源代碼,這里先模擬下他們的工作方式。
細心的讀者很快就會發現,在具體主題ContainerBaser中,對觀察者的管理方法其實是很固定的,無非就是聲明一個Observer的集合,提供添加,刪除,查找的方法。甚至連在主題發生變化時,通知觀察者的方法也是固定的,即輪循的通知每一個觀察者。如果每一個實現了主題接口的具體主題都要實現這些方法,無疑會造成重復,帶來代碼編寫上的麻煩。為了消除重復,減少麻煩,可以提供一個類,實現主題對觀察者的管理及通知。這正是java util包里Observable與Observer所做的。感興趣的讀者可以出看看,這里就不貼代碼了。Tomcat沒有直接使用這個Observable類,而是另外實現了一個LifecycleSupport類。
總的來說,觀察者模式還是很好理解的,要讓觀察者模式用於實際,關鍵有兩點,一點要提供主題與觀察者的實現,第二是將觀察者注冊到具體主題中,這樣主題發生變化時,才能通知到觀察者。
三、Tomcat生命周期管理
理解了觀察者模式,Tomcat的生命周期管理便很容易理解了。所涉及的類有:
-
Lifecycle:相當於抽象主題角色,所有的容器類與組件實現類都實現了這個接口。如StandardContext
-
LifecycleListener:相當於抽象觀察者角色,具體的實現類有ContextConfig, HostConfig, EngineConfig類,它們在容器啟動時與停止時觸發。
-
LifecycleEvent:生命周期事件,對主題與發生的事件進行封裝。
-
LifecycleSupport:生命周期管理的實用類,提供對觀察者的添加,刪除及通知觀察者的方法。
-
LifecycleException:生命周期異常類。
Lifecycle接口
- package com.apache.catalina;
- import org.apache.catalina.LifecycleException;
- public interface Lifecycle {
- //生命周期內的六個事件
- public static final String START_EVENT = "start";
- public static final String BEFORE_START_EVENT = "before_start";
- public static final String AFTER_START_EVENT = "after_start";
- public static final String STOP_EVENT = "stop";
- public static final String BEFORE_STOP_EVENT = "before_stop";
- public static final String AFTER_STOP_EVENT = "after_stop";
- //觀察者的管理與通知方法
- public void addLifecycleListener(LifecycleListener listener);
- public void removeLifecycleListener(LifecycleListener listener);
- public LifecycleListener[] findLifecycleListeners();
- //主題的啟動與停止方法
- public void start()throws LifecycleException;
- public void stop()throws LifecycleException;
- }
Lifecycle相當於觀察者模式中的抽象主題角色(Observable),它定義了添加、刪除及通知管理者的方法。
還定義了與生命周期相關的6個事件。Start和stop方法是Lifecycle最重要的兩個方法,分別代表啟動與停止。所有四種容器的標准實現類(StandardEngine, StandardHost, StandardContext,StandardWrapper)和基本組件(Logger,Loader,Manager等)的實現類都實現了Lifecycle接口,這意義着它們都是具體的觀察者,具有啟動和停止方法。容器啟動時,主要做三件事:調用組件的啟動方法,啟動組件;調用子容器的啟動方法,啟動子容器;通知容器的觀察者,使其執行相應的啟動動作。子容器啟動也做這三件事,這樣整個Tomcat便啟動了。Tomcat的停止也類似。
LifecycleListener接口:
- package org.apache.catalina;
- public interface LifecycleListener {
- /**
- * Acknowledge the occurrence of the specified event.
- *
- * @param event LifecycleEvent that has occurred
- */
- public void lifecycleEvent(LifecycleEvent event);
- }
LifecycleListener相當於觀察者模式中的抽象觀察者角色(Observer),可以看到它與Observer非常的類似,都只有一個更新自己的方法。不同的是,Observer 更新方法中,有兩個參數:Observable與Object,而LifecycleListener中只有一個參數LifecycleEvent,這正是對前面兩個參數的封裝。
LifecycleEvent:
- package org.apache.catalina;
- import java.util.EventObject;
- public final class LifecycleEvent
- extends EventObject {
- public LifecycleEvent(Lifecycle lifecycle, String type) {
- this(lifecycle, type, null);
- }
- public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {
- super(lifecycle);
- this.lifecycle = lifecycle;
- this.type = type;
- this.data = data;
- }
- private Object data = null;
- private Lifecycle lifecycle = null;
- private String type = null;
- public Object getData() {
- return (this.data);
- }
- public Lifecycle getLifecycle() {
- return (this.lifecycle);
- }
- public String getType() {
- return (this.type);
- }
- }
LifecycleEvent
是對主題(事件源),事件及相關數據的封裝,繼承自java.util.是對主題(事件源),事件及相關數據的封裝,繼承自java.util.
EventObject.
LifecycleSupport:
前面說過,抽象主題定義的添加,刪除和通知觀察者的方法都是很固定的,每個實現類實現起來都一樣,這樣就可以提供一個類來實現這些功能,具體的主題類直接調用便可以了。
- package org.apache.catalina.util;
- import org.apache.catalina.Lifecycle;
- import org.apache.catalina.LifecycleEvent;
- import org.apache.catalina.LifecycleListener;
- public final class LifecycleSupport {
- public LifecycleSupport(Lifecycle lifecycle) {
- super();
- this.lifecycle = lifecycle;
- }
- private Lifecycle lifecycle = null;
- private LifecycleListener listeners[] = new LifecycleListener[0];
- //添加一個觀察者
- public void addLifecycleListener(LifecycleListener listener) {
- synchronized (listeners) {
- 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;
- }
- }
- //找出所注冊的觀察者
- public LifecycleListener[] findLifecycleListeners() {
- return listeners;
- }
- //通知觀察者
- public void fireLifecycleEvent(String type, Object data) {
- LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
- LifecycleListener interested[] = null;
- synchronized (listeners) {
- interested = (LifecycleListener[]) listeners.clone();
- }
- for (int i = 0; i < interested.length; i++)
- interested[i].lifecycleEvent(event);
- }
- //刪除一個觀察者
- public void removeLifecycleListener(LifecycleListener listener) {
- synchronized (listeners) {
- int n = -1;
- for (int i = 0; i < listeners.length; i++) {
- if (listeners[i] == listener) {
- n = i;
- break;
- }
- }
- if (n < 0)
- return;
- LifecycleListener results[] =
- new LifecycleListener[listeners.length - 1];
- int j = 0;
- for (int i = 0; i < listeners.length; i++) {
- if (i != n)
- results[j++] = listeners[i];
- }
- listeners = results;
- }
- }
- }
這樣,具體的主題實現抽象主題中對觀察者的添加、刪除與通知方法便非常簡單了。
如在ContainerBase(容器的基本實現類)中:
- protected LifecycleSupport lifecycle = new LifecycleSupport(this);
- public void addLifecycleListener(LifecycleListener listener) {
- lifecycle.addLifecycleListener(listener);
- }
- public LifecycleListener[] findLifecycleListeners() {
- return lifecycle.findLifecycleListeners();
- }
- public void removeLifecycleListener(LifecycleListener listener) {
- lifecycle.removeLifecycleListener(listener);
- }
Lifecycle,LifecycleListener,LifecycleSupport,LifecycleEvent, LifecycleException及其具體的觀察者與具體的主題之間的關系如下:
讓我們再來看一下StandardContext的啟動方法,StandartContext實現了Lifecycle,它的啟動方法由上一級容器所調用。
public synchronized void start() throws LifecycleException {
//略過N多代碼
//通知觀察者,容器即將啟動
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
//略過N多代碼
// 啟動loader,cluster,realm組件
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
//略過N多代碼
//找出所有的子容器,並且啟動
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Lifecycle)
((Lifecycle) children[i]).start();
}
//通知所有觀察者,容器正在啟動
lifecycle.fireLifecycleEvent(START_EVENT, null);
//啟動Manager組件
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
//通知所有觀察者,容器已經啟動
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
這里主要做三件事:調用組件的啟動方法,啟動組件;調用子容器的啟動方法,啟動子容器;通知容器的觀察者,使其執行相應的啟動動作。每一層次的容器都這樣啟動,最終整個Tomcat啟動完畢。
因為源代碼實在是太多了,沒法全部貼出來,如果感興趣,大家可以在附件上下下來研究。
推薦大家閱讀How Tomcat Works這本書。就像書名一樣,這本書詳細的剖析了Tomcat運作機制,寫的非常的好,在豆瓣這本書的評分是9.4分,而同樣經典的Thinking in Java為9.1分,Effective Java為9.2分。Tomcat的源代碼非常值得研究,里面用了很多的設計模式,如本文講的觀察者模式,還是上一篇所講的單例模式,以及門面模式,責任鏈模式等等。
- HowTomcatWorksApps.zip (3.7 MB)
- 下載次數: 11