1. 程式人生 > >深入剖析tomcat之servlet容器

深入剖析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容器將要呼叫的任務集合,定義如下:

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

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

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

valveContext按照字面意思就是閥門的上下文,用於遍歷valve

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

valveContext的invokeNext()方法的實現:

  1. publicfinalvoid 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.             elseif ((subscript == valves.length) && (basic != null))   
  6.                 {   
  7.                     basic.invoke(request, response, this);   
  8.                     }  
  9.                 else
  10.                 {   
  11.                     thrownew ServletException (sm.getString("standardPipeline.noValve"));  
  12.                 }   
  13.             }  

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

  1. publicvoid 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()方法實現如下:

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

如果注意到了invokeNext()的實現,這層呼叫類似與下面的圖:

類似與遞迴呼叫,輸出為

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

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

  1. publicvoid 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實現示例如下:

  1. publicvoid 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定義如下:

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

map方法返回一個子容器(wrapper)負責來處理請求。map方法有兩個引數,一個request物件和一個boolean值。實現將忽略第二個引數。這個方法是從請求物件中獲取context路徑和使用context的findServletMappi