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 的容器仍然必须支持此接口。

注意,可以在下面网址看到 SingleThreadModel相关的更多讨论:
http://w4.metronet.com/~wjm/tomcat/ToFeb11/msg02655.html

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 类做了演示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值