thread.getcontextclassloader()?
盡管沒經(jīng)常遇到這個問題,但是想獲得準確的答案并不那么容易,特別是在開發(fā)應(yīng)用框架的時候,你需要動態(tài)的加載一些類和資源,不可避免的你會被此困擾。一般來說,動態(tài)載入資源有三種ClassLoader可以選擇,System ClassLoader(也叫AppClassLoader)、當前類的ClassLoader和CurrentThread的Context ClassLoader。那么,如何選擇使用?
首先可以簡單排除的是SystemClassLoader,這個ClassLoader負責從參數(shù)-classpath、-cp、和操作系統(tǒng)CLASSPATH中載入資源。并且,任何ClassLoader的getSystemXXX()方法都是有以上幾個路徑指定的。我們應(yīng)該很少需要編寫直接使用ClassLoader的程序,否則你的代碼將只能在命令行運行,發(fā)布你的代碼成為ejb、web應(yīng)用或者java web start應(yīng)用,我肯定他們會崩潰!
接下來,我們只剩下兩個選擇了:當前ClassLoader和Thread Context ClassLoader
Current ClassLoader:當前類所屬的ClassLoader,在虛擬機中類之間引用,默認就是使用這個ClassLoader。另外,當你使用Class.forName(), Class.getResource()這幾個不帶ClassLoader參數(shù)的方法是,默認同樣適用當前類的ClassLoader。你可以通過方法XX.class.GetClassLoader()獲取。
Thread Context ClassLoader,沒一個Thread有一個相關(guān)聯(lián)系的Context ClassLoader(由native方法建立的除外),可以通過Thread.setContextClassLoader()方法設(shè)置。如果你沒有主動設(shè)置,Thread默認集成Parent Thread的 Context ClassLoader(注意,是parentThread 不是父類)。如果 你整個應(yīng)用中都沒有對此作任何處理,那么 所有的Thread都會以SystemClassLoader作為ContextClassLoader。知道這一點很重要,因為從web服務(wù)器,java企業(yè)服務(wù)器使用一些復雜而且精巧的ClassLoader結(jié)構(gòu)去實現(xiàn)諸如JNDI、線程池和熱部署等功能以來,這種簡單的情況越發(fā)的少見了。
這篇文章中為什么把Thread Context ClassLoader放在首要的位置,別人并沒有大張旗鼓的介紹它?很多開發(fā)者都對此不甚了解,因為sun沒有提供很好的說明文檔。
事實上,ContextClassLoader提供一個突破委托代理機制的后門。虛擬機通過父子層次關(guān)系組織管理ClassLoader,沒有個ClassLoader都有一個ParentClassLoader(BootStartp不在此范圍之內(nèi)),當要求一個ClassLoader裝載一個類是,他首先請求ParentClassLoader去裝載,只有parent ClassLoader裝載失敗,才會嘗試自己裝載。
但是,某些時候這種順序機制會造成困擾,特別是jvm需要動態(tài)載入有開發(fā)者提供的資源時。就以JNDI為例,JNDI的類是由bootstarpClassLoader從rt.jar中間載入的,但是JNDI具體的核心驅(qū)動是由正式的實現(xiàn)提供的,并且通常會處于-cp參數(shù)之下(注:也就是默認的System ClassLoader管理),這就要求bootstartpClassLoader去載入只有SystemClassLoader可見的類,正常的邏輯就沒辦法處理。怎么辦呢?parent可以通過獲得當前調(diào)用Thread的方法獲得調(diào)用線程的Context ClassLoder 來載入類。
順帶補充一句,JAXP從1.4之后也換成了類似JNDI的ClassLoader實現(xiàn),嘿嘿,剛剛我說什么來著,SUN文檔缺乏 ^_^
介紹完這些之后,我們走到的十字路口,任一選擇都不是萬能的。一些人認為Context ClassLoader將會是新的標準。但是一旦你的多線程需要通訊某些共享數(shù)據(jù),你會發(fā)現(xiàn),你將有一張極其丑陋的ClassLoader分布圖,除非所有的線程使用一樣的ContextClassLoader。并且委派使用當前ClassLoder對一些方法來說是默認繼承來的,比如說Class.forName()。盡管你明確的在任何你能控制的地方使用Context ClassLoader,但是畢竟還有很多代碼不歸你管(備注:想起一個關(guān)于UNIX名字來源的笑話)。
某些應(yīng)用服務(wù)器使用不同的ClassLoder作為ContextClassLoader和當前ClassLoader,并且這些ClassLoader有著相同的ClassPath,但沒有父子關(guān)系,這使得情況更復雜。請列位看官,花幾秒鐘時間想一想,為什么這樣不好?被載入的類在虛擬機內(nèi)部有一個全名稱,不同的ClassLoader載入的相同名稱的類是不一樣的,這就隱藏了類型轉(zhuǎn)換錯誤的隱患。(注:奶奶的 俺就遇到過,JBOSSClassLoader機制蠻挫的)
這種混亂事實上在java類中也有,試著去猜測任何一個包含動態(tài)加載的java規(guī)范的ClassLoader機制,以下是一個清單:
JNDI uses context classloaders
Class.getResource() and Class.forName()
use the current classloader
JAXP uses context classloaders (as of J2SE 1.4)
java.util.ResourceBundle uses the caller's current classloader
URL protocol handlers specified via java.protocol.handler.pkgs
system property are looked up in the bootstrap and system classloaders only
Java Serialization API uses the caller's current classloader by default
而且關(guān)于這些資源的類加載機制文檔時很少。
java開發(fā)人員應(yīng)該怎么做?
如果你的實現(xiàn)是利用特定的框架,那么恭喜你,實現(xiàn)它遠比實現(xiàn)框架要簡單得多!例如,在web應(yīng)用和EJB應(yīng)用中,你僅僅只要使用 Class.getResource()就足夠了。
其他的情形下,俺有個建議(這個原則是俺工作中發(fā)現(xiàn)的)
下面這個類可以在整個應(yīng)用中的任何地方使用,作為一個全局的ClassLoader(所有的示例代碼可以從download下載):
通過ClassLoaderResolver.getClassLoader()方法獲得一個ClassLoader的引用,并且利用正常的ClassLoader的api去加載資源,你也可以使用 ResourceLoader
API作為備選方案
而決定使用何種ClassLoader策略是由接口實現(xiàn)的,這是一種插件機制,方便變更。
ClassLoadContext.getCallerClass()返回調(diào)用者給ClassLoaderResolver 或者ResourceLoader,因此能獲得調(diào)用者的ClassLoader。需要注意的是,調(diào)用者是不會變的(注:作者使用的final修飾字)。俺的方法不需要對現(xiàn)有的業(yè)務(wù)方法做擴展,而且可以作為靜態(tài)方法是用。而且,你可以根據(jù)自己的業(yè)務(wù)場景實現(xiàn)獨特的ClassLoaderContext。
看出來沒,這是一種很熟悉的設(shè)計模式,XD,把獲得ClassLoader的策略從業(yè)務(wù)中獨立出來,這個策略可以是"總是用ContextClassLoader"或者"總是用當前ClassLoader"。想預先知道那種策略是正確的比較困難,那么這種模式可以讓你簡單的改變策略。
俺寫了一個默認的實現(xiàn),基本可以對付95%的場景(enjoy yourself)
上面的邏輯比較簡單,如果當前ClassLoader和Context ClassLoader是父子關(guān)系,那就總選兒子,根據(jù)委托原則,這個很容易理解。
如果兩人平級,選擇正確的ClassLoader很重要,運行時不允許含糊。這種情況下,我的代碼選擇ContextClassLoader(這是俺個人的經(jīng)驗之談),當然也不要擔心不能改變,你能隨便根據(jù)需要改變。一般而言,ContextClassLoader比較適合框架,而Current ClassLoader在業(yè)務(wù)邏輯中用的更多。
最后,檢查確保選中的ClassLoader不是System ClassLoader的parent,一旦高于System ClassLoader ,請使用System ClassLoader(你的類部署在Ext路徑下面,就會出現(xiàn)這種情況)。
請注意,俺故意沒關(guān)注被載入資源的名稱。Java XML API 成為java核心api的經(jīng)歷告訴我們,根據(jù)資源名稱過濾是很不cool的idea。而且我也沒有去確認到底哪個ClassLoader被取得了,因為只要清楚原理,這很容易被推理出來。(哈哈,俺是強淫)
盡管討論java 的ClassLoader不是一個很cool的話題(譯者注,當年不cool,但是現(xiàn)在很cool),而且JavaEE的ClassLoader策略越發(fā)的依賴各種平臺的升級。如果這沒有一個更好的設(shè)計的話,將會變成一個大大的問題。不敢您是否同意俺的觀點,俺尊重你說話的權(quán)利,所以請給俺分享您的意見經(jīng)驗。