Tomcat源码分析(四)--容器处理链接之责任链模式
目标:在这篇文章希望搞明白connector.getContainer().invoke(request, response);调用容器的invoke后是怎么传递到 servlet或者jsp的?
StandardEngine没有invoke方法,它继承与ContainerBase(事实上所有的容器都继承于ContainerBase,在ContainerBase类有一些容器的公用方法和属性),抽象类ContainerBase的invoke方法如下:
[java]
protected Pipeline pipeline = new StandardPipeline(this);//标准管道的实现StandardPipeline
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);//调用管道里的invoke
}
由代码可知ContainerBase的invoke方法是传递到Pipeline,调用了Pipeline的invoke方法。这里要说一下Pipeline这个类,这是一个管道类,每一个管道类Pipeline包含数个阀类,阀类是实现了Valve接口的类,Valve接口声明了invoke方法。管道和阀的概念跟servlet编程里面的过滤器机制非常像,管道就像过滤器链,阀就好比是过滤器。不过管道中还有一个基础阀的概念,所谓基础阀就是在管道中当管道把所有的普通阀都调用完成后再调用的。不管是普通阀还是基础阀,都实现了Value接口,也都继承于抽象类ValveBase。在tomcat中,当调用了管道的invoke方法,管道则会顺序调用它里面的阀的invoke方法。先看看管道StandardPipeline的invoke方法:
[java]
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);
}
其中StandardPipelineValveContext是管道里的一个内部类,内部类的作用是帮助管道顺序调用阀Value的invoke方法,下面看它的定义代码:
[java]
protected class StandardPipelineValveContext
implements ValveContext {
protected int stage = 0;
public String getInfo() {
return info;
}
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);//当基础阀调用完成后,调用管道的基础阀的invoke阀
} else {
throw new ServletException
(sm.getString("standardPipeline.noValve"));
}
}
}
内部类StandardPipelineValveContext的invokeNext方法通过使用局部变量来访问下一个管道数组,管道类的变量stage保存当前访问到第几个阀,valves保存管道的所有阀,在调用普通阀的invoke方法是,会把内部类StandardPipelineValveContext本身传进去,这样在普通阀中就能调用invokeNext方法以便访问下一个阀的invoke方法,下面看一个普通阀的invoke方法:
[java]
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);//使用调用下一个阀的invoke方法
System.out.println("Client IP Logger Valve");
ServletRequest sreq = request.getRequest();
System.out.println(sreq.getRemoteAddr());
System.out.println("------------------------------------");
}
这个阀的invoke方法,通过传进来到StandardPipelineValveContext(实现了ValveContext接口)的invokeNext方法来实现调用下一个阀的invoke方法。然后简单的打印了请求的ip地址。
最后再看StandardPipelineValveContext的invokeNext方法,调用完普通阀数组valves的阀后,开始调用基础阀basic的invoke方法,这里先说基础阀的初始化,在每一个容器的构造函数类就已经初始化了基础阀,看容器StandardEngine的构造函数:
[java]
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());//容器StandardEngine的基础阀StandardEngineValve
}
即在容器构造的时候就已经把基础阀添加进管道pipeline中,这样在StandardPipelineValveContext中的invokeNext方法里就能调用基础阀的invoke了,当basic.invoke(request, response, this);进入基础阀StandardEngineValve,看基础阀StandardEngineValve的invoke方法:
[java]
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
...........................
// Ask this Host to process this request
host.invoke(request, response);
}
这里省略了很多代码,主要是为了更加理解调用逻辑,在StandardEngine的基础阀StandardEngineValve里,调用了子容器invoke方法(这里子容器就是StandardHost),还记得一开始connector.invoke(request, response)(即StandardEngine的invoke方法)现在顺利的传递到子容器StandardHost的invoke方法,变成了StandardHost.invoke(request, response)。由此可以猜测StandardHost也会传递给它的子容器,最后传递到最小的容器StandardWrapper的invoke方法,然后调用StandardWrapper的基础阀StandardWrapperValue的invoke方法,由于StandardWrapper是最小的容器了,不能再传递到其他容器的invoke方法了,那它的invoke方法做了什么?主要做了两件事, 1:创建一个过滤器链并 2:分配一个servlet或者jsp,主要代码如下:
[java]
StandardWrapperValue的invoke方法
servlet = wrapper.allocate(); //分配一个servlet
....................................................................
// Create the filter chain for this request
ApplicationFilterChain filterChain =
createFilterChain(request, servlet);
.........................................................
String jspFile = wrapper.getJspFile();//分配一个jsp
if (jspFile != null)
 
补充:软件开发 , Java ,