java ClassLoader作用

原文地址:http://blog.chinaunix.net/uid-21227800-id-65885.html


ClassLoader主要對類的請求提供服務,當JVM需要某類時,它根據名稱向ClassLoader要求這個類,然後由ClassLoader返回這個類的class對象。 1.1 幾個相關概念ClassLoader負責載入系統的所有Resources(Class,文件,來自網絡的字節流等),通過ClassLoader從而將資源載入JVM  

每個class都有一個reference,指向自己的ClassLoader。Class.getClassLoader()  
array的ClassLoader就是其元素的ClassLoader,若是基本數據類型,則這個array沒有ClassLoader  
1.2 主要方法和工作過程Java1.1及從前版本中,ClassLoader主要方法:  
Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口點  
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組並把它轉換成 Class 對象。原始數組包含如從文件系統或網絡裝入的數據。  
findSystemClass 方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用 defineClass 將原始字節轉換成 Class 對象,以將該文件轉換成類。當運行 Java 應用程序時,這是 JVM 正常裝入類的缺省機制。  
resolveClass可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調用 resolveClass,這取決於 loadClass 的 resolve 參數的值  
findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先調用該方法  
一般load方法過程如下:  

調用 findLoadedClass 來查看是否存在已裝入的類。  
如果沒有,那麼採用某種特殊的神奇方式來獲取原始字節。(通過IO從文件系統,來自網絡的字節流等)  
如果已有原始字節,調用 defineClass 將它們轉換成 Class 對象。  
如果沒有原始字節,然後調用 findSystemClass 查看是否從本地文件系統獲取類。  
如果 resolve 參數是 true,那麼調用 resolveClass 解析 Class 對象。  
如果還沒有類,返回 ClassNotFoundException。  
否則,將類返回給調用程序。  
1.3 委託模型自從JDK1.2以後,ClassLoader做了改進,使用了委託模型,所有系統中的ClassLoader組成一棵樹,ClassLoader在載入類庫時先讓Parent尋找,Parent找不到才自己找。  
JVM在運行時會產生三個ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++編寫的,在Java中看不到它,是null。它用來加載核心類庫,就是在lib下的類庫,Extension ClassLoader加載lib/ext下的類庫,App ClassLoader加載Classpath裏的類庫,三者的關係爲:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent爲Bootstrap ClassLoader。加載一個類時,首先BootStrap進行尋找,找不到再由Extension ClassLoader尋找,最後纔是App ClassLoader。  

將ClassLoader設計成委託模型的一個重要原因是出於安全考慮,比如在Applet中,如果編寫了一個java.lang.String類並具有破壞性。假如不採用這種委託機制,就會將這個具有破壞性的String加載到了用戶機器上,導致破壞用戶安全。但採用這種委託機制則不會出現這種情況。因爲要加載java.lang.String類時,系統最終會由Bootstrap進行加載,這個具有破壞性的String永遠沒有機會加載。  

委託模型還帶來了一些問題,在某些情況下會產生混淆,如下是Tomcat的ClassLoader結構圖:  

                Bootstrap 
                  | 
                System 
                  | 
                Common 
                /     
            Catalina  Shared 
                      /     
                   Webapp1  Webapp2 ... 

由 Common 類裝入器裝入的類決不能(根據名稱)直接訪問由 Web 應用程序裝入的類。使這些類聯繫在一起的唯一方法是通過使用這兩個類集都可見的接口。在這個例子中,就是包含由 Java servlet 實現的 javax.servlet.Servlet。  
如果在lib或者lib/ext等類庫有與應用中同樣的類,那麼應用中的類將無法被載入。通常在jdk新版本出現有類庫移動時會出現問題,例如最初我們使用自己的xml解析器,而在jdk1.4中xml解析器變成標準類庫,load的優先級也高於我們自己的xml解析器,我們自己的xml解析器永遠無法找到,將可能導致我們的應用無法運行。  

相同的類,不同的ClassLoader,將導致ClassCastException異常  

1.4 線程中的ClassLoader每個運行中的線程都有一個成員contextClassLoader,用來在運行時動態地載入其它類,可以使用方法Thread.currentThread().setContextClassLoader(...);更改當前線程的contextClassLoader,來改變其載入類的行爲;也可以通過方法Thread.currentThread().getContextClassLoader()來獲得當前線程的ClassLoader。  
實際上,在Java應用中所有程序都運行在線程裏,如果在程序中沒有手工設置過ClassLoader,對於一般的java類如下兩種方法獲得的ClassLoader通常都是同一個  

this.getClass.getClassLoader();  
Thread.currentThread().getContextClassLoader();  
方法一得到的Classloader是靜態的,表明類的載入者是誰;方法二得到的Classloader是動態的,誰執行(某個線程),就是那個執行者的Classloader。對於單例模式的類,靜態類等,載入一次後,這個實例會被很多程序(線程)調用,對於這些類,載入的Classloader和執行線程的Classloader通常都不同。  

1.5 Web應用中的ClassLoader回到上面的例子,在Tomcat裏,WebApp的ClassLoader的工作原理有點不同,它先試圖自己載入類(在ContextPath/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成。  
由此可得:  

對於WEB APP線程,它的contextClassLoader是WebAppClassLoader  
對於Tomcat Server線程,它的contextClassLoader是CatalinaClassLoader  
1.6 獲得ClassLoader的幾種方法可以通過如下3種方法得到ClassLoader  
this.getClass.getClassLoader(); // 使用當前類的ClassLoader  
Thread.currentThread().getContextClassLoader(); // 使用當前線程的ClassLoader  
ClassLoader.getSystemClassLoader(); // 使用系統ClassLoader,即系統的入口點所使用的ClassLoader。(注意,system ClassLoader與根ClassLoader並不一樣。JVM下system ClassLoader通常爲App ClassLoader)  
1.7 幾種擴展應用用戶定製自己的ClassLoader可以實現以下的一些應用  
安全性。類進入JVM之前先經過ClassLoader,所以可以在這邊檢查是否有正確的數字簽名等  
加密。java字節碼很容易被反編譯,通過定製ClassLoader使得字節碼先加密防止別人下載後反編譯,這裏的ClassLoader相當於一個動態的解碼器  
歸檔。可能爲了節省網絡資源,對自己的代碼做一些特殊的歸檔,然後用定製的ClassLoader來解檔  
自展開程序。把java應用程序編譯成單個可執行類文件,這個文件包含壓縮的和加密的類文件數據,同時有一個固定的ClassLoader,當程序運行時它在內存中完全自行解開,無需先安裝  
動態生成。可以生成應用其他還未生成類的類,實時創建整個類並可在任何時刻引入JVM  
2.0 資源載入 
所有資源都通過ClassLoader載入到JVM裏,那麼在載入資源時當然可以使用ClassLoader,只是對於不同的資源還可以使用一些別的方式載入,例如對於類可以直接new,對於文件可以直接做IO等。 2.1 載入類的幾種方法假設有類A和類B,A在方法amethod裏需要實例化B,可能的方法有3種。對於載入類的情況,用戶需要知道B類的完整名字(包括包名,例如"com.rain.B")  
1. 使用Class靜態方法 Class.forName  

    Class cls = Class.forName("com.rain.B"); 
    B b = (B)cls.newInstance(); 

2. 使用ClassLoader  
    /* Step 1. Get ClassLoader */ 
    ClassLoader cl; // 如何獲得ClassLoader參考1.6 

    /* Step 2. Load the class */ 
    Class cls = cl.loadClass("com.rain.B"); // 使用第一步得到的ClassLoader來載入B 
     
    /* Step 3. new instance */ 
    B b = (B)cls.newInstance(); // 有B的類得到一個B的實例 

3. 直接new  
    B b = new B(); 

2.2 文件載入(例如配置文件等)假設在com.rain.A類裏想讀取文件夾 /com/rain/config 裏的文件sys.properties,讀取文件可以通過絕對路徑或相對路徑,絕對路徑很簡單,在Windows下以盤號開始,在Unix下以"/"開始  
對於相對路徑,其相對值是相對於ClassLoader的,因爲ClassLoader是一棵樹,所以這個相對路徑和ClassLoader樹上的任何一個ClassLoader相對比較後可以找到文件,那麼文件就可以找到,當然,讀取文件也使用委託模型  

1. 直接IO  

/** 
 * 假設當前位置是 "C:/test",通過執行如下命令來運行A "java com.rain.A" 
 * 1. 在程序裏可以使用絕對路徑,Windows下的絕對路徑以盤號開始,Unix下以"/"開始 
 * 2. 也可以使用相對路徑,相對路徑前面沒有"/" 
 * 因爲我們在 "C:/test" 目錄下執行程序,程序入口點是"C:/test",相對路徑就 
 * 是 "com/rain/config/sys.properties" 
 * (例子中,當前程序的ClassLoader是App ClassLoader,system ClassLoader = 當前的 
 * 程序的ClassLoader,入口點是"C:/test") 
 * 對於ClassLoader樹,如果文件在jdk lib下,如果文件在jdk lib/ext下,如果文件在環境變量裏, 
 * 都可以通過相對路徑"sys.properties"找到,lib下的文件最先被找到 
 */ 
File f = new File("C:/test/com/rain/config/sys.properties"); // 使用絕對路徑 
//File f = new File("com/rain/config/sys.properties"); // 使用相對路徑 
InputStream is = new FileInputStream(f); 

如果是配置文件,可以通過java.util.Properties.load(is)將內容讀到Properties裏,Properties默認認爲is的編碼是ISO-8859-1,如果配置文件是非英文的,可能出現亂碼問題。  
2. 使用ClassLoader  

/** 
 * 因爲有3種方法得到ClassLoader,對應有如下3種方法讀取文件 
 * 使用的路徑是相對於這個ClassLoader的那個點的相對路徑,此處只能使用相對路徑 
 */ 
InputStream is = null; 
is = this.getClass().getClassLoader().getResourceAsStream( 
       "com/rain/config/sys.properties"); //方法1 
//is = Thread.currentThread().getContextClassLoader().getResourceAsStream( 
       "com/rain/config/sys.properties"); //方法2 
//is = ClassLoader.getSystemResourceAsStream("com/rain/config/sys.properties"); //方法3 

如果是配置文件,可以通過java.util.Properties.load(is)將內容讀到Properties裏,這裏要注意編碼問題。  
3. 使用ResourceBundle  

    ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys"); 

這種用法通常用來載入用戶的配置文件,關於ResourceBunlde更詳細的用法請參考其他文檔  
總結:有如下3種途徑來載入文件  

    1. 絕對路徑 ---> IO 
    2. 相對路徑 ---> IO 
                ---> ClassLoader 
    3. 資源文件 ---> ResourceBundle 

2.3 如何在web應用裏載入資源在web應用裏當然也可以使用ClassLoader來載入資源,但更常用的情況是使用ServletContext,如下是web目錄結構  
    ContextRoot 
       |- JSP、HTML、Image等各種文件 
        |- [WEB-INF] 
              |- web.xml 
              |- [lib] Web用到的JAR文件 
                |- [classes] 類文件 

用戶程序通常在classes目錄下,如果想讀取classes目錄裏的文件,可以使用ClassLoader,如果想讀取其他的文件,一般使用ServletContext.getResource()  

如果使用ServletContext.getResource(path)方法,路徑必須以"/"開始,路徑被解釋成相對於ContextRoot的路徑,此處載入文件的方法和ClassLoader不同,舉例"/WEB-INF/web.xml","/download/WebExAgent.rar"


另外一篇關於classLoader的文章,主要介紹classloader加載類時的層次結構,

文章地址:http://hain.bokee.com/1921356.html

Java中的Classloader

作爲一種編程語言, 我總覺得Java有那麼一點奇異, 或者說煅? 它不象傳統的編譯型語言(比如C/C++)那麼純粹, 它不僅僅是一種"語言". 比如Java中有Classloader的概念, 而這通常是操作系統的一部分. 理解這一概念對於J2EE尤其重要. 下面的文章譯自IBM的一篇文檔, 很清楚地解釋了這個重要的概念.

Classloader是如何工作的?

Java虛擬機中的每個Java類都是由某個classloader載入的, classloader本身也是Java類, 所以這一點就特別有趣. 那麼, 這些classloader又是如何載入的呢? 這好像是一個悖論. 幸運的是, 事實並非如此. Java包含一個自舉classloader, 它是用本地代碼寫的, 是JVM的一部分. 這個自舉classloader的主要作用是載入Java核心類, 從而自舉整個Java環境.

在一個企業Java應用中, 使用到的許多類都不是Java核心類. 比如, 程序員也許會引用其應用中的另外一個類, 或者Java擴展中的一個類. Java擴展是擴展Java核心平臺功能的一些Java包. 爲了隔離這兩種不同的Java類, Java採用兩種不同的classloader: application和extension classloader. 它們都是用Java寫的. 這意味着這些類將被它們特定的classloader載入, 如下例所示.
public class WhichClassLoader {
WhichClassLoader( ) {}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));
System.out.println("\nBootstrap classpath= \n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath= "+ extensionClassPath + "\n");
System.out.println("System classpath= "+ systemClassPath + "\n" );

//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader whichClassLoader = new WhichClassLoader();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

這個例子相當簡單, 它查詢和顯示classloader的路徑, 然後創建三個新的對象示例, 類型各不相同: 一個Java核心類(java.lang.Object), 一個Java擴展類(javax.naming.InitialContext)和一個用戶類(WhichClassLoader). 最後它打印出載入這些類的classloader. 運行這個例子的結果是:
D:\Classpath_Project\src>java -classpath . WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath=
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f0272827
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f023282

這個結果中有幾點值得一提. 比如, 你也許想知道爲什麼用戶類是被sun.misc.Launcher$AppClassLoader@f023282所載入的, 而不是更通用的形式比如sun.misc.Launcher$AppClassLoader或者Application ClassLoader. 原因在於getClassLoader()方法的返回類型的是java.lang.ClassLoader. 當這個對象實例被送到一個輸出流上時, 打印的是實例的名字, 這一名字在JVM每次啓動時都會改變. 在我們這次特定的運行中, application classloader的實例名是sun.misc.Launcher$AppClassLoader@a1b1234. 對於extension classloader也是這樣. 有趣的是, 自舉classloader的實例名是空. 這是因爲自舉classloader不是用Java寫的, 所以沒有java.lang.ClassLoader的實例供返回.

另外一件引起你興趣的事情也許是我們如何通過系統類查詢路徑, 比如System.getProperties().getProperty("sun.boot.class.path"). 在這個例子中, 你也許會想我們可以通過動態地改變路徑, 從而強制用一個特定的classloader來載入一個特定的類. 我們可以這樣修改來驗證一下這個想法:
import javax.naming.InitialContext;
public class WhichClassLoader1 {
//Constructor
WhichClassLoader1( ) {
//do nothing
}

public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

//modifying the bootstrapclasspath to include the jar file which contains the InitialContext Class
// This should force the class to be loaded by the bootstrap classloader???????????
String fileSeparator = System.getProperty("file.separator");
String InitialContextJar = "D:"+fileSeparator+"jdk1.2.2"+fileSeparator + "jre"+fileSeparator+"lib"+fileSeparator+"ext"+fileSeparator+"iioprt.jar"
bootstrapClassPath.append(System.getProperty("path.separator")).append(InitialContextJar);
System.setProperty("sun.boot.class.path",bootstrapClassPath.toString());
System.out.println("\nBootstrap classpath=\n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );

//Create new object instances
Object object = new java.lang.Object()
InitialContext initialContext = new InitialContext();
WhichClassLoader1 whichClassLoader1 = new WhichClassLoader1();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, Javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader1, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

運行的結果很有趣:
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\iioprt.jar
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, Javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b290a
User file, WhichClassLoader1, was loaded by: sun.misc.Launcher$AppClassLoader@f01f290a

和我們期待的不同. 事實是改變這些系統級的屬性完全沒有效果. 這說明了這些classloader很重要的一個特性: 一旦JVM啓動後, 它們的路徑是不可更改的, 或者說是靜態的.

讓我們回到這個例子原來的目的. 到目前爲止, 看上去Java的classloader正如我們被告知的那樣工作: 自舉classloader載入Java核心類, extension classloader載入Java擴展包, 而application classloader載入用戶類. 然而, classloader在本質上又是分層的, 遵循"委託給父類"的模式. 這意味着除了自舉classloader, 每一個classloader都有一個父classloader, 當一個classloader試圖載入一個類時, 它首先將這一責任委託給它的父classloader. 這個模式是這樣工作的: 一個classloader將嘗試載入一個類, 當且僅當這個類在這個classloader所屬的層次結構中還未被載入, 並且這個classloader的父classloader找不到這個類.

自頂向下地遍歷classloader層次, 我們看到首先是自舉classloader, 然後是extension classloader和application classloader. 如果你將classloader層次看作稀疏樹結構, 那麼自舉classloader是根節點, application classloader是葉節點.

這種層次結構可以寫程序演示如下:
public class ShowHierarchy{
public static void main (String args[] ) throws Exception {
System.out.println("The System ClassLoader is: " + ClassLoader.getSystemClassLoader().getClass());
System.out.println("The System ClassLoader's Parent is: " + ClassLoader.getSystemClassLoader().getParent().getClass());
System.out.println("The System ClassLoader's Parent's Parent is: " +ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
結果是:
D:\WebSphere\Documents\Classpaths\src>java .classpath . ShowHierarchy
The System ClassLoader is: class sun.misc.Launcher$AppClassLoader
The System ClassLoader's Parent is: class sun.misc.Launcher$ExtClassLoader

爲了演示"委託給父類"的關係, 讓我們在每個classloader的路徑上放一份包含InitialContext.class的JAR文件, 然後執行第一個例子. 記住一旦JVM運行以後, 它的classloader路徑是不可變的, 所以我們需要通過Java開關-classpath和-bootclasspath在運行之前改變它們.
D:\Classpath_Project\src\java -classpath D:\jdk1.2.2\jre\lib\ext\jndi.jar;. -XbootclasspathD:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath= D:\jdk1.2.2\jre\lib\ext\jndi.jar;.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: null
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f01a2987

正如你所見, InitialContext類如我們預見的那樣是由自舉classloader載入的, 你能理解這是爲什麼嗎? 讓我們詳細描述一下發生的事吧:
1. WhichClassLoader類創建一個InitialContext類的新實例: javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
2. Application classloader查看InitialContext類是否已經在它所屬的classloader層次結構中被載入, 也就是說被自舉classloader, 被extension classloader或者被它自己所載入.
3. 結果是否定的.
4. 遵循"委託給父類"的模式, application classloader將載入的任務委託給extension classloader.
5. 再一次遵循"委託給父類"的模式, extension classloader將載入的任務委託給自舉classloader.
6. 自舉classloader沒有父classloader, 嘗試載入類.
7. 自舉classloader成功地載入了InitialContext類.
8. InitialContext的新實例被創建並返回給WhichClassloader.

這一場景需要進一步的解釋. 看上去似乎很明顯的是, application classloader是第一個收到創建InitialContext新實例的classloader, 因爲它位於classloader層次結構的最底層. 但並不總是這樣. 隨着Java 2的出現, 類將通過調用者的classloader被載入, 這可能是, 但也可能不是位於層次結構最底層的classloader. 在我們的例子中, 調用者是WhichClassLoader, 我們知道它是由application classloader載入的.

Java 2中的classloader遵循"委託給父類, 並組織成層次結構"的模式, 這允許你做一些有趣的事情. 但當類不是由classloader層次結構的葉節點載入時, 也可能引起問題. 比如, 讓我們修改一下WhichClassLoader, 重命名爲WhichClassLoader2, 並創建兩個新類WhichClassLoader3和WhichClassLoader4.

文件WhichClassLoader2.java:
public class WhichClassLoader2 {
//Constructor
WhichClassLoader2() {
//do nothing
}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

System.out.println("\nBootstrap classpath="+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );
//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader2 whichClassLoader2 = new WhichClassLoader2();
WhichClassLoader3 whichClassLoader3 = new WhichClassLoader3();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader2, was loaded by: " + whichClassLoader2.getClass().getClassLoader() + "\n");
System.out.println("\nUser file, WhichClassLoader3, was loaded by: " + whichClassLoader3.getClass().getClassLoader() + "\n");
whichClassLoader3.getTheClass();
}
}

文件WhichClassloader3.java:
public class WhichClassLoader3 {
public WhichClassLoader3 () {
}
public void getTheClass() {
WhichClassLoader4 wcl4 = new WhichClassLoader4 ();
System.out.println("WhichClassLoader4 was loaded by " + wcl4.getClass().getClassLoader());
}
}

文件WhichClassloader4.java:
public class WhichClassLoader4 {
WhichClassLoader4 () {
}
}

我們將WhichClassLoader2和WhichClassLoader4放在當前目錄下, 這樣它們僅僅存在於application classloader的路徑之中. 接下來我們將WhichClassLoader3放在extension classloader的路徑中.
D:\Classpath_Project\src>ls
WhichClassLoader2.class
WhichClassLoader2.java
WhichClassLoader1.java
WhichClassLoader4.class
WhichClassLoader4.java
D:\Classpath_Project\src>ls D:\jdk1.2.2\jre\lib\ext\
WhichClassLoader3.jar
cosnaming.jar
iiimp.jar
iioprt.jar
jndi.jar
providerutil.jar
rmiorb.jar
rmiregistry.jar

我們來看一下運行WhichClassLoader2會發生什麼.
D:\Classpath_Project\src>java -classpath . WhichClassLoader2
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
User file, WhichClassLoader2, was loaded by: sun.misc.Launcher$AppClassLoader@f01f3492
User file, WhichClassLoader3, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
java.lang.NoClassDefFoundError: WhichClassLoader4
at WhichClassLoader3.getTheClass(WhichClassLoader3.java:8)
at WhichClassLoaderTest.main(WhichClassLoaderTest.java:38)
Exception in thread "main"

你也許會問既然WhichClassLoader4明明在application classloader的路徑中, 載入WhichClassLoader4爲什麼會失敗. 這種失敗不是因爲classloader找不到類, 而是因爲WhichClassLoader3的classloader找不到類. 爲了更好地理解發生的事情, 讓我們從載入WhichClassLoader3的classloader(也就是extension classloader)的角度來看一下.

首先, 要求創建一個WhichClassLoader4的實例, 於是遵循Java 2的"委託給父類"模式, extension classloader先查看這個類是否已經被它所屬的classloader層次結構所載入, 這種嘗試將失敗. 於是它請求它的父classloader, 就是自舉classloader, 來載入這個類. 這一請求將返回一個NoClassDefFound異常. Extension classloader將捕獲這個異常並求助於它的最後選擇: 它試圖自己載入這個類. 搜尋了自己的搜索路徑之後, extension classloader找不到WhichClassLoader4類的定義, 所以它重新拋出NoClassDefFoundError異常. 此時不再有classloader來捕獲這個異常, 於是在屏幕上打印異常, 程序退出.

這就引起一個問題, 既然有可能發生這樣的問題, 爲什麼要自尋煩惱地採用多個classloader呢? 爲什麼不回到Java 2之前的框架, 在那樣的框架之下, 只有一個系統classloader來載入所有東西? 與引發的問題相比, classloader層次結構和"委託給父類"模式帶來的好處是否值得呢? 簡而言之, 是值得的:
1. 保護. Java預定義了自舉classloader和extension classloader, 缺省情況下, 只把application classloader的定義留給了用戶. 因爲"委託給父類"的模式, 用戶定義的任何類都不可能覆蓋Java擴展類和核心類.
2. 用戶友好. 在同樣的方式下, 因爲自舉classloader和extension classloader是預定義的, 你不再需要把classes.zip文件放在-classpath屬性中.
3. 隔離. 也許這是新的classloader定義做提供的最重要的好處. 到目前爲止, 這看上去不是那麼有用, 但我們還沒有談到Java允許你定義自己的classloader. 使用繼承, 可以形成自己的classloader層次樹.

理解這個模型所提供的益處的最佳方式是和操作系統的名字空間作一個類比. 讓我們假設我們正在開發一個叫MyApp的應用, 這個應用有三個版本: 一個是實際用的, 一個處於Beta測試中, 一個還在開發中. 我們想要在任何時候運行這些應用程序版本中的任何一個, 於是我們將它們放在分開的目錄結構中:
/usr/MyApp
/usr/MyApp/v1
/usr/MyApp/v2
/usr/MyApp/v3

所有版本公用的文件放在MyApp目錄下, 版本特定的文件分別放在v1, v2和v3目錄下. 使用這種方法, 我們能很有效地使用硬盤空間, 同時又能管理版本的差異. 同樣的想法對classloader也適用. 核心類是最通用的, 就放在層次結構的根節點上, 而那些特定"版本"的類放在葉節點上.

譯者注: 比較了WebSphere, WebLogic和JBoss之後, 發現它們的classloader設計都不一樣. 移植代碼時, 這可能引發一些問題.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章