目標:在這篇文章希望搞明白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)
sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
else
sreq.removeAttribute(Globals.JSP_FILE_ATTR);
if ((servlet != null) && (filterChain != null)) {
filterChain.doFilter(sreq, sres);//調用過濾器鏈處理請求,sreq和sres是request和response的包裝類,在這裡面會調用servlet的services方法
}
這裡先不關註jsp,隻關註一下servlet,通過servlet = wrapper.allocate(); 進入StandardWrapper的allocate方法,allocate主要就是調用瞭loadServlet方法,在loadServlet方法類用tomcat自己的類加載器實例化瞭一個servlet對象,並調用瞭該servlet的init和service方法:www.aiwalls.com
[java]
StandardWrapper的loadServlet方法(這裡省略瞭很多其他的代碼)
Servlet servlet = null;
String actualClass = servletClass;//servlet的字節碼字符串
Loader loader = getLoader();
ClassLoader classLoader = loader.getClassLoader();//得到類加載器
Class classClass = null;
if (classLoader != null) {
System.out.println("Using classLoader.loadClass");
classClass = classLoader.loadClass(actualClass);//通過類加載器實例化servlet
} else {
System.out.println("Using forName");
classClass = Class.forName(actualClass);//通過反射實例化servlet
}
servlet = (Servlet) classClass.newInstance();//實例化servlet
servlet.init(facade);//調用servlet的init
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);
};//調用jsp的service方法,jsp會被編譯成servlet,所以也會有service方法
至此已經把請求傳遞到servlet的service(或者jsp的service)方法,整個處理請求到這裡就結束瞭,剩下的就是返回客戶端瞭。這裡提出幾個問題。1:那麼多的servlet,tomcat是怎麼知道要請求到哪個servlet? 這個問題留待下篇博客再來講吧。
作者:haitao111313