ClassLoader 詳解及用途(寫的不錯)


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"


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM