1. 程式人生 > >How tomcat works——11 StandardWrapper

How tomcat works——11 StandardWrapper

概述

我們在第5章中已經學到,一共有4種容器:engine(引擎)、host(主機)、context(上下文)和 wrapper(包裝器)。並且在前面的章節裡也介紹瞭如何建立自己的context 和 wrapper。一個上下文一般包括一個或者多個包裝器,每一個包裝器代表一個 servlet。本章將介紹Catalina 中 Wrapper 介面的標準實現。首先介紹了一個 HTTP 請求會喚醒的一系列方法,接下來介紹了javax.servlet.SingleThreadModel 介面。最後介紹了 StandardWrapper 和StandardWrapperValve 類。本章的應用Demo說明了如何用 StandardWrapper 例項來表示 servlet。

11.1 方法呼叫序列

對於每一個HTTP請求,聯結器都會呼叫相關聯容器的 invoke() 方法。接下來容器呼叫它的所有子容器的 invoke() 方法。例如,如果一個聯結器跟一個 StadardContext例項相關聯,那麼聯結器會呼叫 StandardContext 例項的 invoke()方法,該方法會呼叫所有它的子容器的 invoke()方法(這裡子容器是StandardWrapper)。圖11.1說明了一個聯結器收到一個 HTTP請求時會做的一系列事情。(回想第5章,容器中帶有一個或多個閥的管道)
這裡寫圖片描述
Figure 11.1: The collaboration diagram of methods invocation

》聯結器建立請求和響應物件
》聯結器呼叫 StandardContext 的 invoke()方法
》StandardContext的invoke()方法則轉向呼叫該上下文容器管道的 invoke()方法。StandardContext的管道中基礎閥門是StandardContextValve,所以 StandardContext 的管道會呼叫StandardContextValve 的 invoke ()方法
》StandardContextValve 的 invoke()方法獲取得到合適的包裝器來對請求進行服務並呼叫包裝器的 invoke()方法
》StandardWrapper 是包裝器的標準實現,StandardWrapper 的invoke()方法則呼叫它管道中的invoke()方法
》StandardWrapper管道中的基礎閥門是StandardWrapperValve。因此 StandardWrapperValve 的 invoke()方法會被呼叫。StandardWrapperValve 的 invoke()方法會呼叫包裝器的allocate ()方法來獲得一個 servlet 的例項
》當一個 servlet 需要被載入時, allocate()方法呼叫方法load() 來載入一個 servlet
》load()方法會呼叫 servlet 的init()方法
》StandardWrapperValve則呼叫servlet的service()方法

注意:StandardContext 類的建構函式設定 StandardContextValve的例項做為它的基礎閥門

publicStandardContext() {
    super();
    pipeline.setBasic(new StandardContextValve());
    namingResources.setContainer(this);
}

注意:StandardWrapper 類的建構函式將 StandardWrapperValve 做為它的基礎閥門

public StandardWrapper() {
    super();
    pipeline.setBasic(new StandardWrapperValve());
}

本章關注的是一個 servlet 被呼叫過程中發生的細節。因此我們需要學習StandardWrapper 和 StandarWrapperValve 類。在學習它們之前,讓我們首先關注學習下 javax.servlet.SingleThreadModel介面。理解該介面對於理解一個包裝器是如何載入一個Servlet的是非常重要的。

11.2 SingleThreadModel介面

servlet 可以實現 javax.servlet.SingleThreadModel 介面,實現了此介面的servlet 通俗稱為 SingleThreadModel(STM)。根據 Servlet規範,實現此介面的目的是保證 servlet 一次只處理一個請求。Servlet 2.4 規範的第 SRV.14.2.24 節(Servlet 2.3 中 SingleThreadModel 介面有類似說明):

“If a servlet implements this interface, you are guaranteed that no
two threads will execute concurrently in a servlet’s service method.
The servlet container can guarantee this by synchronizing access to a
single instance of the servlet, or by maintaining a pool of servlet
instances and dispatching each new request to a free servlet. This
interface does not prevent synchronization problems that result from
servlets accessing shared resources such as static class variables or
classes outside the scope of the servlet.”

很多程式設計師並沒有仔細閱讀它,只是認為實現了 SIngleThreadModel 就能保證servlet 是執行緒安全的。顯然並非如此,重新閱讀上面的引文。

一個servlet實現了SIngelThreadModel之後確實能保證它的service()方法不會被兩個執行緒同時使用。為了提高 servlet 容器的效能,可以建立 STM servlet的多個例項。這意味著,STM servlet的service()可以在不同的例項中併發執行。如果servlet需要訪問靜態類變數或類外的其它資源,這將引入同步問題。

因為易使 Servlet 程式設計師產生錯覺(False Sense),認為它是執行緒安全的,該 SingleThreadModel 介面在 Servlet 2.4 中已經廢棄。然而,無論Servlet 2.3 和 Servlet 2.4 的容器仍然必須支援此介面。

11.3 StandardWrapper類

StandardWrapper 物件的主要職責是:載入它表示的 servlet 並分配它的一個例項。然而,該 StandardWrapper並不會呼叫 servlet 的 service()方法。這個任務留給了StandardWrapper 例項的管道中基礎閥門StandardWrapperValve 物件。StandardWrapperValve 物件通過呼叫 StandardWrapper的allocate()方法獲得Servlet 例項。在接收到servlet例項時,StandardWrapperValve會呼叫servlet的service()方法。

在 servlet 第一次被請求時,StandardWrapper 載入 servlet 類。它是動態地載入 servlet的,所以需要知道 servlet 類的完全限定名稱。可以通過StandardWrapper 類的 setServletClass()方法將 servlet 的類名傳遞給StandardWrapper。另外,使用setName()方法也可以傳遞servlet 名。

考慮到 StandardWrapper負責在StandardWrapperValve 需要的時候分配一個servlet 例項,它必須考慮該servlet 是否實現了 SingleThreadModel 介面。

如果一個 servlet 沒有實現 SingleThreadModel介面,StandardWrapper載入該servlet 一次,對於以後的請求返回相同的例項即可。StandardWrapper 假設servlet的service()方法是執行緒安全的,所以並沒有建立 servlet 的多個例項。如果需要的話,由程式設計師自己解決資源同步問題。

對於一個 STM servlet,情況就有所不同了。StandardWrapper 必須保證不能同時有兩個執行緒呼叫 STM servlet 的 service()方法。如果 StandardWrapper 維持一個 STM servlet的例項,下面是它如何呼叫 servlet 的 service()方法:

Servlet instance = <get an instance of the servlet>;
if ((servlet implementing SingleThreadModel>) {
    synchronized (instance) {
        instance.service(request, response);
    }
}else {
    instance.service(request, response);
}

但,為了效能起見,StandardWrapper 維護了該STM servlet 例項池。

一個包裝器還負責準備javax.servlet.ServletConfig 的例項,這可以在servlet 內部完成。接下來兩小節討論如何分配和載入 servlet。

11.3.1分配Servlet

如在本節開始時介紹到StandardWrapperValve的invoke()方法通過呼叫包裝器的allocate()方法來獲得對應請求servlet的例項。因此 StandardWrapper 類必須實現該方法。allocate()方法簽名如下:

public javax.servlet.Servlet allocate() throws ServletException;

注意allocate()方法返回的是請求 servlet 的一個例項。

由於要支援 STM servlet,這使得allocate()方法變的複雜一點。實際上,該方法有兩部分組成,一部分負責非 STM servlet的工作,另一部分負責STM servlet。第一部分的骨架結構如下:

if (!singleThreadModel) {
    // returns a non-STM servlet instance
}

布林變數 singleThreadModel 負責標誌該servlet 是否是 STM servlet。它的初始值是 false,loadServlet()方法會檢測載入的 servlet 是否是 STM 的,如果是則將它的值該為 true。loadServlet()方法在下面會介紹到。

當singleThreadModel 為 true 時,第二部分的框架程式碼如下:

synchronized (instancepool) {
    // returns an instance of the servlet from the pool
}

現在來看一下第一部分和第二部分。

對於非 STM servlet,StandardWrapper 定義一個 java.servlet.Servlet 型別的例項:

private Servlet instance = null;

allocate()方法檢查該例項是否為null,如果是,則呼叫 loadServlet()方法來載入servlet。然後增加整型變數contAllocated的值,並返回該例項。

// If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {

            // Load and initialize our instance if necessary
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            instance = loadServlet();
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            throw new ServletException
                              (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }

            if (!singleThreadModel) {
                if (debug >= 2)
                    log("  Returning non-STM instance");
                countAllocated++;
                return (instance);
            }

        }

如果 StandardWrapper 表示的是一個 STM servlet,方法 allocate()則嘗試返回池中的一個例項。變數 intancePool 是java.util.Stack 型別的 STM servlet例項池。

private Stack instancePool = null;

該變數在 loadServlet ()方法內初始化,該部分在接下來的小節中進行討論。

只要例項數量不超過指定的最大數量,allocate()方法將分配STM servlet的例項。該數量由 maxInstances 整型定義,預設值是 20:

private int maxInstances = 20;

StandardWrapper 提供了 nInstances 整型變數來定義當前 STM 例項的個數:

private int nInstances = 0;

下面是 allocate()方法的第二部分:

synchronized (instancePool) {

            while (countAllocated >= nInstances) {
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new ServletException
                            (sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        ;
                    }
                }
            }
            if (debug >= 2)
                log("  Returning allocated STM instance");
            countAllocated++;
            return (Servlet) instancePool.pop();

        }

上面的程式碼使用while 迴圈等待直到 nInstances的數目少於或等countAllocated。在迴圈裡,allocate()方法檢查nInstance 的值,如果小於maxInstances,則呼叫 loadServlet()方法並將該例項新增到池中,並增加 nInstances 的值。如果 nInstances等於或大於maxInstances,則呼叫例項池堆疊的wait()方法進行等待,直到一個例項被返回到堆疊中。

11.3.2 載入Servlet

StandardWrapper 實現了Wrapper 介面中的load()方法。load()方法呼叫loadServlet()方法來載入一個 servlet 類,並呼叫該 servlet 的 init()方法,傳遞一個javax.servlet.ServletConfig 例項。這裡介紹 loadServlet()是如何工作的。

loadServlet()方法首先檢查該StandardWrapper 表示的servlet是否是STM型別。如果不是,並且該例項變數instance不是 null(即以前已經載入過),則直接返回該例項:

// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
    return instance;

如果該例項是 null 或該servlet是 STM 型別,則繼續執行該方法的其它部分程式碼。

首先獲得 System.out 和 System.err 輸出,方便接下來就可以使用javax.servlet.ServletContext 的 log()方法來記錄資訊:

PrintStream out = System.out;
SystemLogHandler.startCapture();

然後,定義了一javax.servlet.Servlet 型別變數,它就表示loadServlet()方法載入 servlet 後要返回的例項:

Servlet servlet = null;

loadServlet()方法負責載入servlet類,類名應該已被分配給servletClass變數,這裡將該值賦給一個 String 型別區域性變數actualClass:

String  actualClass = servletclass;

但是,由於 Catalina 也是一個 JSP 容器,若請求的是 JSP 頁面,loadServlet() 必須也能工作,如果是 JSP 頁面,則應得到該JSP相應的 Servlet 類:

if ((actualClass == null) && (jspFile != null)) {
    Wrapper jspWrapper = (Wrapper)
    ((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
    if (jspWrapper != null)
        actualClass = jspWrapper.getServletClass();
}

如果 JSP 頁面相應的Servlet 名字找不到,就用 servletclass 變數的值。但是,如果該變數的值之前沒使用 StandardWrapper 類中的 setServletClass()方法設定過,則會丟擲異常,並且不會執行其它程式碼了。

// Complain if no servlet class has been specified
if (actualClass == null) {
    unavailable(null);
    throw new ServletException (sm.getString("StandardWrapper.notClass", getName()));
}

現在,servlet 的名字已經獲得了,接下來需要獲得一載入器。如果找不到載入器,則丟擲異常並停止執行:

// Acquire an instance of the class loader to be used
Loader loader = getLoader();
if (loader == null) {
    unavailable(null);
    throw new ServletException (sm.getString("StandardWrapper.missingLoader", getName()));
}

如果找到載入器,則通過getClassLoader()方法獲得一個ClassLoader:

ClassLoader classLoader = loader.getClassLoader();

Catalina 提供了從屬於 org.apache.catalina包的特殊 Servlet。這些 Servlet可以進入 Servlet 容器的內部。如果該 Servlet 是一個特殊 Servlet,如isContainerProvidedServlet()方法返回 true 值。classLoader 會被分配另一個ClassLoader 的例項,這樣就可以訪問 Catalina 的內部了:

// Special case class loader for a container provided servlet
if (isContainerProvidedServlet(actualClass)) {
    ClassLoader = this.getClass().getClassLoader();
    log(sm.getString ("standardWrapper.containerServlet", getName()));
}

有了類載入器和要載入的 Servlet 名字,就可以來載入類了:

// Load the specified servlet class from the appropriate class loader
Class classClass = null;
try {
    if (ClassLoader != null) {
        System.out.println("Using classLoader.loadClass");
        classClass = classLoader.loadClass(actualClass);
    }else {
        System.out.println("Using forName");
        classClass = Class.forName(actualClass);
    }
}catch (ClassNotFoundException e) {
    unavailable(null);
    throw new ServletException (sm.getstring("standardWrapper.missingClass", actualClass), e);
}

if (classClass == null) {
    unavailable(null);
    throw new ServletException (sm.getString("standardWrapper.missingClass", actualClass));
}

然後,可以例項化該servlet了:

// Instantiate and initialize an instance of the servlet class itself
try {
    servlet = (Servlet) classClass.newInstance();
}catch (ClassCastException e) {
    unavailable(null);
    // Restore the context ClassLoader
    throw new ServletException (sm.getString("standardWrapper.notServlet", actualClass), e);
}catch (Throwable e) {
    unavailable(null);
    // Restore the context ClassLoader
    throw new ServletException (sm.getstring("standardWrapper.instantiate", actualClass), e);
}

然而,在初始化 Servlet 之前,得使用 isServletAllowed()方法來檢查該Servlet是否可以允許訪問:

// Check if loading the servlet in this web application should be allowed
if (!isServletAllowed(servlet)) {
    throw new SecurityException (sm.getString("standardWrapper.privilegedServlet",
    actualClass));
}

如果通過了安全性檢查,接下來檢查該 Servlet 是否是一個 ContainerServlet。ContainerServlet 是實現了 org.apache.catalina.ContainerServlet 介面的Servlet,它可以訪問 Catalina 的內部函式。如果該 Servlet 是ContainerServlet,則呼叫ContainerServlet 的 setWrapper()方法,傳遞該 StandardWrapper 例項:

// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) && isContainerProvidedServlet(actualClass)) {
    ((ContainerServlet) servlet).setWrapper(this);
}

接下來觸發 BEFORE_INIT_EVENT事件,並呼叫init()方法:

try {
    instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet);
    servlet.init(facade);

注意該方法傳遞一個 façade 變數,該變數是一個 javax.servlet.ServletConfig物件。如何建立ServletConfig 物件會在本章的“建立ServletConfig”小節中看到。

如果loadOnStartup變數的值大於零並且 Servlet 是一個 JSP 頁面,呼叫該Servlet 的 service()方法:

// Invoke jspInit on JSP pages
if ((loadOnStartup > 0) && (jspFile != null)) {
    // Invoking jspInit
    HttpRequestBase req = new HttpRequestBase();
    HttpResponseBase res = new HttpResponseBase();
    req.setServletPath(jspFile};
    req.setQueryString("jsp_precompile=true");
    servlet.service(req, res);
}

接下來,觸發 AFTER_INIT_EVENT 事件:

instanceSupport.firelnstanceEvent (InstanceEvent.AFTER_INIT_EVENT,servlet);

如果該 StandardWrapper 物件表示的Servlet是一個 STM型別,則將該例項新增到例項池中,因此,如果例項池如果為 null,首先需要建立它:

// Register our newly initialized instance
singleThreadModel = servlet instanceof SingleThreadModel;
if (singleThreadModel) {
    if (instancePool == null)
        instancePool = new Stack();
}
fireContainerEvent("load", this);

在 finally 塊中,停止捕獲 System.out 和 System.err,並將載入過程中資訊使用該容器的 log()方法記錄到日誌系統中:

finally {
    String log = SystemLogHandler.stopCapture();
    if (log != null && log.length() > 0) {
        if (getServletContext() != null) {
            getServletContext().log(log);
        }else {
            out.println(log);
        }
    }
}

最後,返回 Servlet 例項:

return servlet;

11.3.3 ServletConfig物件

StandardWrapper 的 loadServlet()方法在載入了 servlet之後呼叫該servlet的init()方法。init() 方法傳遞的是一個 javax.servlet.ServletConfig 例項。你可能想知道一個 StandardWrapper 物件如何獲得 ServletConfig 物件?

只要看 StandardWrapper類即可,該類實現 javax.servlet.ServletConfig介面和Wrapper 介面。

ServletConfig 介面有以下4個方法: getServletContext(), getServletName(),getInitParameter(), 和 getInitParameterNames()。接下來讓我們看 StandardWrapper 如何實現這4個方法。

注意:StandardWrapper 並不將自己傳遞給 Servlet 的 init()方法,它而是將自己包裝到 StandardWrapperFacade 例項中,進而向servlet程式設計師隱藏了它的一些public 方法。我們將在隨後的“StandardWrapperFacade類”一節中學習。

11.3.3.1 getServletContext()

該方法的簽名如下:

public ServletContext getServletContext()

一個 StandardWrapper 例項必須是一個 StandardContext 容器的子容器。也就是說,StandardWrapper 的父容器是 StandardContext。可以通過StandardContext物件的 getServletContext()來獲得 ServletContext 物件。如下是StandardWrapper中getServletContext()方法的實現:

public ServletContext getServletContext() {
    if (parent == null)
        return (null);
    else if (!(parent instanceof Context))
        return (null);
    else
        return (((Context) parent).getServletContext());
}

注意:現在我們知道不能單獨部署一個包裝器來表示一個 Servlet,包裝器必須從屬於一個上下文容器,這樣才能使用 ServletConfig 物件通過getServletContext()方法獲得ServletContext 例項。

11.3.3.2 getServletName()

該方法返回 Servlet 的名字,方法簽名如下:

public java.lang.String getServletName()

如下是StandardWrapper 類中getServletName()方法的實現:

public String getServletName() {
    return (getName());
}

它簡單地呼叫 StandardWrapper 的父類 ContainerBase 類的 getName()方法,該方法在 ContainerBase 中實現如下:

public String getName() {
    return (name);
}

可以使用 setName()方法來設定 name 的值。回憶是如何呼叫 StandardWrapper 例項的 setName()方法來傳遞設定Servlet 的 name?

11.3.3.3 getInitParameter()

該方法返回初始化時指定引數的值,該方法的簽名如下:

public java.lang.String getInitParameter(java.lang.String name)

在 StandardWrapper 中,初始化引數被存放在一個名為 parameters 的 HashMap中:

private HashMap parameters = new HashMap();

可以呼叫 StandardWrapper 類的 addInitParameter()方法來填充 parameters。傳遞引數的名字和值:

public void addInitParameter(String name, String value) {
    synchronized (parameters) {
        parameters.put(name, value);
    }
    fireContainerEvent("addInitParameter", name);
}

下面是 StandardWrapper 對 getInitParameter()的實現:

public String getInitParameter(String name) {
    return (findInitParameter(name));
}

方法 findInitParameter()實現如下:

public String findInitParameter(String name) {
    synchronized (parameters) {
        return ((String) parameters.get(name));
    }
}

11.3.3.4 getInitParameterNames()

該方法返回所有初始化引數名字的列舉(Enumeration),方法簽名如下:

public java.util.Enumeration getInitParameterNames()

StandardWrapper 類中 getInitParameterNames()實現如下:

public Enumeration getInitParameterNames() {
    synchronized (parameters) {
        return (new Enumerator(parameters.keyset()));
    }
}

Enumerator實現了java.util.Enumeration介面,是org.apache.catalina.util包的一部分。

11.3.4 父容器 和子容器

一個包裝器表示一個獨立 Servlet 的容器。這樣,包裝器就不能再有子容器,因此不可以呼叫它的 addChild()方法,如果呼叫了會得到一個java.langIllegalStateException異常。StandardWrapper中addChild()方法實現如下:

public void addChild(Container child) {
    throw new IllegalStateException (sm.getString("StandardWrapper.notChild"));
}

一個包裝器的父容器只能是Context。如果傳遞的引數不是一個Context容器,它的 setParent() 方法會丟擲 java.lang.IllegalArgumentException異常:

public void setParent(Container container) {
    if ((container != null) && !(container instanceof Context))
        throw new IllegalArgumentException (sm.getString("standardWrapper.notContext"));
    super.setParent(container);
}

11.4 StandardWrapperFacade類

StandardWrapper呼叫它載入的 Servlet 的 init()方法。該方法需要一個javax.servlet.ServletConfig 的引數,而 StandardWrapper 類自己也實現了ServletConfig 介面,所以理論上 StandardWrapper 可以將它自己作為引數傳遞給init()方法。然而 StandardWrapper 需要對 Servlet 隱藏它的大多數 public方法。為了實現這一點,StandardWraper 將它自己包裝的一個StandardWrapperFacade 例項中。圖11.2展現了 StandardWrapper 和StandardWrapperFacade間關係,它們都實現了 java.servlet.ServletConfig介面。
這裡寫圖片描述
Figure 11.2: The relationship between StandardWrapper and StandardWrapperFacade

下面程式碼可見StandardWrapper把自己作為引數傳遞給StandardWrapperFacade 的建構函式:

private StandardWrapperFacade facade = new StandardWrapperFacade(this);

StandardWrapperFacade 類中有一個 ServletConfig 型別的類級變數 config:

private ServletConfig config = null;

當一個 StandardWrapperFacade 物件被建立時,建構函式將該StandardWrapper 賦值給 config 變數:

public StandardWrapperFacade(StandardWrapper config) {
    super();
    this.config = (ServletConfig) config;
}

因此,當 StandardWrapper 物件呼叫 Servlet 例項的 init()方法時,它傳遞的是一個StandardWrapperFacade 物件。在 Servlet 內部呼叫 ServletConfig的 getServletName(), getInitParameter()和getInitParameterNames()方法只需要呼叫它們在 StandardWrapper 的實現就行:

public String getServletName() {
    return config.getServletName();
}

public String getInitParameter(String name) {
    return config.getInitParameter(name);
}

public Enumeration getInitParameterNames() {
    return config.getInitParameterNames();
}

呼叫 getServletContext()方法稍微複雜點:

public ServletContext getServletContext() {
    ServletContext theContext = config.getServletContext();
    if ((theContext != null) && (theContext instance of ApplicationContext))
    theContext = ((ApplicationContext) theContext).getFacade();
    return (theContext);
}

該方法呼叫StandardWrapper類中的getServletContext()方法,但它返回ServletContext的外觀,而不是ServletContext物件本身。

11.5 StandardWrapperValve類

StandardWrapperValve 是 StandardWrapper 例項上的基礎閥門,該閥門做兩件事情:
》執行本Servlet 所有相關的過濾器;
》呼叫其service()方法;

要做這些工作,下面是 StandardWrapperValve 在它的 invoke()方法要實現的:
》呼叫StandardWrapper的allocate()方法來獲得當前StandardWrapper所表示的servlet例項;
》通過執行私有方法createFilterChain()建立過濾鏈;
》呼叫過濾器鏈的 doFilter()方法。包括呼叫 servlet 的 service()方法;
》釋放過濾器鏈;
》呼叫包裝器的 deallocate()方法;
》如果 Servlet 久而無法使用,呼叫包裝器的 unload()方法;

下面是 invoke() 方法中主要實現部分:

// Allocate a servlet instance to process this request
try {
    if (!unavailable) {
        servlet = wrapper.allocate();
    }
}
...
// Acknowlege the request
try {
    response.sendAcknowledgement();
}
...
// Create the filter chain for this request
ApplicationFilterChain filterChain = createFilterChain(request,servlet);
// Call the filter chain for this request This also calls the servlet's servicet() method
try {
    String  jspFile = wrapper.getJspFile();
    if (jspFile != null)
        sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
    else
        sreq.removeAttribute(Globals.JSP_FILE_ATTR);
    if ((servlet != null) && (filterChain != null)) {
        filterChain.doFilter(sreq, sres);
    }
    sreq.removeAttribute(Globals.JSP_FILE_ATTR);
}
...
// Release the filter chain (if any) for this request
try {
    if (filterChain != null)
        filterChain.release();
}
...
// Deallocate the allocated servlet instance
try {
    if (servlet != null) {
        wrapper.deallocate(servlet);
    }
}
...
// If this servlet has been marked permanently unavailable, unload it and release this instance
try {
    if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) {
        wrapper.unload();
    }
}
...

最重要的是 createFilterChain()方法和過濾器鏈的 doFilter()方法呼叫。createFilterChain()方法 建立了一個 ApplicationFilterChain 例項,並將所有相關的過濾器新增到上面。ApplicationFilterChain 類將在下面的小節中介紹。要完全的理解這個類,還需要理解 FilterDef 和 ApplicationFilterConfig 類。

11.6 FilterDef類

org.apache.catalina.deploy.FilterDef 代表著一個過濾器定義,如在部署檔案中定義一個過濾器元素那樣。Listing11.1 展示了該類:

Listing 11.1: The FilterDef class

package org.apache.catalina.deploy;
import java.util.HashMap;
import java.util.Map;

public final class FilterDef {
/**
* The description of this filter.
*/
private String description = null;
public String getDescription() {
    return (this.description);
}
public void setDescription(String description) {
    this.description = description;
}
/**
* The display name of this filter.
*/
private String displayName = null;
public String getDisplayName() {
    return (this.displayName);
}
public void setDisplayName(String displayName) {
    this.displayName = displayName;
}
/**
* The fully qualified name of the Java class that implements this
* filter.
*/
private String filterClass = null;
public String getFilterClass() {
    return (this.filterClass);
}
public void setFilterclass(String filterClass) {
    this.filterClass = filterClass;
}
/**
* The name of this filter, which must be unique among the filters
* defined for a particular web application.
*/
private String filterName = null;
public String getFilterName() {
    return (this.filterName);
}
public void setFilterName(String filterName) {
    this.filterName = filterName;
}
/**
* The large icon associated with this filter.
*/
private String largeIcon = null;
public String getLargeIcon() {
    return (this.largeIcon);
}
public void setLargeIcon(String largeIcon) {
    this.largeIcon = largeIcon;
}
/**
* The set of initialization parameters for this filter, keyed by
* parameter name.
*/
private Map parameters = new HashMap();
public Map getParameterMap() {
    return (this.parameters);
}
/**
* The small icon associated with this filter.
*/
private String smallIcon = null;
public String getSmallIcon() {
    return (this.smallIcon);
}
public void setSmallIcon(String smallIcon) {
    this.smallIcon = smallIcon;
}
public void addInitParameter(String name, String value) {
    parameters.put(name, value);
}
/**
* Render a String representation  of this object.
*/
public String toString() {
    StringBuffer sb = new StringBuffer("FilterDef[");
    sb.append("filterName=");
    sb.append(this.filterName);
    sb.append(", filterClass=");
    sb.append(this.filterClass);
    sb.append("]");
    return (sb.toString());
}
}

FilterDef 類中的每一個屬性都代表一個可以在過濾器配置中出現的子元素。該類包括一個Map型別的變量表示一個包含所有初始引數。通過addInitParameer()方法新增name/value對到該Map中。

11.7 ApplicationFilterConfig類

org.apache.catalina.core.ApplicationFilterConfig 實現了javax.servlet.FilterConfig 介面。ApplicationFilterConfig 負責管理 web應用程式啟動時建立過濾器例項。

傳遞org.apache.catalina.Context和ApplicationFilterConfig物件給ApplicationFilterConfig的建構函式來建立一個ApplicationFilterConfig例項:

public ApplicationFilterConfig(Context context, FilterDef filterDef)
    throws ClassCastException, ClassNotFoundException,
        IllegalAccessException, InstantiationException, ServletException

Context 物件表示一個 web 應用,FilterDef 表示一個過濾器定義。ApplicationFilterConfig 中getFilter()方法可以返回一個javax.servlet.Filter物件。該方法負責載入過濾器類並例項化它。

Filter getFilter() throws ClassCastException, ClassNotFoundException,
        IllegalAccessException, InstantiationException, ServletException {

        // Return the existing filter instance, if any
        if (this.filter != null)
            return (this.filter);

        // Identify the class loader we will be using
        String filterClass = filterDef.getFilterClass();
        ClassLoader classLoader = null;
        if (filterClass.startsWith("org.apache.catalina."))
            classLoader = this.getClass().getClassLoader();
        else
            classLoader = context.getLoader().getClassLoader();

        ClassLoader oldCtxClassLoader =
            Thread.currentThread().getContextClassLoader();

        // Instantiate a new instance of this filter and return it
        Class clazz = classLoader.loadClass(filterClass);
        this.filter = (Filter) clazz.newInstance();
        filter.init(this);
        return (this.filter);

}

11.8 ApplicationFilterChain類

org.apache.catalina.core.ApplicationFilterChain 類是實現了javax.servlet.FilterChain 介面。StandardWrapperValve 類中的 invoke()方法建立一個該類的例項並且呼叫它的 doFilter()方法。ApplicationFilterChain 類的 doFilter()方法呼叫該鏈中第一個過濾器的 doFilter()方法。Filter 介面中doFilter()方法的簽名如下:

public void doFilter(ServletRaquest request, ServletResponse response,
FilterChain chain) throws java.io.IOException, ServletException

ApplicationFilterChain 的 doFilter()方法,並將它自己作為第三個引數傳遞給過濾器的doFilter()方法。

在doFilter()方法中,一個過濾器可以通過FilterChain中doFilter()方法喚醒下一個過濾器。如下是一個過濾器的 doFilter() 實現的例子:

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
    // do something here
    ...
    chain.doFilter(request, response);
}

如你所見,在 doFilter()方法最後一行,它呼叫過濾鏈的 doFilter()方法。如果該過濾器是過濾鏈中最後一個過濾器,它將呼叫所請求的Servlet的service()方法。如果過濾器沒有呼叫 chain.doFilter,下一個過濾器就不會被呼叫。

11.9 應用Demo

本應用Demo有ex11.pyrmont.core.SimpleContextConfig 和ex11.pyrmont.startup.Bootstrap兩個類組成。SimpleContextConfig 類和前一章Demo中一樣, Bootstrap 類如 Listing11.2 所示:

Listing 11.2: The Bootstrap class

package ex11.pyrmont.startup;

//use StandardWrapper
import ex11.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap {
  public static void main(String[] args) {

  //invoke: http://localhost:8080/Modern or  http://localhost:8080/Primitive

    System.setProperty("catalina.base", System.getProperty("user.dir"));
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new StandardWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new StandardWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/myApp");
    context.setDocBase("myApp");
    LifecycleListener listener = new SimpleContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);

    context.addChild(wrapper1);
    context.addChild(wrapper2);
    // for simplicity, we don't add a valve, but you can add
    // valves to context or wrapper just as you did in Chapter 6

    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    // add ContextConfig. This listener is important because it configures
    // StandardContext (sets configured to true), otherwise StandardContext
    // won't start
    connector.setContainer(context);
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) context).start();

      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) context).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Bootstrap 建立一叫做myApp的 StandardContext 例項。在該 StandardContext 容器中,添加了兩個 StandardWrapper 例項:Primitive 和Modern。

11.9.1執行Demo

在 windows 下執行Demo,可以在工作目錄下面如下執行該程式:

java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ex11.pyrmont.startup.Bootstrap

在 Linux 下,使用冒號分開兩個庫:

java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ex11.pyrmont.startup.Bootstrap

可以分別使用下面的 URL 來請求呼叫PrimitiveServlet和ModernServlet:

http://localhost:8080/Primitive
http://localhost:8080/Modern

11.10小結

在本章中,我們學習到了 Wrapper 介面在 Catalina 中的標準實現類StandardWrapper。並且討論了過濾器以及和其相關的類。在本章的最後使用StandardWrapper 類做了演示。