容器模型主要用于为一个servlet处理request对象,并且为Web客户端返回response对象。容器模型由一个org.apache.catalina.Container接口来表示,一般有四种类型的容器:Engine,Host,Context和Wrapper。这一章节主要讨论Context和Wrapper这两种容器,其它的留在后面的章节讨论。这章首先讨论Container接口,以及在容器中使用管道机制。然后再讨论Context和Wrapper这两个接口。
Container接口
一个容器必须实现org.apache.catalina.Container接口,就如在上一章中,你可以将一个Container通过connector的setContainer方法设置到一个connector中,这样connector就可以调用Container的invoke方法。
在Catalina中,容器被分为四个不同的概念层:
- Engine,表示整个Catalina的servlet引擎
- Host,表示拥有数个Context(上下文)的虚拟主机
- Context,表示一个ServletContext,也就是一个Web应用;一个Context可以包含多个wrapper
- Wrapper,表示一个单独的Servlet
以上的概念层都会由一个接口来表示。这些接口分别是Engine,Host,Context和Wrapper。所有的这四个接口都继承了Container接口,这四个接口的标准实现为StandardEngine, StandardHost, StandardContext 和StandardWrapper。
UML类图如下:
需要注意的是,所以实现类都继承了ContainerBase这个抽象类。
管道任务
这一节主要描述当connector调用了容器container的invoke方法后要做哪些处理。接下来分节讨论四个接口:PipeLine,Value,ValueContext,Contained。
管道(pipeline)包含了容器请求的所有任务,一个Value代表一个指定的任务。在容器的管道中除了你可以任意增加Value以外,还有一个基本的value(Base Value)。管道中Value的数量定义为除了基本value以外的Value的数量。另外,你也可以通过编辑Tomcat的配置文件server.xml来动态增加Value。
管道类似于一个过滤的责任链模式。一个value会处理传递给它的request和response对象。当一个value完成处理时,它会调用管道中的下一个value。基本Value总是在最后被调用。
一个容器拥有一个管道。当一个容器的invoke方法被调用时,容器将处理直接交给管道,管道调用它的第一个Value,然后这个Value调用下一个Value,以此类推,直到管道中再没有任何Value。你可能认为管道中的invoke方法的伪代码是下面这样的:
// invoke each valve added to the pipeline
for (int n=0; n<valves.length; n++) {
valve[n].invoke( ... );
}
// then, invoke the basic valve
basicValve.invoke( ... );
然而,Tomcat在设计时使用了另外一种方式,它引入了org.apache.catalina.ValveContext接口。
当connector调用了容器的Invoke方法后,容器的invoke方法里并没有做任何处理,直接调用了管道的invoke方法。管道PipeLine接口中的invoke方法同Container接口中的invoke方法是完全相同的。
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
这就是Container接口在其实现类ContainerBase中invoke方法的代码。pipleLine是一个容器内的PipeLine实例。
管道必须确保所有Value,包括基本Value,只被调用一次。管道通过一个ValueContext接口来完成这个任务。ValueContext以管道内部类的形式实现,这样ValueContext就可以使用管道所有的成员变量。ValueContext中比较重要的方法就是invokeNext:
public void invokeNext(Request request, Response response)
throws IOException, ServletException
容器直接调用了管道的invoke方法,下面是管道的invoke方法:
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Invoke the first Valve in this pipeline for this request
(new StandardPipelineValveContext()).invokeNext(request, response);
}
在创建了一个ValueContext的实例后,管道直接调用了ValueContext的invokeNext方法。ValueContext会先请求管道中的第一个Value,在第一个Value执行处理之前第一个Value会请求下一个Value。ValueContext会将自己传递给每一个Value,这个每个Value就可以调用它的invokeNext方法。
public void invoke(Request request, Response response,
ValveContext valveContext) throws IOException,ServletException
这是Value接口中的invoke方法。
public void invoke(Request request, Response response,
ValveContext valveContext) throws IOException, ServletException {
// Pass the request and response on to the next valve in our pipeline
valveContext.invokeNext(request, response);
// now perform what this valve is supposed to do
...
}
实现方法如上。
public void invokeNext(Request request, Response response)
throws IOException, ServletException {
int subscript = stage;
stage = stage + 1;
// Invoke the requested Valve for the current request thread
if (subscript < valves.length) {
valves[subscript].invoke(request, response, this);
} else if ((subscript == valves.length) && (basic != null)) {
basic.invoke(request, response, this);
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
这是StandardPipelineValveContext类中的invokeNext方法的具体实现。invokeNext方法利用变量subscript和stage来记忆哪个Value是已经被请求过的。
上面使用过的三个接口:Pipeline,Value和ValueContext,在接下将详细讨论。
Pipeline接口
在Pipeline接口中我们提到的第一个方法是invoke,容器会调用这个方法来请求管道中的Values以及基本Value。addValue增加一个新的Value,removeValue删除一个Value。你可以通过setBasic方法来设置一个基本Value,getBasic方法可以获取一个基本Value。基本Value是在最后被请求的,它主要处理请求以及相应的response。接口如下:
import java.io.IOException;
import javax.servlet.ServletException;
public interface Pipeline {
public Valve getBasic();
public void setBasic(Valve valve);
public void addValve(Valve valve);
public Valve[] getValves();
public void invoke(Request request, Response response)
throws IOException, ServletException;
public void removeValve(Valve valve);
}
Valve接口
package org.apache.catalina;
import java.io.IOException;
import javax.servlet.ServletException;
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response,
ValveContext context) throws IOException, ServletException;
}
Value接口代表一个value,value组件负责处理request请求。这个接口有两个方法:invoke和getInfo。invoke方法上面已经讨论过了。getInfo方法主要是提供该Value实现类的一些信息。
ValveContext接口
package org.apache.catalina;
import java.io.IOException;
import javax.servlet.ServletException;
public interface ValveContext {
public String getInfo();
public void invokeNext(Request request, Response response)
throws IOException, ServletException;
}
这个接口也有两个方法:invokeNext和getInfo。
Contained接口
package org.apache.catalina;
public interface Contained {
public Container getContainer();
public void setContainer(Container container);
}
Vaule类可以选择性的实现org.apache.catalina.Contained接口,这个接口指定其实现类至多只能与一个容器实例关联。
Wrapper接口
org.apache.catalina.Wrapper接口代表一个wrapper,wrapper是代表一个单独servlet的容器。Wrapper实现了Container接口并添加了一些方法。Wrapper接口主要负责一个servlet生命周期的管理以及底层的servlet类。为一个servlet调用init,service和destroy方法。由于Wrapper是最底层的容器,所以它不可以再增加子容器。如果在Wrapper上调用addChild方法会收到一个IllegalArgumantException异常信息。
Wrapper中有两个重要的方法:allocate和load。allocate方法用于分配一个由wrapper代表的已经初始化完成的servlet。allocate方法同时也要考虑servlet是否实现了javax.servlet.SingleThreadModel接口,这将会在后面的章节讨论。load方法用于加载和初始化一个由wrapper表示的servlet。
public Servlet allocate() throws ServletException {
public void load() throws ServletException;
这是两个方法的签名。
剩下的方法将会在11章讨论。
Context接口
Context也是一个容器,它代表了一个servlet context,也就是代表了一个Web应用或是整个Web工程。一个Context可以有一个或是多个Wrapper作为它的字容器。
比较重要的方法有:addWrapper和createWrapper。在12章我们将详细讨论这个接口。