天天看點

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源碼目錄結構

Tomcat7源碼分析
Tomcat7源碼分析

三、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系統中)。

Tomcat7源碼分析

四、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容器間的關系                                                                                                                           

Tomcat7源碼分析

                                                           Tomcat對Servlet規範的實作

Tomcat7源碼分析

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,加載資源,調用Catalina的load方法,利用Digester讀取及解析server.xml配置檔案并且建立StandardServer伺服器對象 */

14.           daemon.start(); //啟動各個元件,容器開始啟動,調用Catalina的start方法

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.                // 調用Server的initialize方法,初始化各個元件  

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.18080和AJP/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加載器,并且建立此WebApp的WebAppClassLoader類加載器,儲存到WebAppLoader中,最後會被設定到此Context的InstanceManager中

11.         setLoader(webappLoader);  

12.     }  

14.     ///線程上下文類加載器切換成目前WebApp的類加載器,從Context的loader中擷取 

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的類加載器,從Context的loader中擷取

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, this, this.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-startup的Servlet

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的啟動流程具體序列圖如下:

Tomcat7源碼分析

 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.                     //調用CoyoteAdapter的service方法,傳入org.apache.coyote.Request對象及org.apache.coyote.Response對象

53.                     adapter.service(request, response);  

54.                 } catch (InterruptedIOException e) {  

55.                     error = true;  

56.                 } 

57.            } 

58.        } 

59. }  

緊接着CoyoteAdapter會調用Container容器的Pipeline管道一步一步的對Request,Response對象處理。Tomcat容器主要由4層組成,通過管道的模式将各個層的功能進行了獨立,也有利于對各層插入需要的功能。如果需要,隻要在server.xml中加入Value元素的配置即可完成擴充功能, CoyoteAdapter的service方法核心代碼如下:

Java代碼 

1.  public void service(org.apache.coyote.Request req,  

2.                      org.apache.coyote.Response res)  

3.      throws Exception {  

4.      Request request = (Request) req.getNote(ADAPTER_NOTES);  

5.      Response response = (Response) res.getNote(ADAPTER_NOTES);  

7.      if (request == null) {  

8.          // Tomcat容器中傳遞的Request和Response都是這裡建立的Request及Response對象 

9.          //通過Connector建立org.apache.catalina.connector.Request對象

10.         request = connector.createRequest();  

11.           

12.         request.setCoyoteRequest(req);  

14.         //通過Connector建立org.apache.catalina.connector.Response對象

15.         response = connector.createResponse();  

16.         //設定org.apache.coyote.Response對象

17.         response.setCoyoteResponse(res);  

19.         //把Request及Response對象互相關聯起來  

20.         request.setResponse(response);  

21.         response.setRequest(request);  

22.     }  

24.     try {  

25.         //解析HTTP協定  

26.         //解析請求的Cookie和sessionId等資訊  

27.         //從Connector中映射此請求對應的StandardHost及StandardContext及StandardWrapper */  

28.         boolean postParseSuccess = postParseRequest(req, request, res, response);  

30.         if (postParseSuccess) {  

31.             //開始調用Tomcat的容器,首先調用StandardEngine容器中管道Pipeline中的第一個Valve,傳入connector.Request和connector.Response

32.             connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

33.         }  

35.         //完成此次請求 

36.         request.finishRequest();  

37.         //完成此次響應,并送出響應資訊

38.         //如果response已經送出則直接傳回,否則送出response  

39.         response.finishResponse();  

40.     } catch (IOException e) {  

41.     } 

43. }  

StandardEngin容器預設配置了StandardEnginValve閥門。它主要做負責選擇相應的Host去處理請求,StandardEngineValve的invoke()方法的核心代碼如下:

Java代碼 

1.  public final void invoke(Request request, Response response)  

2.      throws IOException, ServletException {  

4.      // 得到此請求所對應的StandardHost容器

5.      Host host = request.getHost();  

7.      //調用StandardHost容器中管道Pipeline中的第一個Valve  

8.      host.getPipeline().getFirst().invoke(request, response);  

9.  }  

StandardHost預設情況下配置了ErrorReportValve閥門與StandardHostValue閥門。ErrorReportValve用于處理錯誤日志。StandardHostValue則選擇相應的Context容器,并且把目前Web應用的WebappClassLoader設定為線程上下文類加載器,保證我們的Web應用中拿到的目前線程類加載器為此應用的類加載器, StandardHostValve的invoke()方法的核心代碼如下:

Java代碼 

1.  public final void invoke(Request request, Response response)  

2.      throws IOException, ServletException {  

4.      //得到此次請求所對應的StandardContext容器 

5.      Context context = request.getContext();  

7.      if( context.getLoader() != null ) {   

8.           //線程上下文類加載器切換成目前WebApp的類加載器,從Context的loader中擷取

9.           Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());  

10.     }  

12.     if (asyncAtStart || context.fireRequestInitEvent(request)) {  

13.         try {  

14.             //調用StandardContext容器中管道Pipeline中的第一個Valve

15.             context.getPipeline().getFirst().invoke(request, response);  

16.         } catch (Throwable t) {  

17.             ExceptionUtils.handleThrowable(t);  

18.         }  

19.     }

20.     //還原StandardClassLoader類加載器為線程上下文類加載器  

21.     Thread.currentThread().setContextClassLoader(StandardHostValve.class.getClassLoader());  

22. }  

StandardContext預設情況下配置了StandardContextValve閥門。對于WEB-INF與META-INF目錄禁止通路的控制,之後擷取一個此請求的Wrapper容器, StandardContextValve的invoke()方法核核心代碼如下:

Java代碼 

1.  public final void invoke(Request request, Response response)  

2.       throws IOException, ServletException {

3.       // 對于WEB-INF與META-INF目錄禁止通路的控制

4.       MessageBytes requestPathMB = request.getRequestPathMB();  

5.       if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))  

6.               || (requestPathMB.equalsIgnoreCase("/META-INF"))  

7.               || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))  

8.               || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {  

9.           response.sendError(HttpServletResponse.SC_NOT_FOUND);  

10.          return;  

11.      }  

12.      //得到此請求所對應的StandardWrapper容器 

13.      Wrapper wrapper = request.getWrapper();  

15.      //調用StandardWrapper容器中管道Pipeline中第一個Valve的invoke()方法

16.      wrapper.getPipeline().getFirst().invoke(request, response);  

17.  }  

StandardWrapper容器預設情況下配置了StandardWrapperValve閥門,主要是找到目前請求的需要攔截的過濾器Filter及初始化目前請求的Servlet對象,最終會封裝在FilterChain對象中,責任鍊方式執行, StandardWrapperValve的invoke()方法的核心代碼如下:

Java代碼 

1.  public final void invoke(Request request, Response response)  

2.       throws IOException, ServletException {  

4.       //得到StandardWrapper容器  

5.       //每個請求都會對應相應的StandardWrapper及StandardWrapperValve對象  

6.       StandardWrapper wrapper = (StandardWrapper) getContainer();  

8.       //得到此請求的StandardContext對象  

9.       Context context = (Context) wrapper.getParent();  

11.      //得到所請求的Servlet對象

12.      Servlet servlet = null;  

14.      try {  

15.          if (!unavailable) {  

16.             //從StandardWrapper容器擷取一個Servlet對象, Servlet對象的建立及初始化init都在這裡執行

17.              servlet = wrapper.allocate();  

18.          }  

19.      } catch (UnavailableException e) {}

21.      //建立ApplicationFilterFactory對象

22.      ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance();  

24.      //建立此次請求的ApplicationFilterChain對象,并設定所請求的Servlet對象

25.      //每個請求都會建立一個ApplicationFilterChain對象,包裝了所請求的Servlet對象及一系列攔截的過濾器Filter對象

26.      ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet);  

28.      try {  

29.          if ((servlet != null) && (filterChain != null)) {  

30.               //調用ApplicationFilterChain的doFilter方法

31.              //傳入org.apache.catalina.connector.RequestFacade及org.apache.catalina.connector.ResponseFacade對象,開始進行請求處理 

32.              filterChain.doFilter(request.getRequest(),response.getResponse());  

33.          }

34.      } catch (ClientAbortException e) {  

35.          exception(request, response, e);  

36.      } 

38.      try {  

39.          if (servlet != null) {  

40.             //執行完後把Servlet執行個體回收到Servlet執行個體池  

41.              wrapper.deallocate(servlet);  

42.          }  

43.      } catch (Throwable e) {  

44.          ExceptionUtils.handleThrowable(e);  

45.      }  

46.  }  

最後就會進入ApplicationFilterChain,這個也是Tomcat管道處理請求的最後節點,在ApplicationFilterChain中會依次調用Web應用的Filter,然後最終調用目前請求映射的Servlet, ApplicationFilterChain的doFilter()方法核心代碼如下:

Java代碼 

1.  private void internalDoFilter(ServletRequest request,  

2.                                    ServletResponse response)  

3.          throws IOException, ServletException {  

5.          //如果還有過濾器Filter,則執行Filter  

6.          if (pos < n) {  

8.              //得到過濾器Filter  

9.              ApplicationFilterConfig filterConfig = filters[pos++];  

11.             Filter filter = null;  

12.             try {  

13.                 filter = filterConfig.getFilter();  

14.                 //執行過濾器Filter的doFilter(request,response,FilterChain)方法  

15.                filter.doFilter(request, response, this);  

17.            } catch (IOException e) {  

18.                 throw e;  

19.             } 

20.             //執行完Filter的doFilter()方法之後直接傳回internalDoFilter方法

21.             return;  

22.         }  

24.         try {  

25.             //過濾器Filter全部執行完,最終調用Servlet的service(request,response)方法完成Web應用的業務邏輯 */  

26.            servlet.service(request, response);  

27.         } catch (IOException e) {

28.             throw e;  

29.         } 

30.     }  

Tomcat一次完成請求的處理流程具體序列圖如下:

Tomcat7源碼分析

 3. Tomcat的關閉流程

       Tomcat的關閉流程和Tomcat啟動流程類似,都是之前已經啟動的元件依次關閉銷毀,篇幅原因不贅述了。

Tomcat的關閉流程具體序列圖如下:

Tomcat7源碼分析

 4. Tomcat的Connector元件

     前面也已經經介紹過,Connector元件是Service容器中的一部分。它主要是接收,解析HTTP請求,然後調用本Service下的相關Servlet。由于Tomcat從架構上采用的是一個分層結構,是以根據解析過的HTTP請求,定位到相應Servlet也是一個相對比較複雜的過程,整個Connector實作了從接收Socket到調用Servlet的全過程。先來看一下Connector的功能邏輯:

·        接收Socket

·        從Socket擷取資料包,并解析成HttpServletRequest對象

·        從Engine容器開始走調用流程,經過各層Valve,最後調用Servlet完成業務邏輯

·        傳回Response,關閉Socket

可以看出,整個Connector元件是Tomcat運作主幹,目前Connector支援的協定是HTTP和AJP,本文主要是針對HTTP協定的Connector進行闡述。先來看一下Connector的配置,在server.xml裡; 

Xml代碼  

1. <Connector port="80" URIEncoding="UTF-8" protocol="HTTP/1.1"   

2.                connectionTimeout="20000"   

3.                redirectPort="7443" />  

熟悉的80端口不必說了。"protocol"這裡是指這個Connector支援的協定。針對HTTP協定而言,這個屬性可以配置的值有: 

·        HTTP/1.1

·        org.apache.coyote.http11.Http11Protocol –BIO實作

·        org.apache.coyote.http11.Http11NioProtocol –NIO實作

配置"HTTP/1.1"和"org.apache.coyote.http11.Http11Protocol"的效果是一樣的,是以Connector的HTTP協定實作預設是支援BIO的。無論是BIO還是NIO都是實作一個org.apache.coyote.ProtocolHandler接口,是以如果需要定制化,也必須實作這個接口。 接下來再來看看預設狀态下HTTP Connector的架構及其消息流。

Tomcat7源碼分析

 可以看見Connector中三大塊 

·        Http11Protocol

·        Mapper

·        CoyoteAdapter

Http11Protocol 

       類完全路徑org.apache.coyote.http11.Http11Protocol,這是支援HTTP的BIO實作。Http11Protocol包含了JIoEndpoint對象及Http11ConnectionHandler對象。 

Http11ConnectionHandler對象維護了一個Http11Processor對象池,Http11Processor對象會調用CoyoteAdapter完成HTTPr Request的解析和分派。 

JIoEndpoint維護了兩個線程,Acceptor請求接收線程及Executor請求處理線程池。Acceptor是接收Socket,然後從 Executor請求處理線程池中找出空閑的線程處理socket,如果 Executor請求處理線程池沒有空閑線程,請求會被放入阻塞隊列等待有空閑線程。

Mapper 

      類完全路徑org.apache.tomcat.util.http.mapper.Mapper,此對象維護了一個從Host到Wrapper的各級容器的快照。它主要是為了,當HTTP Request被解析後,能夠将HTTP Request綁定到相應的Host,Context,Wrapper(Servlet),進行業務處理;

CoyoteAdapter 

      類完全路徑org.apache.catalina.connector.CoyoteAdapter,此對象負責将http request解析成HttpServletRequest對象,之後綁定相應的容器,然後從Engine開始逐層調用Valve直至該Servlet。比如根據Request中的JSESSIONID綁定伺服器端的相應Session。這個JSESSIONID按照優先級或是從Request URL中擷取,或是從Cookie中擷取,然後再Session池中找到相應比對的Session對象,然後将其封裝到HttpServletRequest對象,所有這些都是在CoyoteAdapter中完成的。看一下将Request解析為HttpServletRequest對象後,開始調用Servlet的代碼; 

Java代碼  

1. connector.getContainer().getPipeline().getFirst().invoke(request, response);

Connector的拿到的容器就是StandardEngine,代碼的可讀性很強,擷取StandardEngine的Pipeline,然後從第一個Valve開始調用邏輯。

Tomcat的Connector元件工作具體序列圖入下:

Tomcat7源碼分析

 5. Tomcat運作過程中的線程概況及線程模型

       Tomcat在運作過程中會涉及到很多線程,主要的線程有Tomcat啟動時的main主線程(Java程序啟動時的那個線程), 子容器啟動與關系線程池(用來啟動關系子容器),Tomcat背景線程(Tomcat内置的背景線程,比如用來熱部署Web應用), 請求接收線程(用來接收請求的線程), 請求處理線程池(用來處理請求的線程)。了解了Tomcat運作過程中的主要線程,有助于我們了解整個系統的線程模型。下面是每個線程的源碼及功能的詳細分析:

Java代碼

1.  (1) main主線程:即從main開始執行的線程,在啟動Catalina之後就一直在Server的await方法中等待關閉指令,如果未接收到關閉指令就一直等待下去。main線程會一直阻塞着等待關機指令。  

2.         啟動過程:Bootstrap.main>>

3.                           Catalina.start >>

4.                                   Catalina.await() >>

5.                                               StandardServer.await()

7.  (2) 子容器啟動與關閉線程:ContainerBase的protected ThreadPoolExecutor startStopExecutor容器線程池執行器[處理子容器的啟動和停止]在ContainerBase.initInternal()方法中進行初始化       作用:在ContainerBase.startInternal()方法中啟動子容器,在ContainerBase.stopInternal()方法中停止子容器,即處理子容器的啟動和停止事件。  

8.         子容器線程池建立過程:StandardService.initInternal>>

9.                                        ContainerBase.initInternal >> 

10.                                                      new ThreadPoolExecutor()  

11.        啟動過程:StandardService.startInternal>>

12.                               ContainerBase.startInternal >>

13.                                                startStopExecutor.submit(new StartChild(Child)或StopChild(Child))>>

14.                                                                                                        execute(ftask) >>           

15.                                                                                                                     addIfUnderCorePoolSize(command)             

16.        addIfUnderCorePoolSize内容如下:  

17. private boolean addIfUnderCorePoolSize(Runnable firstTask) {  

18.         Thread t = null;  

19.         final ReentrantLock mainLock = this.mainLock;  

20.         mainLock.lock();  

21.         try {  

22.             if (poolSize < corePoolSize && runState == RUNNING)  

23.                 t = addThread(firstTask);  

24.         } finally {  

25.             mainLock.unlock();  

26.         }  

27.         if (t == null)  

28.             return false;  

29.         t.start(); // StartChild.call()被調用,調用子容器的child.start()啟動子容器   

30.         return true;  

31. }  

32. private Thread addThread(Runnable firstTask) {  

33.         Worker w = new Worker(firstTask);  

34.         Thread t = threadFactory.newThread(w); //建立一個線程

35.         if (t != null) {  

36.             w.thread = t;  

37.             workers.add(w);  

38.             int nt = ++poolSize;  

39.             if (nt > largestPoolSize)  

40.                 largestPoolSize = nt;  

41.         }  

42.         return t;  

43.     }

45. (3) 容器的背景處理線程:ContainerBackgroundProcessor是ContainerBase的一個内部類 

46.         啟動過程:StandardService.startInternal>>

47.                                     ContainerBase.startInternal >> 

48.                                                    ContainerBase.threadStart()  

49.    ContainerBase.startInternal(){  

50.         for(int i=0 ;i < children.length;i++){  

51.           startStopExecutor.submit(new StartChild(children[i]); //起新線程啟動子容器  

52.         }  

53.         threadStart(); // 在啟動容器的最後調用threadStart方法  

54.     }   

55. protected void threadStart() {  

56.         if (thread != null)  

57.             return;  

58.         // backgroundProcessorDelay預設為-1,即不産生背景線程,但StandardEngine的構造  

59.         // 函數中設定backgroundProcessorDelay = 10,即預設隻有Engine産生背景線程,  

60.         // 其它子容器共享Engine的背景線程

61.         if (backgroundProcessorDelay <= 0)   

62.             return;  

63.         threadDone = false;  

64.         String threadName = "ContainerBackgroundProcessor[" + toString() + "]";  

65.         thread = new Thread(new ContainerBackgroundProcessor(), threadName);  

66.         thread.setDaemon(true);  

67.         thread.start(); // 啟動容器背景線程

68. }

70. (4) 請求接收線程:即運作Acceptor的線程,啟動Connector的時候産生  

71.         啟動過程:StandardService.startInternal>>

72.                             Connector.startInternal() >>

73.                                            protocolHandler.start() >>

74.                                                      AbstractProtocol.start() >>

75.                                                                        endpoint.start() >>

76.                                                                                  AbstractEndpoint.Start() >>

77.                                                                                               JioEndpoint.startInternal() >>

78.                                                                                                              JIoEndpoint.startAcceptorThreads()  

79.     startAcceptorThreads内容如下:  

80.     protected final void startAcceptorThreads() {  

81.         int count = getAcceptorThreadCount(); // 預設隻有一個接收線程Acceptor

82.         acceptors = new Acceptor[count];  

83.         for (int i = 0; i < count; i++) {  

84.             acceptors[i] = createAcceptor(); // org.apache.tomcat.util.net. JIoEndpoint.Acceptor

85.             Thread t = new Thread(acceptors[i], getName() + "-Acceptor-" + i);  //建立請求接收線程

86.             t.setPriority(getAcceptorThreadPriority());  

87.             t.setDaemon(getDaemon());  

88.             t.start(); // 啟動請求接收線程來接收請求

89.         }  

90.     }  

91. 這樣在Acceptor的run()中就可以接收請求了。

92. 

(5) 請求處理線程:即運作SocketProcessor的線程,從請求處理線程池中産生,org.apache.tomcat.util.net. AbstractEndpoint的成員變量private Executor executor用來處理請求,org.apache.tomcat.util.net.JioEndpoint(繼承了AbstractEndpoint)在方法startInternal()中調用createExecutor()來建立executor為線程池對象ThreadPoolExecutor  

93.         請求處理線程池建立過程:StandardService.startInternal>>

94.                                            Connector.startInternal >>

95.                                                          protocolHandler.start >>

96.                                                                   AbstractProtocol.start >>

97.                                                                                 endpoint.start >>             

98.                                                                                       AbstractEndpoint.Start >>

99.                                                                                               JIoEndpoint.startInternal >>  

100.                                                                                                            JIoEndpoint.createExecutor>> 

101.                                                                                                                           new ThreadPoolExecutor()  

102.      public void createExecutor() {  

103.          internalExecutor = true;  

104.          TaskQueue taskqueue = new TaskQueue();  

105.          // 線程池線程産生的的工廠為java.util.concurrent.ThreadFactory.TaskThreadFactory

106.          TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());  

107.          executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);  

108.          taskqueue.setParent( (ThreadPoolExecutor) executor);  

109.      }  

110.          啟動過程:Acceptor.run()>> // 請求接收線程接收到請求後

111.                             processSocket(socket)>>  

112.                                   getExecutor().execute(new SocketProcessor(wrapper))>> // 交給請求處理線程處理

113.                                                                   ThreadPoolExecutor.execute (command,0,TimeUnit.MILLISECONDS)>>  

114.                                                                                                                 super.execute(command) >>  

115.                                                                                                                            addIfUnderCorePoolSize(command)  

116.  addIfUnderCorePoolSize内容如下:  

117.  private boolean addIfUnderCorePoolSize(Runnable firstTask) {  

118.          Thread t = null;  

119.          final ReentrantLock mainLock = this.mainLock;  

120.          mainLock.lock();  

121.          try {  

122.              if (poolSize < corePoolSize && runState == RUNNING)  

123.                  t = addThread(firstTask);  

124.          } finally {  

125.              mainLock.unlock();  

126.          }  

127.          if (t == null)  

128.              return false;  

129.          t.start();  // SocketProcessor.run運作

130.          return true;  

131.  }  

132.  private Thread addThread(Runnable firstTask) {  

133.          Worker w = new Worker(firstTask);  

134.          Thread t = threadFactory.newThread(w); //建立一個線程

135.          if (t != null) {  

136.              w.thread = t;  

137.              workers.add(w);  

138.              int nt = ++poolSize;  

139.              if (nt > largestPoolSize)  

140.                  largestPoolSize = nt;  

141.          }  

142.          return t;  

143.  }  

144.  至此SocketProcessor.run運作起來了,這個線程負責處理請求。 

 Tomcat運作過程中的線程概況具體序列圖入下:

Tomcat7源碼分析

       大家都知道Java中的IO模型分阻塞式的IO模型(BIO)及非阻塞式的IO模型(NIO),Tomcat中的對請求接收網絡IO子產品中也同樣實作了基于上述兩種IO的線程模型,具體的實作如下,

 Tomcat基于阻塞式IO(BIO)的線程模型序列圖如下:

Tomcat7源碼分析

Tomcat基于非阻塞式IO(NIO)的線程模型序列圖如下:

Tomcat7源碼分析

6. Tomcat的類加載機制

   主流的JavaWeb伺服器如(Tomcat,Jetty,WebLogic)都實作了自己定義的類加載器(一般不止一個),因為Web伺服器一般要解決如下幾個問題:

          1、部署在同一個伺服器上的兩個Web應用程式所使用的Java類庫可以實作互相隔離。

          2、部署在同一個伺服器上的兩個應用程式所使用的Java類庫可以互相共享。

          3、伺服器需要盡可能地保證自身的安全不受部署的Web應用程式的影響。

         為了解決這幾個問題,Tomcat實作了如下類加載器的層級結構:

Tomcat7源碼分析

Tomcat7運作時類的加載說明:

1)Bootstrap Class Loader是JVM的核心由C實作的,加載了JVM的核心包rt.jar。rt.jar中的所有類執行其class的getClassLoader()方法都将傳回null,例如Integer.class.getClassLoader()。

2)Extension Class Loader主要加載了JVM擴充包中相關的jar包。例如運作下列代碼将System.out.println(ZipPath.class.getClassLoader());将得到如下的運作結果:sun.misc.Launcher$ExtClassLoader。

3)System Class Loader加載CLASSPATH相關的類,例如在Tomcat的Bootstrap的main方法中執行System.out.println(Bootstrap.class.getClassLoader());則将得到:sun.misc.Launcher$AppClassLoader。

4)Common Class Loader,Tomcat7中的CATALINA_HOME/lib下的jar包。注意Tomcat在啟動檔案中将啟動時配置了-classpath "%CATALINA_HOME%\lib\catalina.jar"是以catalina.jar中的類雖然指定使用類加載器Common Class Loader,但是按JVM的委托加載原則System.out.println(Bootstrap.class.getClassLoader());得到的類加載器是:sun.misc.Launcher$AppClassLoader。

5)Webapp Class Loader, 主要負責加載Context容器中的所有的類。實際上該加載器提供了參數delegateLoad供使用者設定是否使用parent-first加載。預設該值為false,預設用parent-last加載。出于安全性的考慮對于核心類WebappClassLoader是不允許加載的。包括:java.,javax.servlet.jsp.jstl,javax.servlet.,javax.el。

Tomcat類加載器的相關類圖 

Tomcat7源碼分析

Tomcat類加載器的相關源代碼分析

(一) ClassLoader的load方法

Java代碼  

1. protected Class<?> loadClass(String name, boolean resolve)  

2.         throws ClassNotFoundException  

3.     {  

4.         synchronized (getClassLoadingLock(name)) {  

5.             // First, check if the class has already been loaded  

6.             Class c = findLoadedClass(name);

7.             if (c == null) {  

8.                 try {  

9.                     if (parent != null) {  

10.                        c = parent.loadClass(name, false);  

11.                    } else {  

12.                        c = findBootstrapClassOrNull(name);  

13.                    }  

14.                }   

15.                if (c == null) {  

16.                    // If still not found, then invoke findClass in order  

17.                    // to find the class.  

18.                    long t1 = System.nanoTime();  

19.                    c = findClass(name);  

20.                }  

21.            }  

22.            if (resolve) {  

23.                resolveClass(c);  

24.            }  

25.            return c;  

26.        }  

27.    }  

1)在該方法中,首先檢查是否已經加載了該類,這裡有個問題JVM如何判斷一個類是否被加載過的?這裡涉及到了類的命名空間問題。在JAVA中判斷一個類是否相同不僅看類名是否相同,還要看其類加載器是否相同。同一個類可以被不同的類加載器所加載,并且認為是不同的。該問題可以分解下面兩個方面看。

  a) 單一加載原則:在加載器鍊中,一個類隻會被鍊中的某一個加載器加載一次。而不會被重複加載。實作類的共享,Tomcat多個應用,如果需要共享一些jar包,那麼隻需要交給                   commonClassLoader加載,那麼所有的應用就可以共享這些類。

  b) 可見性原則:父加載器加載的類,子加載器是可以通路的。而自加載器所加載的類,父加載器無法通路。不同加載器鍊之間其是互相不可見,無法通路的。實作隔離,Tomcat就是應         用該特性,為每一個Context容器建立一個WebappClassLoader類加載器對象,進而實作了應用間的互相隔離。應用間的類是不可見的是以無法互相通路。

2) 如果步驟一中無緩存,檢視該類父加載器,如果存在那麼委托給付加載器。如果沒有父加載器那麼認為BootstrapClassLoader是其父加載器,委托進行加載。

3)如果父加載器無法加載則抛出ClassNotFoundException,調用抽象方法findClass方法。

4)此處的resolveClass方法指的是上文類加載過程中連接配接的第三步操作。resolve該類的形式引用等等。

(二)類URLClassLoader的findClass方法

Java代碼  

1. protected Class<?> findClass(final String name)  

2.          throws ClassNotFoundException  

3.     {  

4.         try {  

5.             return AccessController.doPrivileged(  

6.                 new PrivilegedExceptionAction<Class>() {  

7.                     public Class run() throws ClassNotFoundException {  

8.                         String path = name.replace('.', '/').concat(".class");  

9.                         Resource res = ucp.getResource(path, false);  

10.                        if (res != null) {  

11.                            try {  

12.                                return defineClass(name, res);  

13.                            } catch (IOException e) {  

14.                                throw new ClassNotFoundException(name, e);  

15.                            }  

16.                        } else {  

17.                            throw new ClassNotFoundException(name);  

18.                        }  

19.                    }  

20.                }, acc);  

21.        } catch (java.security.PrivilegedActionException pae) {  

22.            throw (ClassNotFoundException) pae.getException();  

23.        }  

24.    }  

  該方法的核心是加載擷取Java類位元組碼檔案的位元組流,然後調用父類的defineClass方法完成類的構造過程。defineClass是由JVM實作的,不允許被覆寫,是以使用者類檔案就必須遵循     JVM的檔案規範才能被正确的解析。

(三)WebappClassLoader重寫了ClassLoader的loadClass方法

Java代碼  

1. @Override  

2. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {  

3.         Class<?> clazz = null;  

5.         // (0) 目前對象緩存中檢查是否已經加載該類  

6.         clazz = findLoadedClass0(name);  

8.         // (0.1) 檢查JVM的緩存,是否已經加載過該類  

9.         clazz = findLoadedClass(name);  

11.        // (0.2) 防止加載一些系統相關的類  

12.        try {  

13.            clazz = system.loadClass(name);  

14.            if (clazz != null) {  

15.                if (resolve)  

16.                    resolveClass(clazz);  

17.                return (clazz);  

18.            }  

19.        } catch (ClassNotFoundException e) {}  

21.        boolean delegateLoad = delegate || filter(name);  

23.        // (1) 如果配置了parent-first模式,那麼委托給父加載器  

24.        if (delegateLoad) {  

25.            ClassLoader loader = parent;  

26.            if (loader == null) loader = system;  

27.            try {  

28.                clazz = Class.forName(name, false, loader);  

29.                if (clazz != null) {  

30.                    if (resolve) resolveClass(clazz);  

31.                    return (clazz);  

32.                }  

33.            } catch (ClassNotFoundException e) {}  

34.        }  

36.        // (2) 從WebApp中去加載類,主要是WebApp下的/classes目錄與/lib目錄  

37.        try {  

38.            clazz = findClass(name);  

39.            if (clazz != null) {  

40.                if (resolve)  

41.                    resolveClass(clazz);  

42.                return (clazz);  

43.            }  

44.        } catch (ClassNotFoundException e) {}  

46.        // (3) 如果在目前WebApp中無法加載到,委托給StandardClassLoader從$catalina_home/lib中去加載

47.        if (!delegateLoad) {  

48.            ClassLoader loader = parent;  

49.            if (loader == null)  

50.                loader = system;  

51.            try {  

52.                clazz = Class.forName(name, false, loader);  

53.                if (clazz != null) {  

54.                    if (resolve)  

55.                        resolveClass(clazz);  

56.                    return (clazz);  

57.                }  

58.            } catch (ClassNotFoundException e) {}  

59.        }  

61.        throw new ClassNotFoundException(name);  

62.    }  

(四)WebappClassLoader的findClass()方法>>findClassInternal()方法>>findResourceInternal()方法

Java代碼 

1.      //從WebappClassLoader的classpath中加載類或資源檔案 

2.      protected ResourceEntry findResourceInternal(String name, String path) {  

3.          if (!started) {  

4.              log.info(sm.getString("webappClassLoader.stopped", name));  

5.              return null;  

6.          }  

7.          ResourceEntry entry = resourceEntries.get(name);  

8.          if (entry != null)  

9.              return entry;  

11.         int contentLength = -1;  

12.         InputStream binaryStream = null;  

14.         int jarFilesLength = jarFiles.length;  

15.         int repositoriesLength = repositories.length;  

17.         int i;  

18.         Resource resource = null;  

19.         boolean fileNeedConvert = false;  

21.        //首先從/WEB-INF/classes路徑中加載類

22.         for (i = 0; (entry == null) && (i < repositoriesLength); i++) {  

23.             try {  

24.                 String fullPath = repositories[i] + path;  

25.                 //擷取資源的絕對路徑 

26.                 Object lookupResult = resources.lookup(fullPath);  

27.                 if (lookupResult instanceof Resource) {  

28.                     resource = (Resource) lookupResult;  

29.                 }  

31.                 ResourceAttributes attributes =  

32.                     (ResourceAttributes) resources.getAttributes(fullPath);  

33.                 contentLength = (int) attributes.getContentLength();  

34.                 String canonicalPath = attributes.getCanonicalPath();  

35.                 if (canonicalPath != null) {  

36.                     entry = findResourceInternal(new File(canonicalPath), "");  

37.                 } else {  

38.                     //擷取類或檔案的ResourceEntry  

39.                     entry = findResourceInternal(files[i], path);  

40.                 }  

41.                 entry.lastModified = attributes.getLastModified();  

43.                 if (resource != null) {  

44.                     try {  

45.                         //得到類或資源的輸入流InputStream  

46.                         binaryStream = resource.streamContent();  

47.                     } catch (IOException e) {  

48.                         return null;  

49.                     }  

50.                     if (needConvert) {  

51.                         if (path.endsWith(".properties")) {  

52.                             fileNeedConvert = true;  

53.                         }  

54.                     }  

55.                 }  

56.             } catch (NamingException e) {  

57.             }  

58.         }  

60.        //然後再從/WEB-INF/lib路徑中加載類

61.         if ((entry == null) && (notFoundResources.containsKey(name)))  

62.             return null;  

63.         JarEntry jarEntry = null;  

64.         synchronized (jarFiles) {  

65.             try {  

66.                 for (i = 0; (entry == null) && (i < jarFilesLength); i++) {  

67.                     //擷取JarFile下的JarEntry 

68.                     jarEntry = jarFiles[i].getJarEntry(path);  

70.                     if (jarEntry != null) {  

71.                         entry = new ResourceEntry();  

72.                         try {  

73.                             //設定類或檔案的URL  

74.                             entry.codeBase = getURL(jarRealFiles[i], false);  

75.                             String jarFakeUrl = getURI(jarRealFiles[i]).toString();  

76.                             jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;  

77.                             //設定URL  

78.                             entry.source = new URL(jarFakeUrl);  

79.                             entry.lastModified = jarRealFiles[i].lastModified();  

80.                         } catch (MalformedURLException e) {  

81.                             return null;  

82.                         }  

83.                         contentLength = (int) jarEntry.getSize();  

84.                         try {  

85.                             entry.manifest = jarFiles[i].getManifest();  

86.                             //從JarFile中根據JarEntry擷取jar包中類的輸入流InputStream  

87.                             binaryStream = jarFiles[i].getInputStream(jarEntry);  

88.                         } catch (IOException e) {  

89.                             return null;  

90.                         }  

91.                     }  

92.                 }  

94.                 if (binaryStream != null) {  

95.                     byte[] binaryContent = new byte[contentLength];  

96.                     int pos = 0;  

97.                     try {  

98.                         while (true) {  

99.                             //從輸入流InputStream中讀取類或檔案的二進制流  

100.                              int n = binaryStream.read(binaryContent, pos, binaryContent.length - pos); 

101.                              if (n <= 0)  

102.                                  break;  

103.                              pos += n;  

104.                          }  

105.                      } catch (IOException e) {  

106.                          return null;  

107.                      }  

108.                      //設定二進制設定到ResourceEntry 

109.                      entry.binaryContent = binaryContent;  

110.                  }  

111.              } 

112.          }  

113.          synchronized (resourceEntries) {

114.              ResourceEntry entry2 = resourceEntries.get(name);

115.              if (entry2 == null) {  

116.                  //向本地資源緩存這注冊ResourceEntry

117.                  resourceEntries.put(name, entry);  

118.              } else {  

119.                  entry = entry2;  

120.              }  

121.          }  

122.          return entry;  

123.      }  

(五)Web應用中經常使用到的線程上下文類加載器的在Tomcat中的設定實作

 在Web應用中我們經常用到線程上下文類加載器,拿到的肯定是目前WebApp的WebAppClassLoader, 如下

Java代碼  

1.  ClassLoader classLoader= Thread.currentThread().getContextClassLoader();

我們用到的線程上下文類加載器其實是在StandardHostValve的invoke()方法中被設定的

Java代碼 

1.  public final void invoke(Request request, Response response)  

2.      throws IOException, ServletException {  

4.      //得到此次請求所對應的StandardContext容器  

5.      Context context = request.getContext();  

7.      if( context.getLoader() != null ) {  

8.              //線程上下文類加載器切換成目前WebApp的類加載器,從Context的loader中擷取  

9.              Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());  

10.     }  

12.     if (asyncAtStart || context.fireRequestInitEvent(request)) {  

13.         try {  

14.             //調用StandardContext容器中管道Pipeline中的第一個Valve,直到調用Servlet  

15.             context.getPipeline().getFirst().invoke(request, response);  

16.         } catch (Throwable t) {  

17.             ExceptionUtils.handleThrowable(t);  

18.             request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);  

19.             throwable(request, response, t);  

20.         }  

21.     }  

22.     if (!Globals.IS_SECURITY_ENABLED) {  

23.         //還原StandardClassLoader類加載器為線程上下文類加載器 

24.         Thread.currentThread().setContextClassLoader(StandardHostValve.class.getClassLoader();  

25.     }  

26. }  

7、Tomcat所涉及的設計模式

         Tomcat雖然代碼比較龐大,但是整體還是設計的比較優雅,特别是很多元件化的設計思路,其中涉及到的一些常用的設計模式值得我們學習及借鑒:

責任鍊模式

        Tomcat中有兩個地方比較明顯的使用了責任鍊模式,一、Tomcat中的ApplicationFilterChain實作了Filter攔截和實際Servlet的請求,是典型的責任鍊模式。其他開源架構中類似的設計還有Struts2中的DefaultActionInvocation實作Interceptor攔截和Action的調用。Spring AOP中ReflectiveMethodInvocation實作MethodInceptor方法攔截和target的調用。二、Tomcat中的Pipeline-Valve模式也是責任鍊模式的一種變種,從Engine到Host再到Context一直到Wrapper都是通過一個鍊來傳遞請求。

觀察者模式

        Tomcat通過LifecycleListener對元件生命周期元件Lifecycle進行監聽就是典型的觀察者模式,各個元件在其生命期中會有各種各樣行為,而這些行為都會觸發相應的事件,Tomcat就是通過偵聽這些事件達到對這些行為進行擴充的目的。在看元件的init和start過程中會看到大量如:lifecycle.fireLifecycleEvent(AFTER_START_EVENT,null);這樣的代碼,這就是對某一類型事件的觸發,如果你想在其中加入自己的行為,就隻用注冊相應類型的事件即可。

門面模式

      門面設計模式在 Tomcat 中有多處使用,在 Request 和 Response 對象封裝中(RequestFacade,ResponseFacade)、ApplicationContext 到ApplicationContextFacade等都用到了這種設計模式。這種設計模式主要用在一個大的系統中有多個子系統組成時,這多個子系統肯定要涉及到互相通信,但是每個子系統又不能将自己的内部資料過多的暴露給其它系統,不然就沒有必要劃分子系統了。每個子系統都會設計一個門面,把别的系統感興趣的資料封裝起來,通過這個門面來進行通路。

模闆方法模式

  模闆方法模式是我們平時開發當中常用的一種模式,把通用的骨架抽象到父類中,子類去實作特地的某些步驟。Tomcat及Servlet規範API中也大量的使用了這種模式,比如Tomcat中的ContainerBase中對于生命周期的一些方法init,start,stop和Servlet規範API中的GenericServlet中的service抽象骨架模闆方法均使用了模闆方法模式。

參考資源

Tomcat官網:http://tomcat.apache.org/

<How Tomcat Wroks>:Tomcat5的架構分析

Tomcat源碼分析:http://zddava.iteye.com/blog/305944

Tomat的源碼環境搭建:http://mabusyao.iteye.com/blog/1198557

<Servlet2.5規範>:JSR制定的規Servlet範