ClassLoader詳解及用途

ClassLoader主要對類的請求提供服務,當JVM須要某類時,它根據名稱向ClassLoader要求這個類,而後由ClassLoader返回 這個類的class對象。java

 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"web

相關文章
相關標籤/搜索