這一節基於《深度剖析Tomcat》第五章:容器 總結而成html
必定要先到個人github上下載本書相關的代碼,同時去上網下載這本書。java
看了不懂的歡迎加我QQ一塊兒探討或者在博客下評論發問。git
時隔兩個多月,中間包括了學校的期末考試月和一些瑣粹的事情。github
這一部分會先給你們先總結一下Tomcat鏈接器的相關內容,而後是 Tomcat 容器的整體介紹,最後在下一部分進入本章的主題: Tomcat 容器 的詳細講解。web
咱們都知道 Catalina 的核心組件包括 鏈接器和容器,那鏈接器是幹嗎的呢?前面講過,總的來講就是等待用戶的請求,接收用戶請求,解析請求,封裝成請求對象和響應對象,而後將請求和響應對象交給容器處理。apache
整個過程的細節包括了建立ServerSocket對象,建立處理器池,在每一個處理器內建立請求和響應對象,而後啓動全部處理器線程(阻塞,由於沒有請求到達,沒法處理),而後等待用戶請求(阻塞), 用戶請求到來時,獲取其中一個處理器線程,將socket交給處理器處理,以喚醒處理器線程(利用available變量),讓處理器線程解析請求,具體方法是解析端口,解析請求頭等信息,解析完後交給容器真正處理請求的內容(如servlet,就是調用咱們常說的invoke方法),處理完後就講處理器的全部變量恢復到未被使用前,最後回收回線程池。安全
connector.getContainer().invoke(request, response);
前面幾節咱們都在學習 Tomcat鏈接器是如何實現的,也學到了它實現的巧妙之處,而這一節咱們要開始學習Tomcat的另外一個組件:容器。和鏈接器同樣,容器有着不可忽略的做用,咱們看到鏈接器從頭至尾都是在作一些基礎的工做,從等待請求到解析請求,卻沒有參與處處理用戶真正想要的東西(如servlet要進行某些操做),因此說容器是鏈接器基礎工做作完後,作真正的請求處理操做(加載servlet)。看到咱們上面的鏈接器總結沒,invoke方法就是本節咱們要講解的重中之重----獲取容器執行invoke方法。服務器
容器是一個處理用戶 servlet 請求並返回對象給 web 用戶的模塊。org.apache.catalina.Container 接口定義了容器的形式,有四種容器: Engine(引擎) , Host(主機) , Context(上下文) , 和 Wrapper(包裝器)。app
我也按照書上的邏輯,先介紹Context和Wrapper,其它兩個留之後講解。eclipse
對於 Catalina 的容器首先須要注意的是它一共有四種不一樣的容器:
它們的標準實現是 StandardEngine,StandardHost, StandardContext, and StandardWrapper,它們都是org.apache.catalina.core 包的一部分,這些類都擴展了抽象類 ContainerBase。
要實現一個容器,首先必需要實現 org.apache.catalina.Container 接口。從上一節的鏈接器講解中咱們也看到了ex04.pyrmont.core.SimpleContainer 也實現了Container接口,實現了簡單的servlet的功能
public class SimpleContainer implements Container { public void invoke(Request request, Response response) throws IOException, ServletException { //中間省略 } }
同時,咱們須要知道的是並非每個功能都須要用到四種容器,下面的程序中會看到。
一個容器能夠有一個或多個低層次上的子容器,例如,一個 Context 有一個或多個 wrapper,而 wrapper 做爲容器層次中的最底層,不能包含子容器。可使用在 Container 接口中定義的 addChild()方法,wrapper調用會拋出異常。
容器的整個處理流程經過一個叫作Pipeline(流水線)的接口實現,這個接口的功能是放置一些Valve(閥門),這些閥門的功能是執行一些基礎功能操做,好比記錄日誌,記錄ip等,像流水線同樣,經過閥門上下文接口(ValveContext)調用下一個閥門,最後進入一個基礎閥門,實現用戶真正的請求。我估計若是以前沒學過容器的相關內容是看不懂我再說什麼的,第二部分再講解。
Pipeline: 一個 pipeline 包含了改容器要喚醒的全部任務。每個閥門表示了一個特定的任務。一個容器的流水線有一個基本的閥門,可是你能夠添加任意你想要添加的閥門。閥門的數目定義爲添加的閥門的個數(不包括基本閥門)。
Valve:閥門接口表示一個閥門,該組件負責處理請求。
ValveContext: 用於切換到下一個閥門
Contained:一個閥門能夠選擇性的實現 org.apache.catalina.Contained 接口。該接口定義了其實現類跟一個容器相關聯。包含 getContainer和setContainer 方法
Wrapper:org.apache.catalina.Wrapper 接口表示了一個包裝器。一個包裝器是表示一個獨立 servlet 定義的容器。包裝器繼承了 Container 接口,而且添加了幾個方法。包裝器的實現類負責管理其下層 servlet 的生命週期,包括 servlet 的 init,service,和 destroy 方法。
Context:一個 context 在容器中表示一個 web 應用。一個 context 一般含有一個或多個包裝器做爲其子容器。
但願你們看到不懂的不要沮喪,經過下面的講解,你們應該都能明白吧。 這裏省略了全部鏈接器的內容,從invoke方法開始講起。
爲了模擬容器的實現,容器,流水線等都是用ex05.pyrmont包下相關的類,如SimpleWrapper等,而不是去查看StandardWrapper源代碼
一樣的咱們先接觸啓動類ex05.pyrmont.startup.Bootstrap1.java,這個類是對單個servlet的處理,因此能夠看到只用了wrapper,沒有用到context。若是咱們要訪問,能夠用localhost:8080/ModernServlet
public final class Bootstrap1 { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); //定義一個包裝器類(容器),實際上connector.getContainer().invoke(request, response);
//就是調用SimpleWrapper對象的invoke方法 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.getContainer().invoke時要將請求交給容器處理 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(); } } }
固然,直接看註釋是沒用的,由於咱們不清楚咱們爲何要這樣作。不急,下面慢慢講解。記得不斷調回這個類整理邏輯哦
這個類算是本節比較重要的類了,大部分的接口均可以在這裏看到。 SimpleWrapper是invoke出現的地方。
補充說明在代碼後面
下面貼上比較重要的代碼和相應註釋
public class SimpleWrapper implements Wrapper, Pipeline { // the servlet instance private Servlet instance = null;//加載到的servlet實例 private String servletClass; private Loader loader;//類加載器 private String name; //從一開始就綁定建立的流水線是該容器 private SimplePipeline pipeline = new SimplePipeline(this); protected Container parent = null; public SimpleWrapper() { //其實咱們在建立SimpleWrapper對象時就設置流水線的基礎閥門,經過setBasic pipeline.setBasic(new SimpleWrapperValve()); } public synchronized void addValve(Valve valve) { //爲流水線添加其餘Valve pipeline.addValve(valve); } //這個方法的做用是加載Servlet,獲取到Servlet實例 public Servlet allocate() throws ServletException { // Load and initialize our instance if necessary if (instance==null) { try { instance = loadServlet(); } catch (ServletException e) { throw e; } catch (Throwable e) { throw new ServletException("Cannot allocate a servlet instance", e); } } return instance; } //具體加載servlet的方法 private Servlet loadServlet() throws ServletException { if (instance!=null) return instance; Servlet servlet = null; String actualClass = servletClass; if (actualClass == null) { throw new ServletException("servlet class has not been specified"); } Loader loader = getLoader();//獲取到咱們定義的類加載器類SimpleLoader,定義和咱們須要的加載方式 // Acquire an instance of the class loader to be used if (loader==null) { throw new ServletException("No loader."); } ClassLoader classLoader = loader.getClassLoader(); // Load the specified servlet class from the appropriate class loader Class classClass = null; try { if (classLoader!=null) { classClass = classLoader.loadClass(actualClass);//加載servlet } } catch (ClassNotFoundException e) { throw new ServletException("Servlet class not found"); } // Instantiate and initialize an instance of the servlet class itself try { servlet = (Servlet) classClass.newInstance();//建立servlet實例 } catch (Throwable e) { throw new ServletException("Failed to instantiate servlet"); } // Call the initialization method of this servlet try { //這裏調用了servlet的初始化方法,全部跟咱們之前學過的知識同樣,init方法只調用一次 servlet.init(null); } catch (Throwable f) { throw new ServletException("Failed initialize servlet."); } return servlet; } //就是調用這個invoke方法 public void invoke(Request request, Response response) throws IOException, ServletException { pipeline.invoke(request, response); }
這個類裏面有幾個比較關心的方法:
invoke方面裏面的是pipeline.invoke(request, response),前面咱們說到,Pineline的做用是負責處理多個任務,
有的人就認爲爲何不直接調用咱們的Valve,甚至說不直接去掉Pineline,這裏我要說一下,咱們不能簡簡單單的認爲容器就只是處理咱們要作的任務,還有不少任務要作的,好比記錄是什麼IP操做過的,這些就像咱們程序中用的攔截器同樣,須要先通過不少的中間步驟。因此回到Bootstrap1.java中的addValve操做,和setLoader方法,能夠理解了吧。固然,addValve要真正理解須要繼續看下去
那流水線的invoke是如何實現多個任務按順序處理執行的呢?
SimpleWrapper.java中一開始就建立流水線對象,並綁定容器爲SimpleWrapper,由於invoke中要用到
pipeline.invoke(request, response);
下面查看部分關鍵代碼
public class SimplePipeline implements Pipeline { public void invoke(Request request, Response response) throws IOException, ServletException { // Invoke the first Valve in this pipeline for this request (new SimplePipelineValveContext()).invokeNext(request, response); } //添加閥門 public void addValve(Valve valve) { if (valve instanceof Contained) ((Contained) valve).setContainer(this.container); synchronized (valves) { Valve results[] = new Valve[valves.length +1]; System.arraycopy(valves, 0, results, 0, valves.length); results[valves.length] = valve; valves = results; } } public void setBasic(Valve valve) { this.basic = valve; ((Contained) valve).setContainer(container); } //SimplePipeline的內部類,實現ValveContext接口,負責切換到下一個閥門 protected class SimplePipelineValveContext implements ValveContext { protected int stage = 0; public String getInfo() { return null; } 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);//最後纔是basic閥門 } else { throw new ServletException("No valve"); } } } // end of inner class }
須要注意的是:我專門加setBasic方法做爲關鍵代碼貼出來,是爲了提醒你們SimplePineline上在SimpleWrapper中已經設置了基本Valve,
咱們看到SimplePipeline的invoke方法裏面是建立一個內部類SimplePipelineValveContext對象:這個對象負責流水線上閥門Valve的切換,這個內部類實現了ValveContext接口,ValveContext接口中其中一個方法是invokeNext方法,用來調取下一個Valve,具體的實現也不難
下一小節,咱們先選擇其中一個閥門講解
這個類實現了Valve接口和Contained接口,咱們知道Valve的接口功能是負責處理請求,因此接口裏面有invoke方法
public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;
HeaderLoggerValve.java是用來輸出請求頭的內容。
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("------------------------------------"); } }
invoke(Request request, Response response, ValveContext context)方法中須要注意的是第三個參數ValveContext context, 咱們會看到SimplePipeline裏面傳遞了this引用,在某個閥門調用context的invokeNext方法時
valveContext.invokeNext(request, response);
以保證下標(subscript)和級別(stage)的值是正確的(由於是同一個引用),下一次執行的閥門就是valve[1], 也就是ClientIPLoggerValve。
最後調用的是基礎閥門
basic.invoke(request, response, this);//最後纔是basic閥門
SimpleWrapperValve.java也是一個閥門,因此也實現了Valve和Contained接口
public class SimpleWrapperValve implements Valve, Contained { protected Container container; 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; //看到沒,這裏強制轉換爲HttpServletRequest和HttpServletResponse 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,這個方法上面講了 servlet = wrapper.allocate(); if (hres!=null && hreq!=null) { servlet.service(hreq, hres);//這裏調用了service } else { servlet.service(sreq, sres); } } catch (ServletException e) { } } public String getInfo() { return null; } public Container getContainer() { return container; } public void setContainer(Container container) { this.container = container; } }
SimpleWrapperValve做爲基礎閥門,終於找到咱們夢寐已久的service方法
其中SimpleWrapper中咱們看到了這個容器是如何加載servlet的,加載的時候調用了servlet的init方法,
也就是基礎閥門的invoke方法中調用加載初始化(wrapper.allocate())
接下來調用了servlet的service方法,
咱們看到servlet是加載一次的而已,因此咱們能夠獲得結論:Servlet不是線程安全的(此次再也不是隻看結論了哦,咱們是推出來的,實在不信,能夠看看Standard*的源代碼)
講了這麼多,將全部的重要接口都混合在代碼裏面講解了,不知道你們認識到多少
我感受 Tomcat容器 理解代碼仍是次要的 最重要的是明白 Tomcat 爲何要這樣進行模塊設計,咱們能夠好好學習這種思想,爲何要分這些接口, 無論讀什麼,讀源代碼就是這樣,在理解是如何自己是如何實現的基礎上,理解設計思想。
Point: 讀同一份源代碼,一萬我的有一萬中感悟
附
相應代碼能夠在個人github上找到下載,拷貝到eclipse,而後打開對應包的代碼便可。
如發現編譯錯誤,多是因爲jdk不一樣版本對編譯的要求不一樣致使的,能夠無論,供學習研究使用。