天天看點

深入剖析tomcat之servlet容器

       其實我們開發中經常用tomcat應用伺服器,tomcat就一個servlet容器,能夠運作基于serlvlet的應用程式并響應相應的http請求,開發時間長了,還是想想具體知道它是怎麼運作的,尤其是servlet容器的機理,是以有幸拜讀了外國人的《深入剖析tomcat》,感覺挺不錯的,可以在此點選免費下載下傳電子書,建議大家有時間讀讀,在讀的過程中邊讀邊翻閱着tomcat的源碼,更有助于你了解它的各個機制,此處有tomcat 7的源碼,點選可免費下載下傳。

       本人目前時間群組織語言能力及功力有限,心有餘而力不足,網上看到有好心人(randyjiawenjie)做過記錄,大概就是把書中重要的東西摘錄的了一下,本人就就特意摘錄以下了。再就是為了更有助了解,大家可以參考Tomcat 容器與servlet的互動原理,簡單介紹了serlvet的原理和生命周期,還有學習tomcat源碼(2) 實作servlet容器功能,這篇文章主要還是電子書的代碼及解釋,升華的文章Tomcat 系統架構與設計模式,第 2 部分: 設計模式分析。

主要是《深入剖析tomcat》的第五章和第十一章。個人覺得如下3點是關鍵:

1. pipeline相關概念及其執行valve的順序;

2. standardwrapper的接受http請求時候的調用序列;

3. standardwrapper基礎閥加載servlet的過程(涉及到STM);

順便問一句,應該每一個servlet程式員都知道filter。但是你知道Filter在tomcat的哪一個地方出現的嗎?答案是standardwrapper基礎閥會建立Filter鍊,并調用doFilter()方法

servlet容器的作用

管理servlet資源,并為web用戶端填充response對象。

不同級别的容器

Engine:表示整個Catalina的servlet引擎、

Host:表示一個擁有數個上下文的虛拟主機

Context:表示一個Web應用,一個context包含一個或多個wrapper

Wrapper:表示一個獨立的servlet

容器都實作了Container接口,繼承了ContainerBase抽象類。

管道任務

3個概念:pipeline、valve、valveContext

pipeline包含servlet容器将要調用的任務集合,定義如下:

[java]  view plain copy

  1. public interface Pipeline {   
  2. public Valve getBasic();   
  3. public void setBasic(Valve valve);   
  4. public void addValve(Valve valve);   
  5. public Valve[] getValves();   
  6. public void invoke(Request request, Response response) throws IOException, ServletException;   
  7. public void removeValve(Valve valve);  
  8. }  

valve表示一個具體的執行任務,定義如下:

[java]  view plain copy

  1. public interface Valve {   
  2.     public String getInfo();   
  3.     public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;  
  4. }  

valveContext按照字面意思就是閥門的上下文,用于周遊valve

[java]  view plain copy

  1. public interface ValveContex{   
  2.     public String getInfo();   
  3.     public void invokeNext(Request request, Response response) throws IOException, ServletException;  
  4. }  

valveContext的invokeNext()方法的實作:

[java]  view plain copy

  1. public final void invokeNext(Request request, Response response) throws IOException, ServletException {   
  2.     int subscript = stage; stage = stage + 1;  
  3.     if (subscript < valves.length)   
  4.         { valves[subscript].invoke(request, response, this); }   
  5.             else if ((subscript == valves.length) && (basic != null))   
  6.                 {   
  7.                     basic.invoke(request, response, this);   
  8.                     }  
  9.                 else   
  10.                 {   
  11.                     throw new ServletException (sm.getString("standardPipeline.noValve"));  
  12.                 }   
  13.             }  

一個閥門的invoke方法可以如下實作:

[java]  view plain copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {    
  2.  //Pass the request and response on to the next valve in our pipeline   
  3.  valveContext.invokeNext(request, response);   
  4.  // now perform what this valve is supposed to do ...   
  5.  }  

如果pipeline由valve1、valve2、valve3組成,調用valve1. Invoke()會發生什麼?執行順序是什麼樣的?假設valveN(N=1,2,3)的invoke()方法實作如下:

[java]  view plain copy

  1. valveContext.invokeNext(request, response);  
  2. System.out.println(“valve1 invoke!”);  

如果注意到了invokeNext()的實作,這層調用類似與下面的圖:

深入剖析tomcat之servlet容器

類似與遞歸調用,輸出為

[java]  view plain copy

  1. “valve3 invoke!”  
  2. “valve2 invoke!”  
  3. “valve1 invoke!”  

順序恰好于添加的順序相反。是以這個也可以說明,為什麼基礎閥看起來是放在最後的,但确實要做裝載servlet這樣的操作。其實,放在最後的閥門總是第一個被調用。書中的依次添加HeaderLoggerValve、ClientIPLoggerValve依次添加,那麼調用順序為ClientIPLoggerValve、HeaderLoggerValve。注意到書中示例程式的運作結果和添加閥門的順序。不過話說回來,如果valve的invoke()方法實作為:

[java]  view plain copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {    
  2. // now perform what this valve is supposed to do ...    
  3. valveContext.invokeNext(request, response); //Pass the request and response on to the next valve in our pipeline    
  4.  }  

那麼閥門調用的順序就會和閥門添加的順序一緻(個人感覺這樣好了解)

Wrapper接口

         Wrapper表示一個獨立servlet定義的容器,wrapper繼承了Container接口,并且添加了幾個方法。包裝器的實作類負責管理其下層servlet的生命周期。

包裝器接口中重要方法有allocate和load方法。allocate方法負責定位該包裝器表示的servlet的執行個體。Allocate方法必須考慮一個servlet,是否實作了avax.servlet.SingleThreadModel接口。

Wrapper調用序列:

(1)      連接配接器調用Wrapper的invoke()方法;

(2)      Wrapper調用其pipeline的invoke()方法;

(3)      Pipeline調用valveContext.invokeNext()方法;

基礎閥的invoke實作示例如下:

[java]  view plain copy

  1. public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {   
  2.     SimpleWrapper wrapper = (SimpleWrapper) getContainer();   
  3.     ServletRequest sreq = request.getRequest();   
  4.     ServletResponse sres = response.getResponse();   
  5.     Servlet servlet = null;   
  6.     HttpServletRequest hreq = null;   
  7.         if (sreq instanceof HttpServletRequest)   
  8.             hreq = (HttpServletRequest) sreq;   
  9.         HttpServletResponse hres = null;   
  10.             if (sres instanceof HttpServletResponse) hres = (HttpServletResponse) sres;   
  11.             // Allocate a servlet instance to process this request   
  12.             try {   
  13.                 servlet = wrapper.allocate();   
  14.                 if (hres!=null && hreq!=null)   
  15.                 {   
  16.                     servlet.service(hreq, hres);   
  17.                     }   
  18.                     else   
  19.                     {   
  20.                     servlet.service(sreq, sres);   
  21.                     }   
  22.                     }   
  23.                     catch (ServletException e) {  
  24.                     }  
  25.                     }  

Context應用程式

         Context包含多個wrapper,至于用哪一個wrapper,是由映射器mapper決定的。Mapper定義如下:

[java]  view plain copy

  1. public interface Mapper {  
  2.     public Container getContainer();   
  3.     public void setContainer(Container container);   
  4.     public String getProtocol();   
  5.     public void setProtocol(String protocol);   
  6.     public Container map(Request request, boolean update);   
  7. }  

map方法傳回一個子容器(wrapper)負責來處理請求。map方法有兩個參數,一個request對象和一個boolean值。實作将忽略第二個參數。這個方法是從請求對象中擷取context路徑和使用context的findServletMapping方法擷取相關的路徑名。如果一個路徑名被找到,它使用context的findChild方法擷取一個Wrapper的執行個體。一個mapper的實作:

[java]  view plain copy

  1. public Container map(Request request, boolean update) {   
  2.      // Identify the context-relative URI to be mapped   
  3.      String contextPath = ((HttpServletRequest) request.getRequest()).getContextPath();   
  4.      String requestURI = ((HttpRequest) request).getDecodedRequestURI();   
  5.      String relativeURI = requestURI.substring(contextPath.length());   
  6.      // Apply the standard request URI mapping rules from   
  7.      // the specification   
  8.      Wrapper wrapper = null;   
  9.      String servletPath = relativeURI;   
  10.      String pathInfo = null;   
  11.      String name = context.findServletMapping(relativeURI);   
  12.      if (name != null)   
  13.        wrapper = (Wrapper) context.findChild(name);  
  14.        return (wrapper);  

[java]  view plain copy

  1. }  

容器包含一條管道,容器的invoke方法會調用pipeline的invoke方法。

1. pipeline的invoke方法會調用添加到容器中的閥門的invoke方法,然後調用基本閥門的invoke方法。

2.在一個wrapper中,基礎閥負責加載相關的servlet類并對請求作出相應。

3. 在一個包含子容器的Context中,基礎閥使用mapper來查找負責處理請求的子容器。如果一個子容器被找到,子容器的invoke方法會被調用,然後傳回步驟1。

         Context的一個基礎閥示例如下:注意最後一句話:wrapper.invoke(request, response); 

[java]  view plain copy

  1. public void invoke(Request request, Response response,ValveContext valveContext)   
  2.    throws IOException, ServletException {   
  3.    // Validate the request and response object types   
  4.    if (!(request.getRequest() instanceof HttpServletRequest) ||   
  5.      !(response.getResponse() instanceof HttpServletResponse)) {   
  6.      return;   
  7.    }  
  8.    // Disallow any direct access to resources under WEB-INF or META-INF   
  9.    HttpServletRequest hreq = (HttpServletRequest) request.getRequest();   
  10.    String contextPath = hreq.getContextPath();   
  11.    String requestURI = ((HttpRequest) request).getDecodedRequestURI();   
  12.    String relativeURI = requestURI.substring(contextPath.length()).toUpperCase();  
  13.    Context context = (Context) getContainer();   
  14.    // Select the Wrapper to be used for this Request   
  15.    Wrapper wrapper = null;   
  16.    try {   
  17.      wrapper = (Wrapper) context.map(request, true);   
  18.    }catch (IllegalArgumentException e) {   
  19.      badRequest(requestURI, (HttpServletResponse)   
  20.        response.getResponse());   
  21.        return;   
  22.    }  
  23.    if (wrapper == null) {   
  24.      notFound(requestURI, (HttpServletResponse) response.getResponse());   
  25.      return;   
  26.    }   
  27.    // Ask this Wrapper to process this Request   
  28.    response.setContext(context);   
  29.    wrapper.invoke(request, response);   
  30. }  

standardwrapper 方法調用序列Sequence of Methods Invocation

對于每一個連接配接,連接配接器都會調用關聯容器的invoke方法。接下來容器調用它的所有子容器的invoke方法。 如果一個連接配接器跟一個StadardContext執行個體相關聯,那麼連接配接器會調用StandardContext執行個體的invoke方法,該方法會調用所有它的子容器的invoke方法。

深入剖析tomcat之servlet容器

具體過程

1. 連接配接器建立request和response對象;

2.連接配接器調用StandardContext的invoke方法;

3.StandardContext的invoke方法必須調用該管道對象的invoke方法。StandardContext管道對象的基礎閥是StandardContextValve類的執行個體。是以,StandardContext管道對象會調用StandardContextValve的invoke方法。

4.StandardContextValve的invoke方法擷取相應的wrapper,處理http請求,調用wrapper的invoke方法

5.StandardWrapper是wrapper的标準實作,StandardWrapper對象的invoke方法調用pipeline的invoke方法。

6,StandardWrapper流水線的基本閥門時StandardWrapperValve。是以StandardWrapperValve的invoke方法會被調用。StandardWrapperValve的invoke方法會調用包裝器的allocate方法獲得一個servlet的執行個體。

7,當一個servlet需要被加載的時候,方法allocate調用方法load來加載一個servlet

8,方法load會調用servlet的init方法

我們主要關注的是一個servlet被調用的時候發生的細節。是以我們需要看StandardWrapper和StandarWrapperValve類

javax.servlet.SingleThreadModel

一個servlet可以實作javax.servlet.SingleThreadModel接口,實作此接口的一個servlet通俗稱為SingleThreadModel(STM)的程式元件。

根據Servlet規範,實作此接口的目的是保證servlet一次隻能有一個請求。

StandardWrapper實作

一個StandardWrapper對象的主要職責是:加載它表示的servlet,并執行個體化。該StandardWrapper不會調用servlet的service方法這個任務留給StandardWrapperValve對象,在StandardWrapper執行個體的基本閥門管道。StandardWrapperValve對象通過調用StandardWrapper的allocate方法獲得Servlet執行個體。在獲得Servlet執行個體之後的StandardWrapperValve調用servlet的service方法。

在servlet第一次被請求的時候,StandardWrapper加載servlet類。它是動态的加載servlet,是以需要知道servlet類的完全限定名稱。通過StandardWrapper類的setServletClass方法将servlet的類名傳遞給StandardWrapper。必須考慮一個servlet是否實作了SingleThreadModel接口。 如果一個servlet沒有實作SingleThreadModel接口,StandardWrapper加載該servlet一次,對于以後的請求傳回相同的執行個體即可。

對于一個STM servlet,情況就有所不同了。StandardWrapper必須保證不能同時有兩個線程送出STM servlet的service方法。

[java]  view plain copy

  1. Servlet instance = <get an instance of the servlet>;  
  2. if ((servlet implementing SingleThreadModel>) {  
  3.     synchronized (instance) {  
  4.         instance.service(request, response);  
  5.     }  
  6. }else{  
  7.     instance.service(request, response);  
  8. }  

Allocating the Servlet

StandardWrapperValve的invoke方法調用了包裝器的allocate方法來獲得一個請求servlet的執行個體,是以StandardWrapper類必須實作該接口

public javax.servlet.Servlet allocate() throws ServletException;

由于要支援STM servlet,這使得該方法更複雜了一點。實際上,該方法有兩部分組成,一部分負責非STM servlet的工作,另一部分負責STM servlet。

第一部分的結構如下

if (!singleThreadModel) {

 // returns a non-STM servlet instance 

}

布爾變量singleThreadModel負責标志一個servlet是否是STM servlet。

它的初始值是false,loadServlet方法會檢測加載的servlet是否是STM的,如果是則将它的值該為true

第二部分處理singleThreadModel為true的情況

synchronized (instancepool) { 

 // returns an instance of the servlet from the pool 

}

對于非STM servlet,StandardWrapper定義一個java.servlet.Servlet類型的執行個體

private Servlet instance = null;

方法allocate檢查該執行個體是否為null,如果是調用loadServlet方法來加載servlet。然後增加contAllocated整型并傳回該執行個體。

[java]  view plain copy

  1. if (!singleThreadModel) {  
  2.     if (instance == null) {  
  3.     synchronized (this) {  
  4.         if (instance == null) {  
  5.             try {   
  6.                 instance = loadServlet();  
  7.             }catch (ServletException e) {   
  8.                 throw e;  
  9.             }  
  10.         }  
  11.     }  
  12. }  
  13. if (!singleThreadModel) {  
  14.     countAllocated++;  
  15.     return (instance);  
  16. }  

StandardWrapperValve

StandardWrapperValve是StandardWrapper執行個體上的基本閥,主要完成2個操作:

1,執行與該servlet的所有過濾器;(看到了嗎,filter是在StandardWrapper的基礎閥出現的)

2,調用servlet執行個體的的service()方法

要實作這些内容,下面是StandardWrapperValve在他的invoke方法要實作的

. 調用StandardWrapper的allocate的方法來獲得一個servlet執行個體 

 ·調用它的private createFilterChain方法獲得過濾鍊

· 調用過濾器鍊的doFilter方法。包括調用servlet的service方法

· 釋放過濾器鍊

· 調用包裝器的deallocate方法

· 如果Servlet無法使用了,調用Wrapper的unload方法