前言
本文继续讲解TOMCAT的请求原理分析,建议朋友们阅读本文时首先阅读过《Tomcat7.0源码分析——请求原理分析(上)》和《Tomcat7.0源码分析——请求原理分析(中)》。在《Tomcat7.0源码分析——请求原理分析(中)》一文我简单讲到了Pipeline,但并未完全展开,本文将从Pipeline开始讲解请求原理的剩余内容。
管道
在Tomcat中管道Pipeline是一个接口,定义了使得一组阀门Valve按照顺序执行的规范,Pipeline中定义的接口如下:
- getBasic:获取管道的基础阀门;
- setBasic:设置管道的基础阀门;
- addValve:添加阀门;
- getValves:获取阀门集合;
- removeValve:移除阀门;
- getFirst:获取第一个阀门;
- isAsyncSupported:当管道中的所有阀门都支持异步时返回ture,否则返回false;
- getContainer:获取管道相关联的容器,比如StandardEngine;
- setContainer:设置管道相关联的容器。
Engine、Host、Context及Wrapper等容器都定义了自身的Pipeline,每个Pipeline都包含一到多个Valve。Valve定义了各个阀门的接口规范,其类继承体系如图1所示。
图1 Valve的类继承体系
这里对图1中的主要部分(LifecycleMBeanBase及Contained接口在《Tomcat7.0源码分析——生命周期管理》一文已详细阐述过)进行介绍:
- Valve:定义了管道中阀门的接口规范,getNext和setNext分别用于获取或者设置当前阀门的下游阀门,invoke方法用来应用当前阀门的操作。
- ValveBase:Valve接口的基本实现,ValveBase与Valve的具体实现采用抽象模板模式将管道中的阀门串联起来。
- StandardEngineValve:StandardEngine中的唯一阀门,主要用于从request中选择其host映射的Host容器StandardHost。
- AccessLogValve:StandardHost中的第一个阀门,主要用于管道执行结束之后记录日志信息。
- ErrorReportValve:StandardHost中紧跟AccessLogValve的阀门,主要用于管道执行结束后,从request对象中获取异常信息,并封装到response中以便将问题展现给访问者。
- StandardHostValve:StandardHost中最后的阀门,主要用于从request中选择其context映射的Context容器StandardContext以及访问request中的Session以更新会话的最后访问时间。
- StandardContextValve:StandardContext中的唯一阀门,主要作用是禁止任何对WEB-INF或META-INF目录下资源的重定向访问,对应用程序热部署功能的实现,从request中获得StandardWrapper。
- StandardWrapperValve:StandardWrapper中的唯一阀门,主要作用包括调用StandardWrapper的loadServlet方法生成Servlet实例和调用ApplicationFilterFactory生成Filter链。
代码清单1
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
代码清单1中的getContainer方法获取到的实际是StandardService中的StandardEngine容器,根据《Tomcat7.0源码分析——生命周期管理》一文的内容,我们知道StandardEngine继承自ContainerBase,所以这里的getPipeline方法实际是ContainerBase实现的,代码如下:
public Pipeline getPipeline() {
return (this.pipeline);
}
pipeline在ContainerBase实例化时生成,代码如下:
protected Pipeline pipeline =
CatalinaFactory.getFactory().createPipeline(this);
这里的CatalinaFactory采用单例模式实现,要获取CatalinaFactory实例,只能通过调用getFactory方法,见代码清单2。createPipeline方法中创建了StandardPipeline,StandardPipeline是Pipeline的标准实现。
代码清单2
public class CatalinaFactory {
private static CatalinaFactory factory = new CatalinaFactory();
public static CatalinaFactory getFactory() {
return factory;
}
private CatalinaFactory() {
// Hide the default constructor
}
public String getDefaultPipelineClassName() {
return StandardPipeline.class.getName();
}
public Pipeline createPipeline(Container container) {
Pipeline pipeline = new StandardPipeline();
pipeline.setContainer(container);
return pipeline;
}
}
代码清单1随后调用了StandardPipeline的getFirst方法(见代码清单3)用来获取管道中的第一个Valve ,由于Tomcat并没有为StandardEngine的StandardPipeline设置first,因此将返回StandardPipeline的basic。
代码清单3
public Valve getFirst() {
if (first != null) {
return first;
}
return basic;
}
代码清单3中的basic的类型是StandardEngineValve,那么它是何时添加到StandardEngine的StandardPipeline中的呢?还记得《Tomcat7.0源码分析——server.xml文件的加载与解析》一文介绍过的ObjectCreateRule?在执行ObjectCreateRule的begin方法时,会反射调用StandardEngine的构造器生成StandardEngine的实例,StandardEngine的构造器中就会给其StandardPipeline设置basic为StandardEngineValve,见代码清单4。
代码清单4
/**
* Create a new StandardEngine component with the default basic Valve.
*/
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
/* Set the jmvRoute using the system property jvmRoute */
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
}
// By default, the engine will hold the reloading thread
backgroundProcessorDelay = 10;
}
代码清单1中最后调用了StandardEngineValve的invoke方法(见代码清单5)正式将请求交给管道处理。根据《Tomcat7.0源码分析——请求原理分析(中)》一文对postParseRequest方法的介绍,request已经被映射到相对应的Context容器(比如/manager)。此处首先调用request的getHost方法(实质是通过request映射的Context容器获取父容器得到,见代码清单6)获取Host容器,然后调用Host容器的Pipeline的getFirst方法获得AccessLogValve。AccessLogValve的invoke方法(见代码清单7),从中可以看出调用了getNext方法获取Host容器的Pipeline的下一个Valve,并调用其invoke方法。
代码清单5
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
Host host = request.getHost();
if (host == null) {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getServerName()));