文章寫得很詳細,容易理解,轉載推薦。
原文地址:http://www.cnblogs.com/chenying99/archive/2012/09/05/2671199.html
轉載主要是供自己記錄學習用的,哈哈哈~~~~
本文主要結合觀察者模式,講述Tomcat的生命週期管理。Tomcat的生命週期管理機制設計的非常優雅,在Tomcat啓動時,只需要啓動一個Server組件,就會啓動所有的容器及對應的組件,並且觸發這些容器的監聽者,完成啓動過程的設置。可以說是“一鍵式”啓動的。停止過程也是一樣。
本文首先簡單介紹Tomcat中容器,組件及監聽類的功能。因爲Tomcat的生命週期管理應用了觀察者模式,所以接下來會分析觀察者模式,最後結合觀察者模式及Tomcat源代碼,詳細說明Tomcat的生命週期管理。
一、幾種基本的容器,組件及事件監聽類(Listener)
Tomcat中有四種不同的容器:
Engine:代表整個Catalina servle引擎
Host:代表虛擬主機
Context:代表某個web應用
Wrapper:代表某個應用中的servlet
這些容器都擴展了Container接口(譯爲:容器,這也是爲什麼一般都稱tomcat爲容器而不是服務器的原因之一吧~)。更重要的是,這些容器都是父子的關係,Engine位於最頂層,一個Engine包含多個Host,一個Host(虛擬主機)包含多個Context(web應用),一個Context(web 應用)包含多個Wrapper(servlet),Wrapper位於最底層,沒有孩子。當父容器啓動時,相應的子容器也應該啓動,子容器的子容器也啓動。如此,只需要啓動最頂層的容器,就會啓動所有的子容器。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時,正在訪問網站的用戶不用重新登錄的原因。另外,還有一些類,它們並不像容器的基本組件(如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的源代碼非常值得研究,裏面用了很多的設計模式,如本文講的觀察者模式,還是上一篇所講的單例模式,以及門面模式,責任鏈模式等等。