Tomcat7源碼分析

一、背景

        Tomcat作爲JavaWeb領域的Web容器,目前在我們淘寶也使用的也非常廣泛,現在基本上所有線上業務系統都是部署在Tomcat上。爲了對平時開發的Web系統有更深入的理解以及出於好奇心對我們寫的Web系統是如何跑在Tomcat上的,於是仔細研究了下Tomcat的源碼。大家都知道Servlet規範是Java領域中爲服務端編程制定的規範,對於我們開發者只是關注了Servlet規範中提供的編程組件(ServletContextListener,Filer,Servlet) 等 ,但是規範中還有一些我們經常使用的接口(ServletContext,ServletRequest,ServletResponse,FilterChain)等都是由Tomcat去實現的,並且我們開發者實現的編程組件只是被Tomcat去回調而已。所以看Tomcat源碼實現也有助於我們更好的理解Servlet規範及系統如何在容器中運行(一些開源的MVC框架如Struts2,Webx,SpringMVC本質無非就是這個),順便整理了一下與大家分享(Tomcat版本7.0.23,JDK版本1.6)。

二、Tomcat源碼目錄結構


三、Tomcat體系結構

        仔細查看下圖(網絡上描述Tomcat架構比較清晰的一張圖),不難發現其中的Connecotr組件以及與Container組件是Tomcat的核心。一個Server可以有多個Service,而一個Service可以包含了多個Connector組件和一個Engine容器組件,一個Engine可以由多個虛擬主機Host組成,每一個Host下面又可以由多個Web應用Context構成,每一個的Context下面可以包含多個Wrapper(Servlet的包裝器)組成。

       Tomcat將Engine,Host,Context,Wrapper統一抽象成Container。一個抽象的Container模塊可以包含各種服務。例如,Manager管理器(Session管理),Pipeline管道( 維護管道閥門Value )等。Lifecycle接口統一定義了容器的生命週期,通過事件機制實現各個容器間的內部通訊。而容器的核心接口Container的抽象實現中定義了一個Pipeline,一個Manager,一個Realm以及ClassLoader統一了具體容器的實現規範。連接器(Connector)組件的主要任務是爲其所接收到的每一個請求(可以是HTTP協議,也可以AJP協議),委託給具體相關協議的解析類ProtocolHandler,構造出Request 對象和Response 對象。然後將這兩個對象傳送給容器(Container)進行處理。容器(Container)組件收到來自連接器(Connector)的Request 和Response對象後,負責調用Filter,最後調用Servlet的service 方法(進入我們開發的Web系統中)。

四、Tomcat源碼解析

        Servlet規範由一組用 Java編程語言編寫的類和接口組成。Servlet規範爲服務端開發人員提供了一個標準的 API以及爲服務器廠商制定了相關實現規範,開發人員只需要關心Servlet規範中的編程組件(如Filter,Servlet等),其他規範接口由第三方服務器廠商(如Tomcat)去實現,三者的關係如下圖,Servlet規範之於Tomcat的關係,也類似於JDBC規範與數據庫驅動的關係,本質就是一套接口和一套實現的關係。對於一個Web服務器主要需要做的事情,個人認爲基本由以下組件組成: [TCP連接管理] --> [請求處理線程池管理] --> [HTTP協議解析封裝] --> [Servlet規範的實現,對編程組件的回調] --> [MVC框架,Web系統業務邏輯]

       Tomcat的源碼及功能點因爲實在過於龐大,下面的源碼解析不可能全部功能模塊都涉及到,接下來我們主要會根據Tomcat的啓動、請求處理、關閉三個流程來梳理Tomcat的具體實現,儘可能把各個模塊的核心組件都展示出來。 基本上每個流程的源碼分析我都畫了時序圖,如果不想看文字的話,可以對着時序圖看(如果想看完整的源代碼分析也會在附件中上傳),最後還會分析下Tomcat的Connector組件及Tomcat運行過程中的線程概況及線程模型及Tomcat的類加載機制及Tomcat所涉及的設計模式。

                                                              Servlet規範和Web應用及Web容器間的關系                                                                                                                           

                                                           Tomcat對Servlet規範的實現

1. Tomcat的啓動流程         

Tomcat在啓動時的重點功能如下:

·        初始化類加載器:主要初始化Tomcat加載自身類庫的StandardClassLoader。

·        解析配置文件:使用Digester組件解析Tomcat的server.xml,初始化各個組件(包含各個web應用,解析對應的web.xml進行初始化)。

·        初始化Tomcat的各級容器Container,當然最後會初始我們Web應用(我們熟悉的Listener,Filter,Servlet等初始化等在這裏完成)。

·        初始化連接器Connector:初始化配置的Connector,以指定的協議打開端口,等待請求。

不管是是通過腳本bootstrap.sh啓動還是通過Eclipse中啓動,Tomcat的啓動流程是在org.apache.catalina.startup.Bootstrap類的main方法中開始的,啓動時這個類的核心代碼如下所示:

Java代碼 

1.  public static void main(String args[]) {  

2.      if (daemon == null) {  

3.           daemon = new Bootstrap(); //實例化Bootstrap的一個實例  

4.           try {

5.               // 創建StandardClassLoader類加載器,並設置爲main線程的線程上下文類加載器

6.               // 通過StandardClassLoader加載類Cataline並創建Catalina對象 ,並保存到Boosstrap對象中

7.               daemon.init();

8.           } catch (Throwable t) {  

9.          }  

10.     }  

11.     if (command.equals("start")) {  

12.          daemon.setAwait(true);  

13.           daemon.load(args); //執行load,加載資源,調用Catalinaload方法,利用Digester讀取及解析server.xml配置文件並且創建StandardServer服務器對象 */

14.           daemon.start(); //啓動各個組件,容器開始啓動,調用Catalinastart方法

15.     }  

16. }  

從以上的代碼中,可以看到在Tomcat啓動的時候,執行了三個關鍵方法即init、load、和start。後面的兩個方法都是通過反射調用org.apache.catalina.startup.Catalina的同名方法完成的,所以後面在介紹時將會直接轉到Catalina的同名方法。首先分析一下Bootstrap的init方法,在該方法中將會初始化一些全局的系統屬性、初始化類加載器、通過反射得到Catalina實例,在這裏我們重點看一下初始化類加載器的initClassLoaders()方法

Java代碼 

1.  private void initClassLoaders() {  

2.          try {

3.             //創建StandardClassLoader類加載器  

4.              commonLoader = createClassLoader("common"null);  

5.              if( commonLoader == null ) {  

6.                  commonLoader=this.getClass().getClassLoader();  

7.              }  

8.              catalinaLoader = createClassLoader("server", commonLoader);  

9.              sharedLoader = createClassLoader("shared", commonLoader);  

10.         } catch (Throwable t) {  

11.             System.exit(1);  

12.         }  

13. }  

在以上的代碼總,我們可以看到初始化了StandardClassLoader類加載器,這個類加載器詳細的會在後面關於Tomcat類加載器機制中介紹。

然後我們進入Catalina的load方法:

Java代碼 

1.  public void load() {  

2.          //初始化Digester組件,定義瞭解析規則  

3.          Digester digester = createStartDigester();  

4.          try {  

5.              inputSource.setByteStream(inputStream);  

6.              digester.push(this);  

7.              //通過Digester解析這個文件,在此過程中會初始化各個組件實例及其依賴關係

8.             //最後會把server.xml文件中的內容解析到StandardServer

9.              digester.parse(inputSource);  

10.             inputStream.close();  

11.         } catch (Exception e) {}  

12.         if (getServer() instanceof Lifecycle) {  

13.             try {

14.                // 調用Serverinitialize方法,初始化各個組件  

15.                 getServer().init();  

16.             } catch (LifecycleException e) {  

17.             }  

18.         }  

19. }  

在以上的代碼中,關鍵的任務有兩項即使用Digester組件按照給定的規則解析server.xml、調用Server的init方法,而Server的init方法中,會發布事件並調用各個Service的init方法,從而級聯完成各個組件的初始化。每個組件的初始化都是比較有意思的,但是我們限於篇幅先關注Tomcat各級容器的初始化及Connector的初始化,這可能是最值得關注的地方。

首先看下StandardService的start方法(), 核心代碼如下:

1.  // StandardService的啓動  

2.  protected void startInternal() throws LifecycleException {

4.      //啓動容器StandardEngine 

5.      if (container != null) {  

6.          synchronized (container) {  

7.              // 啓動StandardEngine容器  

8.              container.start();  

9.          }  

10.     }  

12.     //兩個connector的啓動,HTTP/1.18080AJP/1.38009  

13.     synchronized (connectors) {  

14.         for (Connector connector: connectors) {  

15.             try {  

16.                 if (connector.getState() != LifecycleState.FAILED) {  

17.                     //依次啓動Connector[HTTP/1.1-8080]Connector[AJP/1.3-8009]

18.                     //這裏會創建請求線程執行器並啓動接收線程Acceptor

19.                     connector.start();  

20.                 }  

21.             } catch (Exception e) {  

22.             }  

23.         }  

24.     }  

25. }  

啓動Tomcat各級容器的會依次先啓動StandardEngine --> StandardHost --> StandardContext(代表一個WebApp應用), 因爲我們比較關心我們的Web應用是哪裏被初始化回調的,所以就重點看下StandardContext的start()方法,核心代碼如下:

Java代碼 

1.  //啓動WebApp應用 

2.  @Override  

3.  protected synchronized void startInternal() throws LifecycleException {  

4.      boolean ok = true;  

6.      if (getLoader() == null) {  

7.          //創建此WebApp應用的WebApp載入器WebAppLoader,這個應用的WebAppClassLoader類加載器全部是由這個載入器創建

8.          WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  

9.          webappLoader.setDelegate(getDelegate());  

10.         //在這裏開始啓動WebAppLoader加載器,並且創建此WebAppWebAppClassLoader類加載器,保存到WebAppLoader,最後會被設置到此ContextInstanceManager

11.         setLoader(webappLoader);  

12.     }  

14.     ///線程上下文類加載器切換成當前WebApp的類加載器,Contextloader中獲取 

15.     ClassLoader oldCCL = bindThread();  

17.     try {  

18.         if (ok) {  

19.             //觸發Context組件的configure_start事件,通知ContextConfig監聽器

20.             //開始解析Web應用WEB-INF/web.xml文件配置到WebXml對象,最後把配置信息全部解析到StandardContext

21.             fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);  

23.             if (pipeline instanceof Lifecycle) {  

24.                 //啓動StandardContext的管道Pipeline

25.                 ((Lifecycle) pipeline).start();  

26.             }  

27.         }  

28.     } finally {  

29.         //線程上下文類加載器切換成之前的StandardClassLoader

30.         unbindThread(oldCCL);  

31.     }  

33.     //線程上下文類加載器切換成當前WebApp的類加載器,Contextloader中獲取

34.     oldCCL = bindThread();  

36.     if (ok ) {  

37.         if (getInstanceManager() == null) {  

38.             Map<String, Map<String, String>> injectionMap = buildInjectionMap(  

39.                     getIgnoreAnnotations() ? new NamingResources(): getNamingResources());  

40.             //創建每個應用StandardContext自己的DefaultInstanceManager實例管理器對象

41.             //並且會從WebAppLoader拿到WebAppClassLoader類加載器,之後此WebApp的應用類都由此ClassLoader加載

42.             setInstanceManager(new DefaultInstanceManager(context,  

43.                     injectionMap, thisthis.getClass().getClassLoader()));  

44.         }  

45.     }  

47.     try {  

48.         //StandardContext的上下文參數設置到ServletContext

49.         mergeParameters();  

51.         if (ok) {  

52.             //啓動監聽器Listener

53.             if (!listenerStart()) {  

54.                 log.error( "Error listenerStart");  

55.                 ok = false;  

56.             }  

57.         }  

59.         try {  

60.             if ((manager != null) && (manager instanceof Lifecycle)) {  

61.                 //啓動Session管理器StandardManager

62.                 ((Lifecycle) getManager()).start();  

63.             }  

64.             super.threadStart();  

65.         } catch(Exception e) {  

66.             ok = false;  

67.         }  

69.         if (ok) {  

70.             //啓動過濾器Filter

71.             if (!filterStart()) {  

72.                 log.error("Error filterStart");  

73.                 ok = false;  

74.             }  

75.         }  

77.         if (ok) {  

78.             //啓動load-on-startupServlet

79.             loadOnStartup(findChildren());  

80.         }  

82.     } finally {  

83.         // WepApp應用啓動完成,線程上下文類加載器切換成之前的StandardClassLoader

84.         unbindThread(oldCCL);  

85.     }  

86. }  

Tomcat的各級容器初始化完成後,就開始對Connector的初始化,接着看Connector的initInternal方法,核心代碼如下:

Java代碼 

1.  public void initInternal() throws LifecycleException{  

2.          //該協議適配器會完成請求的真正處理     

3.          adapter = new CoyoteAdapter(this);  

4.          //對於不同的實現,會有不同的ProtocolHandler實現類, Http11Protocol用來處理HTTP請求  

5.          protocolHandler.setAdapter(adapter);

6.          try {  

7.             // 初始化Http11Protocol協議

8.              protocolHandler.init();  

9.          } catch (Exception e) {  

10.         }  

11. }  

Http11Protocol的init方法中,核心代碼如下:

Java代碼 

1.  public void init() throws Exception {  

2.          endpoint.setName(getName());

3.          endpoint.setHandler(cHandler);  

4.          try {  

5.              endpoint.init(); //核心代碼就是調用JIoEndpoint的初始化方法  

6.          } catch (Exception ex) {  

7.          }  

8.  }  

我們看到最終的初始化方法最終都會調到JIoEndpoint的bind方法,網絡初始化和對請求的最初處理都是通過該類及其內部類完成的,後續的內容會詳細闡述這個JioEndpoint:

Java代碼 

1.  public void bind() throws Exception {

2.          //請求接收線程數,默認爲1     

3.          if (acceptorThreadCount == 0) { 

4.              acceptorThreadCount = 1;  

5.          }  

6.          if (serverSocket == null) {  

7.              try {  

8.                  if (address == null) {  

9.                    //創建ServerSocket,綁定指定端口並打開該端口的服務,默認8080端口 

10.                   serverSocket = serverSocketFactory.createSocket(port, backlog);  

11.                 } else {  

12.                     serverSocket = serverSocketFactory.createSocket(port, backlog, address);  

13.                 }  

14.             } catch (BindException orig) {  

15.             }  

16.         }  

17.     }  

在上面的代碼中,我們可以看到此時初始化了一個ServerSocket對象,用來監聽綁定端口的請求。

緊接着我們看JioEndpoint的start()方法,核心代碼如下:

Java代碼 

1.  public void startInternal() throws Exception {  

2.      if (!running) {  

4.          if (getExecutor() == null) {  

5.              //創建請求處理線程池ThreadPoolExecutor, 請求接收線程啓動前必須把請求處理線程池準備好

6.              createExecutor();  

7.          }  

9.          initializeConnectionLatch();  

11.         //創建並啓動Acceptor接收線程

12.         startAcceptorThreads();  

13.     }  

14. }  

從以上的代碼,可以看到,如果沒有在server.xml中聲明Executor的話,將會使用內部的一個容量爲200的線程池用來後續的請求處理。並且按照參數acceptorThreadCount的設置,初始化線程來接受請求。而Acceptor就是正在接受請求並會分派給請求處理線程池:

Java代碼 

1.  protected class Acceptor implements Runnable {  

2.          public void run() {  

3.              while (running) {  

4.                     // 默認監聽8080端口請求,server.xml中的默認配置

5.                     Socket socket =serverSocketFactory.acceptSocket(serverSocket);

6.                      serverSocketFactory.initSocket(socket);  

7.                      // 處理Socket,把客戶端請求Socket交給線程池來處理,當前Acceptor線程繼續返回接收客戶端Socket  

8.                      if (!processSocket(socket)) {  

9.                          //關閉連接  

10.                         try {  

11.                             socket.close();  

12.                         } catch (IOException e) {  

13.                         }  

14.                     }  

15.             }  

16.         }  

17. }  

從這裏我們可以看到,Acceptor已經可以接收Socket請求了,並可以調用processSocket方法來對請求進行處理。至此,Tomcat的組件啓動初始化完成,等待請求的到來。

Tomcat的啓動流程具體序列圖如下:


 2. Tomcat一次完整請求的處理流程

Tomcat一次完整請求處理的重點功能如下:

·        接收Socket請求,把請求轉發到線程池,由線程池分配一個線程來處理。

·        獲取Socket數據包之後,解析HTTP協議,翻譯成Tomcat內部的Request和Response對象,再映射相應Container。

·        Request和Response對象進入Tomcat中的Container容器的各級Piepline管道去流式執行,最終會流到StandardWrapperValve這個閥門。

·        在 StandardWrapperValve這個閥門中,把當前請求的Filter及Servlet封裝成FilterChain, 最終執行FilterChain, 回調Web應用,完成響應。

JIoEndpoint的Acceptor線程在接收到用戶的請求之後,調用processSocket方法。該方法主要是從Executor請求處理線程池中獲取一個線程,然後啓用一個新線程執行Socket請求,JIoEndpoint的processSocket()方法的核心代碼如下:

Java代碼 

1.  protected boolean processSocket(Socket socket) {  

2.      try {  

3.          //Socket包裝成SocketWrapper

4.          SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);  

5.          //包裝成SocketProcessor交給線程池處理,當前Acceptor線程不處理,以便接收下一個到達的請求

6.          getExecutor().execute(new SocketProcessor(wrapper));  

7.      } catch (RejectedExecutionException x) {  

8.          return false;  

9.      }

10.     return true;  

11. }  

接着請求處理線程池會起一個線程來處理請求來執行SocketProcessor,而SocketProcessor的run()方法中會調用Http11ConnectionHandler的process方法,SocketProcessor的run()方法核心代碼如下:

Java代碼 

1.   // Tomcat線程池中的請求處理線程

2.   public void run() {  

3.       boolean launch = false;  

4.       synchronized (socket) {  

5.           try {  

6.               SocketState state = SocketState.OPEN;  

7.               if ((state != SocketState.CLOSED)) {  

8.                   if (status == null) {  

9.                      //通過Http11Protocol$Http11ConnectionHandler處理請求,引用外部類對象的成員handler

10.                      state = handler.process(socket, SocketStatus.OPEN);  

11.                  } else {  

12.                      state = handler.process(socket, status);  

13.                  }  

14.              }  

15.              if (state == SocketState.CLOSED) {  

16.                  try {  

17.                     //關閉Socket  

18.                    socket.getSocket().close();  

19.                  } catch (IOException e) {  

20.                  }  

21.              }

22.          }

23.      }  

24.      //清空請求Socket  

25.      socket = null;  

26. }  

在Http11ConnectionHandler中會根據當前請求的協議類型去創建相應的協議處理器,我們這裏分析的是HTTP協議,所以會創建Http11Processor去執行process()方法, 拿到Socket數據包後解析生成Tomcat內部的Request對象與Response對象。其中Request對象只是解析Header部分內容,請求參數等做延遲處理,接着就開始調用CoyoteAdapter類進入容器處理, Http11Processor的process方法核心代碼如下:

Java代碼 

1.  public SocketState process(SocketWrapper<S> socketWrapper)  

2.          throws IOException {  

3.          RequestInfo rp = request.getRequestProcessor();  

4.          //設置SocketWrapper  

5.          setSocketWrapper(socketWrapper);  

7.          //獲取Socket中的inputStream設置到inputBuffer,也就是設置到Request  

8.          getInputBuffer().init(socketWrapper, endpoint);  

9.          //獲取Socket中的outputStream設置到outputBuffer,也就是設置到Response

10.         getOutputBuffer().init(socketWrapper, endpoint);  

12.         while (!error && keepAlive && !comet && !isAsync() &&  

13.                 !endpoint.isPaused()) {  

14.             try {  

15.                 setRequestLineReadTimeout();  

17.                 //解析HTTP請求的method,requestURI,protocol 

18.                 if (!getInputBuffer().parseRequestLine(keptAlive)) {  

19.                     if (handleIncompleteRequestLineRead()) {  

20.                         break;  

21.                     }  

22.                 }  

24.                 if (endpoint.isPaused()) {  

25.                     response.setStatus(503);  

26.                     error = true;  

27.                 } else {  

28.                     request.setStartTime(System.currentTimeMillis());  

29.                     keptAlive = true;  

31.                     //解析HTTP請求的報頭headers  

32.                     if (!getInputBuffer().parseHeaders()) {  

33.                         openSocket = true;  

34.                         readComplete = false;  

35.                         break;  

36.                     }  

37.                 }  

38.             } catch (IOException e) {  

39.             } catch (Throwable t) {  

40.             }  

42.             if (!error) {  

43.                 try {  

44.                     //準備Request,根據已解析的信息做一些過濾 

45.                     prepareRequest();  

46.                 } catch (Throwable t) {  

47.                     ExceptionUtils.handleThrowable(t);  

48.                 }  

49.             }  

50.             if (!error) {  

51.                 try {  

52.                     //調用CoyoteAdapterservice方法,傳入org.apache.coyote.Request對象及org.apache.coyote.Response對象

53.                     adapter.service(request, response);  

54.                 } catch (InterruptedIOException e) {  

55.                     error = true;  

56.                 }  39.             } catch (Throwable t) {  

40.             }  

42.             if (!error) {  

43.                 try {  

44.                     //準備Request,根據已解析的信息做一些過濾 

45.                     prepareRequest();  

46.                 } catch (Throwable t) {  

47.                     ExceptionUtils.handleThrowable(t);  

48.                 }  

49.             }  

50.             if (!error) {  

51.                 try {  

52.                     //調用CoyoteAdapterservice方法,傳入org.apache.coyote.Request對象及org.apache.coyote.Response對象

53.                     adapter.service(request, response);  

54.                 } catch (InterruptedIOException e) {  

55.                     error = true;  

56.                 } 

57.            } 

58.        } 

相關文章
相關標籤/搜索