ClassLoader主要對(duì)類(lèi)的請(qǐng)求提供服務(wù),當(dāng)JVM需要某類(lèi)時(shí),它根據(jù)名稱(chēng)向ClassLoader要求這個(gè)類(lèi),然后由ClassLoader返回這個(gè)類(lèi)的class對(duì)象。 1.1 幾個(gè)相關(guān)概念ClassLoader負(fù)責(zé)載入系統(tǒng)的所有Resources(Class,文件,來(lái)自網(wǎng)絡(luò)的字節(jié)流等),通過(guò)ClassLoader從而將資源載入JVM
每個(gè)class都有一個(gè)reference,指向自己的ClassLoader。Class.getClassLoader()
array的ClassLoader就是其元素的ClassLoader,若是基本數(shù)據(jù)類(lèi)型,則這個(gè)array沒(méi)有ClassLoader
1.2 主要方法和工作過(guò)程java1.1及從前版本中,ClassLoader主要方法:
Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口點(diǎn)
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節(jié)組成的數(shù)組并把它轉(zhuǎn)換成 Class 對(duì)象。原始數(shù)組包含如從文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。
findSystemClass 方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類(lèi)文件,如果存在,就使用 defineClass 將原始字節(jié)轉(zhuǎn)換成 Class 對(duì)象,以將該文件轉(zhuǎn)換成類(lèi)。當(dāng)運(yùn)行 Java 應(yīng)用程序時(shí),這是 JVM 正常裝入類(lèi)的缺省機(jī)制。
resolveClass可以不完全地(不帶解析)裝入類(lèi),也可以完全地(帶解析)裝入類(lèi)。當(dāng)編寫(xiě)我們自己的 loadClass 時(shí),可以調(diào)用 resolveClass,這取決于 loadClass 的 resolve 參數(shù)的值
findLoadedClass 充當(dāng)一個(gè)緩存:當(dāng)請(qǐng)求 loadClass 裝入類(lèi)時(shí),它調(diào)用該方法來(lái)查看 ClassLoader 是否已裝入這個(gè)類(lèi),這樣可以避免重新裝入已存在類(lèi)所造成的麻煩。應(yīng)首先調(diào)用該方法
一般load方法過(guò)程如下:
調(diào)用 findLoadedClass 來(lái)查看是否存在已裝入的類(lèi)。
如果沒(méi)有,那么采用某種特殊的神奇方式來(lái)獲取原始字節(jié)。(通過(guò)IO從文件系統(tǒng),來(lái)自網(wǎng)絡(luò)的字節(jié)流等)
如果已有原始字節(jié),調(diào)用 defineClass 將它們轉(zhuǎn)換成 Class 對(duì)象。
如果沒(méi)有原始字節(jié),然后調(diào)用 findSystemClass 查看是否從本地文件系統(tǒng)獲取類(lèi)。
如果 resolve 參數(shù)是 true,那么調(diào)用 resolveClass 解析 Class 對(duì)象。
如果還沒(méi)有類(lèi),返回 ClassNotFoundException。
否則,將類(lèi)返回給調(diào)用程序。
1.3 委托模型自從JDK1.2以后,ClassLoader做了改進(jìn),使用了委托模型,所有系統(tǒng)中的ClassLoader組成一棵樹(shù),ClassLoader在載入類(lèi)庫(kù)時(shí)先讓Parent尋找,Parent找不到才自己找。
JVM在運(yùn)行時(shí)會(huì)產(chǎn)生三個(gè)ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++編寫(xiě)的,在Java中看不到它,是null。它用來(lái)加載核心類(lèi)庫(kù),就是在lib下的類(lèi)庫(kù),Extension ClassLoader加載lib/ext下的類(lèi)庫(kù),App ClassLoader加載Classpath里的類(lèi)庫(kù),三者的關(guān)系為:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent為Bootstrap ClassLoader。加載一個(gè)類(lèi)時(shí),首先BootStrap進(jìn)行尋找,找不到再由Extension ClassLoader尋找,最后才是App ClassLoader。
將ClassLoader設(shè)計(jì)成委托模型的一個(gè)重要原因是出于安全考慮,比如在Applet中,如果編寫(xiě)了一個(gè)java.lang.String類(lèi)并具有破壞性。假如不采用這種委托機(jī)制,就會(huì)將這個(gè)具有破壞性的String加載到了用戶(hù)機(jī)器上,導(dǎo)致破壞用戶(hù)安全。但采用這種委托機(jī)制則不會(huì)出現(xiàn)這種情況。因?yàn)橐虞djava.lang.String類(lèi)時(shí),系統(tǒng)最終會(huì)由Bootstrap進(jìn)行加載,這個(gè)具有破壞性的String永遠(yuǎn)沒(méi)有機(jī)會(huì)加載。
委托模型還帶來(lái)了一些問(wèn)題,在某些情況下會(huì)產(chǎn)生混淆,如下是Tomcat的ClassLoader結(jié)構(gòu)圖:
Bootstrap
|
System
|
Common
/
Catalina Shared
/
Webapp1 Webapp2 ...
由 Common 類(lèi)裝入器裝入的類(lèi)決不能(根據(jù)名稱(chēng))直接訪問(wèn)由 Web 應(yīng)用程序裝入的類(lèi)。使這些類(lèi)聯(lián)系在一起的唯一方法是通過(guò)使用這兩個(gè)類(lèi)集都可見(jiàn)的接口。在這個(gè)例子中,就是包含由 Java servlet 實(shí)現(xiàn)的 javax.servlet.Servlet。
如果在lib或者lib/ext等類(lèi)庫(kù)有與應(yīng)用中同樣的類(lèi),那么應(yīng)用中的類(lèi)將無(wú)法被載入。通常在jdk新版本出現(xiàn)有類(lèi)庫(kù)移動(dòng)時(shí)會(huì)出現(xiàn)問(wèn)題,例如最初我們使用自己的xml解析器,而在jdk1.4中xml解析器變成標(biāo)準(zhǔn)類(lèi)庫(kù),load的優(yōu)先級(jí)也高于我們自己的xml解析器,我們自己的xml解析器永遠(yuǎn)無(wú)法找到,將可能導(dǎo)致我們的應(yīng)用無(wú)法運(yùn)行。
相同的類(lèi),不同的ClassLoader,將導(dǎo)致ClassCastException異常
1.4 線(xiàn)程中的ClassLoader每個(gè)運(yùn)行中的線(xiàn)程都有一個(gè)成員contextClassLoader,用來(lái)在運(yùn)行時(shí)動(dòng)態(tài)地載入其它類(lèi),可以使用方法Thread.currentThread().setContextClassLoader(...);更改當(dāng)前線(xiàn)程的contextClassLoader,來(lái)改變其載入類(lèi)的行為;也可以通過(guò)方法Thread.currentThread().getContextClassLoader()來(lái)獲得當(dāng)前線(xiàn)程的ClassLoader。
實(shí)際上,在Java應(yīng)用中所有程序都運(yùn)行在線(xiàn)程里,如果在程序中沒(méi)有手工設(shè)置過(guò)ClassLoader,對(duì)于一般的java類(lèi)如下兩種方法獲得的ClassLoader通常都是同一個(gè)
this.getClass.getClassLoader();
Thread.currentThread().getContextClassLoader();
方法一得到的Classloader是靜態(tài)的,表明類(lèi)的載入者是誰(shuí);方法二得到的Classloader是動(dòng)態(tài)的,誰(shuí)執(zhí)行(某個(gè)線(xiàn)程),就是那個(gè)執(zhí)行者的Classloader。對(duì)于單例模式的類(lèi),靜態(tài)類(lèi)等,載入一次后,這個(gè)實(shí)例會(huì)被很多程序(線(xiàn)程)調(diào)用,對(duì)于這些類(lèi),載入的Classloader和執(zhí)行線(xiàn)程的Classloader通常都不同。
1.5 Web應(yīng)用中的ClassLoader回到上面的例子,在Tomcat里,WebApp的ClassLoader的工作原理有點(diǎn)不同,它先試圖自己載入類(lèi)(在ContextPath/WEB-INF/...中載入類(lèi)),如果無(wú)法載入,再請(qǐng)求父ClassLoader完成。
由此可得:
對(duì)于WEB APP線(xiàn)程,它的contextClassLoader是WebAppClassLoader
對(duì)于Tomcat Server線(xiàn)程,它的contextClassLoader是CatalinaClassLoader
1.6 獲得ClassLoader的幾種方法可以通過(guò)如下3種方法得到ClassLoader
this.getClass.getClassLoader(); // 使用當(dāng)前類(lèi)的ClassLoader
Thread.currentThread().getContextClassLoader(); // 使用當(dāng)前線(xiàn)程的ClassLoader
ClassLoader.getSystemClassLoader(); // 使用系統(tǒng)ClassLoader,即系統(tǒng)的入口點(diǎn)所使用的ClassLoader。(注意,system ClassLoader與根ClassLoader并不一樣。JVM下system ClassLoader通常為App ClassLoader)
1.7 幾種擴(kuò)展應(yīng)用用戶(hù)定制自己的ClassLoader可以實(shí)現(xiàn)以下的一些應(yīng)用
安全性。類(lèi)進(jìn)入JVM之前先經(jīng)過(guò)ClassLoader,所以可以在這邊檢查是否有正確的數(shù)字簽名等
加密。java字節(jié)碼很容易被反編譯,通過(guò)定制ClassLoader使得字節(jié)碼先加密防止別人下載后反編譯,這里的ClassLoader相當(dāng)于一個(gè)動(dòng)態(tài)的解碼器
歸檔。可能為了節(jié)省網(wǎng)絡(luò)資源,對(duì)自己的代碼做一些特殊的歸檔,然后用定制的ClassLoader來(lái)解檔
自展開(kāi)程序。把java應(yīng)用程序編譯成單個(gè)可執(zhí)行類(lèi)文件,這個(gè)文件包含壓縮的和加密的類(lèi)文件數(shù)據(jù),同時(shí)有一個(gè)固定的ClassLoader,當(dāng)程序運(yùn)行時(shí)它在內(nèi)存中完全自行解開(kāi),無(wú)需先安裝
動(dòng)態(tài)生成。可以生成應(yīng)用其他還未生成類(lèi)的類(lèi),實(shí)時(shí)創(chuàng)建整個(gè)類(lèi)并可在任何時(shí)刻引入JVM
2.0 資源載入
所有資源都通過(guò)ClassLoader載入到JVM里,那么在載入資源時(shí)當(dāng)然可以使用ClassLoader,只是對(duì)于不同的資源還可以使用一些別的方式載入,例如對(duì)于類(lèi)可以直接new,對(duì)于文件可以直接做IO等。 2.1 載入類(lèi)的幾種方法假設(shè)有類(lèi)A和類(lèi)B,A在方法amethod里需要實(shí)例化B,可能的方法有3種。對(duì)于載入類(lèi)的情況,用戶(hù)需要知道B類(lèi)的完整名字(包括包名,例如"com.rain.B")
1. 使用Class靜態(tài)方法 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來(lái)載入B
/* Step 3. new instance */
B b = (B)cls.newInstance(); // 有B的類(lèi)得到一個(gè)B的實(shí)例
3. 直接new
B b = new B();
2.2 文件載入(例如配置文件等)假設(shè)在com.rain.A類(lèi)里想讀取文件夾 /com/rain/config 里的文件sys.
對(duì)于相對(duì)路徑,其相對(duì)值是相對(duì)于ClassLoader的,因?yàn)镃lassLoader是一棵樹(shù),所以這個(gè)相對(duì)路徑和ClassLoader樹(shù)上的任何一個(gè)ClassLoader相對(duì)比較后可以找到文件,那么文件就可以找到,當(dāng)然,讀取文件也使用委托模型
1. 直接IO
/**
* 假設(shè)當(dāng)前位置是 "C:/test",通過(guò)執(zhí)行如下命令來(lái)運(yùn)行A "java com.rain.A"
* 1. 在程序里可以使用絕對(duì)路徑,Windows下的絕對(duì)路徑以盤(pán)號(hào)開(kāi)始,Unix下以"/"開(kāi)始
* 2. 也可以使用相對(duì)路徑,相對(duì)路徑前面沒(méi)有"/"
* 因?yàn)槲覀冊(cè)?nbsp;"C:/test" 目錄下執(zhí)行程序,程序入口點(diǎn)是"C:/test",相對(duì)路徑就
* 是 "com/rain/config/sys.properties"
* (例子中,當(dāng)前程序的ClassLoader是App ClassLoader,system ClassLoader = 當(dāng)前的
* 程序的ClassLoader,入口點(diǎn)是"C:/test")
* 對(duì)于ClassLoader樹(shù),如果文件在jdk lib下,如果文件在jdk lib/ext下,如果文件在環(huán)境變量里,
* 都可以通過(guò)相對(duì)路徑"sys.properties"找到,lib下的文件最先被找到
*/
File f = new File("C:/test/com/rain/config/sys.properties"); // 使用絕對(duì)路徑
//File f = new File("com/rain/config/sys.properties"); // 使用相對(duì)路徑
InputStream is = new FileInputStream(f);
如果是配置文件,可以通過(guò)java.util.Properties.load(is)將內(nèi)容讀到Properties里,Properties默認(rèn)認(rèn)為is的編碼是ISO-8859-1,如果配置文件是非英文的,可能出現(xiàn)亂碼問(wèn)題。
2. 使用ClassLoader
/**
* 因?yàn)橛?種方法得到ClassLoader,對(duì)應(yīng)有如下3種方法讀取文件
* 使用的路徑是相對(duì)于這個(gè)ClassLoader的那個(gè)點(diǎn)的相對(duì)路徑,此處只能使用相對(duì)路徑
*/
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
如果是配置文件,可以通過(guò)java.util.Properties.load(is)將內(nèi)容讀到Properties里,這里要注意編碼問(wèn)題。
3. 使用ResourceBundle
ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys");
這種用法通常用來(lái)載入用戶(hù)的配置文件,關(guān)于ResourceBunlde更詳細(xì)的用法請(qǐng)參考其他文檔
總結(jié):有如下3種途徑來(lái)載入文件
1. 絕對(duì)路徑 ---> IO
2. 相對(duì)路徑 ---> IO
---> ClassLoader
3. 資源文件 ---> ResourceBundle
2.3 如何在web應(yīng)用里載入資源在web應(yīng)用里當(dāng)然也可以使用ClassLoader來(lái)載入資源,但更常用的情況是使用ServletContext,如下是web目錄結(jié)構(gòu)
ContextRoot
|- jsp、HTML、Image等各種文件
|- [WEB-INF]
|- web.xml
|- [lib] Web用到的JAR文件
|- [classes] 類(lèi)文件
用戶(hù)程序通常在classes目錄下,如果想讀取classes目錄里的文件,可以使用ClassLoader,如果想讀取其他的文件,一般使用ServletContext.getResource()
如果使用ServletContext.getResource(path)方法,路徑必須以"/"開(kāi)始,路徑被解釋成相對(duì)于ContextRoot的路徑,此處載入文件的方法和ClassLoader不同,舉例"/WEB-INF/web.xml","/download/WebExAgent.rar"
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注