上一章讲完了复杂的默认连接器,这一章介绍的是容器。容器呢,是用来处理servlet请求并填充返回对象给web客户端的模块。接口Container定义了容器的形式,容器分为这四种:
- Engine:表示整个Catalina的serlvet引擎
- Host:表示一个拥有数个上下文(Context)的虚拟主机
- Context:表示一个Web应用,包含多个包装器(Wrapper)
- Wrapper:表示一个独立的serlvet
本章先讲的是Context和Wrapper,不过在讲这两个之前要先了解一下管道(pipeline)和阀门(valve),一个管道(pipeline)中包含了该容器要调用所有的任务,而每个阀门(valve)则代表了每个特定的任务。打个比方,一个饮料厂里有一条生产饮料的流水线,那条流水线的管道(pipeline)刚开始流过的是自来水,后来经过一个一个的阀门(valve),每个阀门都添加需要添加的添加剂(特定的任务),最后从出口流出来的就是按照配方(配置文件server.xml)做出来的饮料了。
一个容器可以有一个管道。当容器的invoke()方法被调用时,容器将通过管道调用第一个阀门,随后一个接着一个调用阀门,直到所有的阀门被调用完为止。那么这些是怎么实现的呢?首先我们来看一张类图:
然后,我们来看一下最重要的invoke方法,有很多接口都有invoke方法,我们都来看一下:
public void invoke(Request request, Response response)
throws IOException, ServletException;
首先Container接口有invoke方法,这个方法在containerBase得到了实现:
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
这里调用了管道的invoke方法,然后我们来看一下实现了ContainerBase的StandarPipeline类,这个类最为关键:
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);
}
protected class StandardPipelineValveContext
implements ValveContext {
...
protected int stage = 0;
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"));
}
}
}
这里的invoke方法,调用了内部类StandardPipelineValveContext的invokeNext方法,方法中,用stage和subscript 记录了要唤醒的阀门,每次invokeNext方法被调用时,stage会加1,指向下一个要被调用的阀门。所以每个阀门里都会调用invokeNext,用来激活下一个阀门,最后激活基本阀门。
public class HeaderLoggerValve implements Valve, Contained {
protected Container container;
public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException {
// Pass this request on to the next valve in our pipeline
valveContext.invokeNext(request, response);
System.out.println("Header Logger Valve");
ServletRequest sreq = request.getRequest();
if (sreq instanceof HttpServletRequest) {
HttpServletRequest hreq = (HttpServletRequest) sreq;
Enumeration headerNames = hreq.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement().toString();
String headerValue = hreq.getHeader(headerName);
System.out.println(headerName + ":" + headerValue);
}
}
else
System.out.println("Not an HTTP Request");
System.out.println("------------------------------------");
}
...
}
最后来总结一下所有阀门被一个个调用的步骤:
- 首先容器调用invoke方法。
- 容器的invoke方法会调用管道的invoke方法。
- 管道的invoke方法会调用内部类的invokeNext方法,成员属性stage会加一,指向下一个阀门。
- invokeNext会调用阀门的invoke方法,做阀门特定的任务。
- 阀门会调用invokeNext方法,继续唤醒下一个阀门,直到基本阀门为止。