servlet容器是用來處理請求servlet資源,并為Web用戶端填充response對象的子產品。在上一篇文章(也就是書的第四章)我們設計了SimpleContainer類,讓他實作Container接口,也基本完成了容器的作用。但是我們得知道在實際的tomcat中有4類容器:
Engine: 表示整個Catalina servlet引擎;
Host: 包含一個或多個Context容器的虛拟主機;
Context:表示一個Web應用程式,一個Context可以包含多個Wrapper
Wrapper: 表示一個獨立的Servlet;
UML圖如下:
Container容器中包含addChild(Container con),removeChild(Container con),findChild(String name),findChildren()四個方法,方法的作用就不解釋了,Wrapper就表示一個獨立的Servlet是以,它的addChild方法體為空。
在本節,我們主要展示Wrapper的特性,其他的容器以後再說。
首先我們看看時序圖(第一次畫 可能在時序圖規則上有些問題)
管道任務
這節主要說明檔連接配接器調用了servlet容器的invoke方法後發生的事情。
管道包含了一個servlet容器要執行的任務(和本身的servlet無關,是指額外執行的任務)
一個Wrapper會包含一個管道,而一個管道會包含若幹個Valve(閥)
public class SimpleWrapper implements Wrapper, Pipeline {
// the servlet instance
private Servlet instance = null;
private String servletClass;
private Loader loader;
private String name;
private SimplePipeline pipeline = new SimplePipeline(this);
protected Container parent = null;
...
}
public class SimplePipeline implements Pipeline {
public SimplePipeline(Container container) {
setContainer(container);
}
// The basic Valve (if any) associated with this Pipeline.
protected Valve basic = null;
// The Container with which this Pipeline is associated.
protected Container container = null;
// the array of Valves
protected Valve valves[] = new Valve[0]; //所有的閥
}
在閥中,請求會像水一樣經過管道中的每一個閥,上圖中的閥n會調用我們請求的servlet類。
再調用容器的invoke方法後,會到如下的方法
invokeNext是StandardPipelineValveContext類中的方法(StandardPipelineValveContext是管道的一個内部類,是以可以通路管道的所有成員)
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("No valve");
}
}
} // end of inner class
ok?如水般流過。下面這行代碼值得我們仔細看看
valves[subscript].invoke(request, response, this);
也可參閱拙作
<<說說struts2中攔截器的請求流程一(模拟大緻流程)>>
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("------------------------------------");
}
}
這行代碼 valveContext.invokeNext(request, response); 看懂了吧
另一方面 大家應該能看出來,所有的閥都是逆向執行的,每次執行一個閥的時候,閥都會先傳遞給後面的閥,等到後面的執行完畢後,才執行自己的業務邏輯。
當通道裡的處了最後一個基礎閥外,都走了一遍的時候自然就執行如下代碼
basic.invoke(request, response, this);
問題是basic是誰?
這個咱們等會再說。
Pipeline接口
我們在這裡使用的是SimplePipeline,它實作了Pipeline的接口。
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);
}
Pipeline裡面有個基礎閥,是最後調用的,負責處理request對象魚response對象。因而這裡有getset方法
通道裡面存放的是閥,是以剩下的方法就不用多說了吧。
另外tomcat4中,還存在一個StandardPipelineValveContext,上面已經說它是SimplePipeline的一個内部類。
Valve接口
public interface Valve {
public String getInfo();
public void invoke(Request request, Response response,ValveContext context) throws IOException, ServletException;
}
很清晰,不是嗎?
Contained接口
public interface Contained {
public Container getContainer();
public void setContainer(Container container);
}
實作這個接口的類,至多與一個servlet容器相關聯。
Wrapper接口
首先我們在文章開始的時候就說了,Wrapper包裝的是一個最基礎的servlet
這個接口裡面有兩個方法比較重要
public javax.servlet.Servlet allocate() throws javax.servlet.ServletException;
public void load() throws javax.servlet.ServletException;
allocate會配置設定一個已經初始化的servlet執行個體,
load會載入它。
Wrapper應用程式
類圖如下:
SimpleLoader類
public class SimpleLoader implements Loader {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
ClassLoader classLoader = null;
Container container = null;
public SimpleLoader() {
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
classLoader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
}
}
構造函數會初始會類加載器,供SimpleWrapper使用
SimpleWrapper類
該類實作了 org.apache.catalina.Wrapper 接口并且實作了 allocate 方法和load 方法。
器構造函數如下
public SimpleWrapper() {
pipeline.setBasic(new SimpleWrapperValve());
}
上文我們曾問過,basic從哪裡來,從這裡來!
SimpleWrapperValve類
通過pipeline.setBasic(new SimpleWrapperValve())我們知道SimpleWrapperValve類是一個基礎閥,用來處理傳遞Servlet的參數,如下:
public void invoke(Request request, Response response, ValveContext valveContext)
throws IOException, ServletException {
SimpleWrapper wrapper = (SimpleWrapper) getContainer();
ServletRequest sreq = request.getRequest();
ServletResponse sres = response.getResponse();
Servlet servlet = null;
HttpServletRequest hreq = null;
if (sreq instanceof HttpServletRequest)
hreq = (HttpServletRequest) sreq;
HttpServletResponse hres = null;
if (sres instanceof HttpServletResponse)
hres = (HttpServletResponse) sres;
// Allocate a servlet instance to process this request
try {
servlet = wrapper.allocate();
if (hres!=null && hreq!=null) {
servlet.service(hreq, hres);
}
else {
servlet.service(sreq, sres);
}
}
catch (ServletException e) {
}
}
應該沒有什麼問題。
ClientIPLoggerValve類
是用來顯示用戶端的ip位址的,代碼從略,另外還有HeaderLoggerValve,用來顯示http的請求頭,代碼從略。
Bootstrap1啟動類
public final class Bootstrap1 {
@SuppressWarnings("deprecation")
public static void main(String[] args) {
/* call by using http://localhost:8080/ModernServlet,
but could be invoked by any name */
HttpConnector connector = new HttpConnector();
Wrapper wrapper = new SimpleWrapper();
wrapper.setServletClass("ModernServlet");
Loader loader = new SimpleLoader();
Valve valve1 = new HeaderLoggerValve();
Valve valve2 = new ClientIPLoggerValve();
wrapper.setLoader(loader);
((Pipeline) wrapper).addValve(valve1);
((Pipeline) wrapper).addValve(valve2);
connector.setContainer(wrapper);
try {
connector.initialize();
connector.start();
// make the application wait until we press a key.
System.in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
運作結果如下:
看到了,其實在部分裡,不管你請求那個服務,傳回的結果都一樣,為什麼?這個還用我來解釋嗎?