1. 程式人生 > >Tomcat 啟動時類載入順序

Tomcat 啟動時類載入順序

Tomcat啟動時classloader載入順序
  Tomcat的class載入的優先順序一覽  
  1.最先是$JAVA_HOME/jre/lib/ext/下的jar檔案。  
  2.環境變數CLASSPATH中的jar和class檔案。  
  3.$CATALINA_HOME/common/classes下的class檔案。  
  4.$CATALINA_HOME/commons/endorsed下的jar檔案。  
  5.$CATALINA_HOME/commons/i18n下的jar檔案。  
  6.$CATALINA_HOME/common/lib   下的jar檔案。  
  (JDBC驅動之類的jar檔案可以放在這裡,這樣就可以避免在server.xml配置好資料來源卻出現找不到JDBC   Driver的情況。)  
  7.$CATALINA_HOME/server/classes下的class檔案。  
  8.$CATALINA_HOME/server/lib/下的jar檔案。  
  9.$CATALINA_BASE/shared/classes   下的class檔案。  
  10.$CATALINA_BASE/shared/lib下的jar檔案。  
  11.各自具體的webapp   /WEB-INF/classes下的class檔案。  
  12.各自具體的webapp   /WEB-INF/lib下的jar檔案。  
  class的搜尋順序如下:  
  -------------  
  Bootstrap   classes   of   your   JVM    
  System   class   loader   classses   (described   above)    
  /WEB-INF/classes   of   your   web   application    
  /WEB-INF/lib/*.jar   of   your   web   application    
  $CATALINA_HOME/common/classes    
  $CATALINA_HOME/common/endorsed/*.jar    
  $CATALINA_HOME/common/i18n/*.jar    
  $CATALINA_HOME/common/lib/*.jar    
  $CATALINA_BASE/shared/classes    
  $CATALINA_BASE/shared/lib/*.jar    
  --------------  
  因此放在不同webapp裡的class檔案,會被classloader載入成不同的例項。  
  例如假設下面兩個不同內容的class。分別放在不同的webapp的class目錄下。  
  package   com.lizongbo;  
  public   class   TestClass   {  
      private   String   NAME="lizongbo";  
  }  
  package   com.lizongbo;  
  public   class   TestClass   {  
      private   String   NAME="li_zongbo";  
  }  
  在不同的webapp得到的com.lizongbo.NAME結果是不同的,且互不影響。  
  但是注意,以下包名開頭的class例外:  
  javax.*    
  org.xml.sax.*    
  org.w3c.dom.*    
  org.apache.xerces.*    
  org.apache.xalan.*    
  ps,注意.在各個jar中的/META-INF/MAINFEST.MF檔案裡Class-Path鍵值對,也會提供jar的載入優先順序。  
  例如某jar的MAINFEST.MF內容如下:  
  Manifest-Version:   1.0  
  Created-By:   lizongbo  
  Class-Path:   commons-beanutils.jar  
  Class-Path:   commons-collections.jar  
  Class-Path:   commons-dbcp.jar  
  Class-Path:   commons-digester.jar  
  Class-Path:   commons-logging.jar  
  Class-Path:   commons-pool.jar  
  Class-Path:   commons-services.jar  
  Class-Path:   commons-validator.jar  
  Class-Path:   jakarta-oro.jar  
  Main-Class:   com.lizongbo.MyTestClass  
  那麼在載入這個jar的時候,會先在此jar所在目錄下依次先載入commons-beanutils.jar,commons-collections.jar。。。等jar檔案。  
  在不同的地方放置jar和class可能會產生意想不到的後果,,尤其是不同版本的jar檔案,因此在實際應用部署web應用時候要特別留心.  
  例如   使用javamail常見的一個出錯資訊:  
  javax.mail.NoSuchProviderException:   No   provider   for   smtp  
  其真實原因就很可能如下:  
  在不同的載入jar的目錄下放置了不同版本的mail.jar,比如一個是javamail1.3.1的mail.jar  
  在D:/jakarta-tomcat-5.5.8/common/lib下,而另外一個是javamail1.3.2的mail.jar在  
  D:/jakarta-tomcat-5.5.8/webapps/lizongbo/WEB-INF/lib下,  
  那麼lizongbo這個webapp中使用到javamail進行郵件傳送的時候,便會出現No   provider   for   smtp的錯誤。  
*******************************************************************
ClassLoader in Tomcat

************************************
1 - Tomcat 的類載入器的結構


Tomcat Server 在啟動的時候將構造一個ClassLoader樹,以保證模組的類庫是私有的
Tomcat Server的ClassLoader結構如下:


        +-----------------------------+ 

        |         Bootstrap           | 

        |             |               | 

        |          System             | 

        |             |               | 

        |          Common             | 

        |         /      /            | 

        |     Catalina  Shared        | 

        |               /    /        | 

         |          WebApp1  WebApp2   | 

        +-----------------------------+


其中:
- Bootstrap - 載入JVM自帶的類和$JAVA_HOME/jre/lib/ext/*.jar
- System - 載入$CLASSPATH/*.class
- Common - 載入$CATALINA_HOME/common/...,它們對TOMCAT和所有的WEB APP都可見
- Catalina - 載入$CATALINA_HOME/server/...,它們僅對TOMCAT可見,對所有的WEB APP都不可見
- Shared - 載入$CATALINA_HOME/shared/...,它們僅對所有WEB APP可見,對TOMCAT不可見(也不必見)
- WebApp - 載入ContextBase?/WEB-INF/...,它們僅對該WEB APP可見



2 - ClassLoader 的工作原理


每個執行中的執行緒都有一個成員contextClassLoader,用來在執行時動態地載入其它類
系統預設的contextClassLoader是systemClassLoader,所以一般而言java程式在執行時可以使用JVM自帶的類、$JAVA_HOME/jre/lib/ext/中的類和$CLASSPATH/中的類
可以使用 Thread.currentThread().setContextClassLoader(...); 更改當前執行緒的contextClassLoader,來改變其載入類的行為


ClassLoader 被組織成樹形,一般的工作原理是:
1) 執行緒需要用到某個類,於是contextClassLoader被請求來載入該類
2) contextClassLoader請求它的父ClassLoader來完成該載入請求
3) 如果父ClassLoader無法載入類,則contextClassLoader試圖自己來載入


注意 :WebApp?ClassLoader的工作原理和上述有少許不同:
它先試圖自己載入類(在ContextBase?/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成


由此可得:
- 對於WEB APP執行緒,它的contextClassLoader是WebApp?ClassLoader
- 對於Tomcat Server執行緒,它的contextClassLoader是CatalinaClassLoader



3 類的查詢

ClassLoader類中loadClass方法為預設實現,用下面的順序查詢類:

1、呼叫findLoadedClass方法來檢查是否已經被載入。如果沒有則繼續下面的步驟。

2、如果當前類裝載器有一個指定的委託父裝載器,則用委託父裝載器的loadClass方法載入類,也就是委託給父裝載器載入相應的類。

3、如果這個類裝載器的委託層級體系沒有一個類裝載器載入該類,則使用類裝載器定位類的特定實現機制,呼叫findClass方法來查詢類。



4 - 部分原始碼分析


4.1 - org/apache/catalina/startup/Bootstrap.java

Bootstrap中定義了三個classloader:commonLoader,catalinaLoader,sharedLoader.三者關係如下:


// 注意三個自己定置的ClassLoader的層次關係: 

            // systemClassLoader (root) 

              //   +--- commonLoader 

            //          +--- catalinaLoader 

            //          +--- sharedLoader


Tomcat Server 執行緒的起點
構造ClassLoader樹,通過Thread.currentThread().setContextClassLoader(catalinaLoader)設定當前的classloader為catalinaLoader。
載入若干類,然後轉入org.apache.catalina.startup.Catalina類中



4.2 org.apache.catalina.loader.StandardClassLoader.java

通過看loadClass這個方法來看tomcat是如何載入類的,順序如下:

(0) Check our previously loaded class cache查詢已經裝載的class
        clazz = findLoadedClass(name);

(1) If a system class, use system class loader通過系統classloader來裝載class

        ClassLoader loader = system;
            clazz = loader.loadClass(name);

(2) Delegate to our parent if requested如果有代理則使用父類classloader

            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            clazz = loader.loadClass(name);

(3) Search local repositories 查詢本地類池,比如$CATALINA_HOME/server

           clazz = findClass(name);

(4) Delegate to parent unconditionally 預設使用代理裝載器



[ 檢視程式碼]


4.3 - org/apache/catalina/startup/ClassLoaderFactory.java


根據設定建立並返回StandardClassLoader的例項


[ 檢視程式碼]


4.4 - org/apache/catalina/loader/StandardClassLoader.java


類載入器


4.5 - org/apache/catalina/startup/SecurityClassLoad.java


該類僅包含一個靜態方法,用來為catalinaLoader載入一些類


[ 檢視程式碼]


Appendix - 參考


[1] http://jakarta.apache.org/tomcat/ 中的Tomcat 4.1.x文件Class Loader HOW-TO


在一個 JVM 中可能存在多個 ClassLoader ,每個 ClassLoader 擁有自己的 NameSpace 。一個 ClassLoader 只能擁有一個 class 物件型別的例項,但是不同的 ClassLoader 可能擁有相同的 class 物件例項,這時可能產生致命的問題。如 ClassLoaderA ,裝載了類 A 的型別例項 A1 ,而 ClassLoaderB ,也裝載了類 A 的物件例項 A2 。邏輯上講 A1=A2 ,但是由於 A1 和 A2 來自於不同的 ClassLoader ,它們實際上是完全不同的,如果 A 中定義了一個靜態變數 c ,則 c 在不同的 ClassLoader 中的值是不同的。