標籤:servlet tomcat 源碼 .重構
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中攔截器的請求流程一(類比大致流程)>>
http://blog.csdn.net/dlf123321/article/details/40078583
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(); } }}
運行結果如下:
看到了,其實在部分裡,不管你請求那個服務,返回的結果都一樣,為什麼?這個還用我來解釋嗎?
這一章東西比較多,關於context容器的使用,我們放在下一節中。
how tomcat works 五 servlet容器 上