獲得ClassLoader的幾種方法可以通過如下3種方法得到ClassLoader
this.getClass().getClassLoader(); // 使用當前類的ClassLoader
Thread.currentThread().getContextClassLoader(); // 使用當前線程的ClassLoader
ClassLoader.getSystemClassLoader(); // 使用系統ClassLoader,即系統的入口點所使用的ClassLoader。(注意,system ClassLoader與根ClassLoader並不一樣。JVM下system ClassLoader通常為App ClassLoader)
路徑區別:
在使用ClassLoader.getResourceAsStream時, 路徑直接使用相對於classpath的絕對路徑.不加"/"
Class.getResourceAsStream()是相對路徑。
----------------------------------------------------------------
Class.getResourse()和Class.getClassLoader().getResource()
這兩個getResource()是使用當前ClassLoader加載資源(即資源在 Class path中),這樣資源和class直接打在jar包中,避免文件路徑問題.兩者不同是Class的getResource()方法是從當前.class 文件路徑查找資源,ClassLoader則是從jar包根目錄查找.
Class.getResource()
public java.net.URL getResource(String name)查找帶有給定名稱的資源.查找與給定類相關的資源的規則是通過定義類的 class loader 實現的.此方法委托給此對象的類加載器.如果此對象通過引導類加載器加載,則此方法將委托給 ClassLoader.getSystemResource(java.lang.String).
在委托前,使用下面的算法從給定的資源名構造一個絕對資源名:
ClassLoader.getResource()
public URL getResource(String name)查找具有給定名稱的資源.資源是可以通過類代碼以與代碼基無關的方式訪問的一些數據(圖像、聲音、文本等). 資源名稱是以 ‘/’ 分隔的標識資源的路徑名稱.
此方法首先搜索資源的父類加載器;如果父類加載器為 null,則搜索的路徑就是虛擬機的內置類加載器的路徑.如果搜索失敗,則此方法將調用 findResource(String) 來查找資源.
兩個方法的區別是資源的定義不同, 主要用於相對與一個object取資源,而另一個用於取相對於classpath的資源,用的是絕對路徑.
在使用Class.getResourceAsStream 時,資源路徑有兩種方式,一種以/開頭,則這樣的路徑是指定絕對路徑,如果不以/開頭,則路徑是相對與這個class所在的包的.
在使用ClassLoader.getResourceAsStream時, 路徑直接使用相對於classpath的絕對路徑.
project
|-src
|-com.xx.test
|-Main.java
|-b.bmp
|-resource
|-com.icon
|-a.bmp
Main.class.getResource("/icon/a.bmp"); // NOT icon/a.bmp
Main.class.getResource("b.bmp");
// need to add resource/a.bmp to build path! it will be package in jar file
Main.class.getClassLoader().getResource("icon/a.bmp");
// NOT /icon/a.bmp or a.bmp
Thread.currentThread().getContextClassLoader().getResource("icon/a.bmp");
--------------------------------------------------------------------------------------------------------
getClassLoader()報 java.lang.NullPointerException原因:
at android.content.ContextWrapper.getClassLoader
(ContextWrapper.java:130)
InputStream is = getClass().getClassLoader().getResourceAsStream("helloworld.properties");中getClass()和getClassLoader()都是什么意思呀.
getClass():取得當前對象所屬的Class對象
getClassLoader():取得該Class對象的類裝載器
類裝載器負責從Java字符文件將字符流讀入內存,並構造Class類對象,在你說的問題哪里,通過它可以得到一個文件的輸入流
getClass :
public final Class getClass()
Returns the runtime class of an object. That Class object is the object that is locked by static synchronized methods of the represented class.
Returns:
the object of type Class that represents the runtime class of the object.
getClassLoader
public ClassLoader getClassLoader()
Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.
If a security manager is present, and the caller′s class loader is not null and the caller′s class loader is not the same as or an ancestor of the class loader for the class whose class loader is requested, then this method calls the security manager′s checkPermission
method with a RuntimePermission("getClassLoader") permission to ensure it′s ok to access the class loader for the class.
If this object represents a primitive type or void, null is returned.
Returns:
the class loader that loaded the class or interface represented by this object.
Throws:
SecurityException - if a security manager exists and its checkPermission method denies access to the class loader for the class.
See Also:
ClassLoader, SecurityManager.checkPermission(java.security.Permission), RuntimePermission
Class.getClassLoader()的一個小陷阱:)
昨天我的code總在Integer.class.getClassLoader().getResource("*********");這一句拋出空指針異常,定位為getClassLoader()返回null,查了一下jdk的文檔,原來這里還有一個陷阱:
jdk中關於getClassLoader()的描述:
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*
* <p> If a security manager is present, and the caller's class loader is
* not null and the caller's class loader is not the same as or an ancestor of
* the class loader for the class whose class loader is requested, then
* this method calls the security manager's <code>checkPermission</code>
* method with a <code>RuntimePermission("getClassLoader")</code>
* permission to ensure it's ok to access the class loader for the class.
*
* <p>If this object
* represents a primitive type or void, null is returned.
.....
上面的英文可以用下面的話來理解:
裝載類的過程非常簡單:查找類所在位置,並將找到的Java類的字節碼裝入內存,生成對應的Class對象。Java的類裝載器專門用來實現這樣的過程,JVM並不止有一個類裝載器,事實上,如果你願意的話,你可以讓JVM擁有無數個類裝載器,當然這除了測試JVM外,我想不出還有其他的用途。你應該已經發現到了這樣一個問題,類裝載器自身也是一個類,它也需要被裝載到內存中來,那么這些類裝載器由誰來裝載呢,總得有個根吧?沒錯,確實存在這樣的根,它就是神龍見首不見尾的Bootstrap ClassLoader. 為什么說它神龍見首不見尾呢,因為你根本無法在Java代碼中抓住哪怕是它的一點點的尾巴,盡管你能時時刻刻體會到它的存在,因為java的運行環境所需要的所有類庫,都由它來裝載,而它本身是C++寫的程序,可以獨立運行,可以說是JVM的運行起點,偉大吧。在Bootstrap完成它的任務后,會生成一個AppClassLoader(實際上之前系統還會使用擴展類裝載器ExtClassLoader,它用於裝載Java運行環境擴展包中的類),這個類裝載器才是我們經常使用的,可以調用ClassLoader.getSystemClassLoader()來獲得,我們假定程序中沒有使用類裝載器相關操作設定或者自定義新的類裝載器,那么我們編寫的所有java類通通會由它來裝載,值得尊敬吧。AppClassLoader查找類的區域就是耳熟能詳的Classpath,也是初學者必須跨過的門檻,有沒有靈光一閃的感覺,我們按照它的類查找范圍給它取名為類路徑類裝載器。還是先前假定的情況,當Java中出現新的類,AppClassLoader首先在類傳遞給它的父類類裝載器,也就是Extion ClassLoader,詢問它是否能夠裝載該類,如果能,那AppClassLoader就不干這活了,同樣Extion ClassLoader在裝載時,也會先問問它的父類裝載器。我們可以看出類裝載器實際上是一個樹狀的結構圖,每個類裝載器有自己的父親,類裝載器在裝載類時,總是先讓自己的父類裝載器裝載(多么尊敬長輩),如果父類裝載器無法裝載該類時,自己就會動手裝載,如果它也裝載不了,那么對不起,它會大喊一聲:Exception,class not found。有必要提一句,當由直接使用類路徑裝載器裝載類失敗拋出的是NoClassDefFoundException異常。如果使用自定義的類裝載器loadClass方法或者ClassLoader的findSystemClass方法裝載類,如果你不去刻意改變,那么拋出的是ClassNotFoundException。
這里jdk告訴我們:如果一個類是通過bootstrap 載入的,那我們通過這個類去獲得classloader的話,有些jdk的實現是會返回一個null的,比如說我用 new Object().getClass().getClassLoader()的話,會返回一個null,這樣的話上面的代碼就會出現NullPointer異常.所以保險起見我們最好還是使用我們自己寫的類來獲取classloader("this.getClass().getClassLoader()“),這樣一來就不會有問題。
--------------------------------------------------------------------------------------------------------
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
---> ClassLoade
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"