1. 程式人生 > >阿里大神----教你如何深入剖析tomcat之servlet容器

阿里大神----教你如何深入剖析tomcat之servlet容器

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

本人目前時間和組織語言能力及功力有限,心有餘而力不足,網上看到有好心人(<span style="font-family:Microsoft YaHei">randyjiawenjie</span>)做過記錄,大概就是把書中重要的東西摘錄的了一下,本人就就特意摘錄以下了。再就是為了更有助理解,大家可以參考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()方法

對Java感興趣的同學可以加入到我們的學習 交流群:450936584 每天都會分享最新的視訊和資料 可以免費領取學習視訊和資料

servlet容器的作用

管理servlet資源,併為web客戶端填充response物件。

不同級別的容器

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

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

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

Wrapper:表示一個獨立的servlet

容器都實現了Container介面,繼承了ContainerBase抽象類。

管道任務

3個概念:pipeline、valve、valveContext

pipeline包含servlet容器將要呼叫的任務集合,定義如下:

[java]

view plaincopy

  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表示一個具體的執行任務,定義如下:

[java]view plaincopy

  1. publicinterface Valve {

  2. public String getInfo();

  3. publicvoid invoke(Request request, Response response, ValveContext context) throws IOException, ServletException;

  4. }

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

[java]view plaincopy

  1. publicinterface ValveContex{

  2. public String getInfo();

  3. publicvoid invokeNext(Request request, Response response) throws IOException, ServletException;

  4. }

valveContext的invokeNext()方法的實現:

[java]view plaincopy

  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方法可以如下實現:

[java]view plaincopy

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

[java]view plaincopy

  1. valveContext.invokeNext(request, response);

  2. System.out.println(“valve1 invoke!”);

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

類似與遞迴呼叫,輸出為

[java]view plaincopy

  1. “valve3 invoke!”

  2. “valve2 invoke!”

  3. “valve1 invoke!”

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

[java]view plaincopy

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

[java]view plaincopy

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

[java]view plaincopy

  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的findServletMapping方法獲取相關的路徑名。如果一個路徑名被找到,它使用context的findChild方法獲取一個Wrapper的例項。一個mapper的實現:

[java]view plaincopy

  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 plaincopy

  1. }

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

1. pipeline的invoke方法會呼叫新增到容器中的閥門的invoke方法,然後呼叫基本閥門的invoke方法。

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

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

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

[java]view plaincopy

  1. publicvoid 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例項相關聯