1.第一次破壞
由于雙親委派模型是在JDK1.2之后才被引入的,而類加載器和抽象類java.lang.ClassLoader則在JDK1.0時代就已經(jīng)存在癌淮,面對已經(jīng)存在的用戶自定義類加載器的實現(xiàn)代碼玄呛,Java設計者引入雙親委派模型時不得不做出一些妥協(xié)蛉顽。在此之前尤筐,用戶去繼承java.lang.ClassLoader的唯一目的就是為了重寫loadClass()方法诉探,因為虛擬機在進行類加載的時候會調(diào)用加載器的私有方法loadClassInternal()日熬,而這個方法唯一邏輯就是去調(diào)用自己的loadClass()。
2.第二次破壞
雙親委派模型的第二次“被破壞”是由這個模型自身的缺陷所導致的阵具,雙親委派很好地解決了各個類加載器的基礎類的同一問題(越基礎的類由越上層的加載器進行加載)碍遍,基礎類之所以稱為“基礎”,是因為它們總是作為被用戶代碼調(diào)用的API阳液,但世事往往沒有絕對的完美怕敬。
如果基礎類又要調(diào)用回用戶的代碼,那該么辦帘皿?
一個典型的例子就是JNDI服務东跪,JNDI現(xiàn)在已經(jīng)是Java的標準服務,
它的代碼由啟動類加載器去加載(在JDK1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查找虽填,它需要調(diào)用由獨立廠商實現(xiàn)并部署在應用程序的ClassPath下的JNDI接口提供者的代碼丁恭,但啟動類加載器不可能“認識”這些代碼。
為了解決這個問題斋日,Java設計團隊只好引入了一個不太優(yōu)雅的設計:線程上下文類加載器(Thread Context ClassLoader)牲览。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設置,如果創(chuàng)建線程時還未設置恶守,他將會從父線程中繼承一個第献,如果在應用程序的全局范圍內(nèi)都沒有設置過的話,那這個類加載器默認就是應用程序類加載器兔港。
有了線程上下文加載器庸毫,JNDI服務就可以使用它去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作衫樊,這種行為實際上就是打通了雙親委派模型層次結構來逆向使用類加載器飒赃,實際上已經(jīng)違背了雙親委派模型的一般性原則,但這也是無可奈何的事情科侈。Java中所有涉及SPI的加載動作基本上都采用這種方式载佳,例如JNDI、JDBC兑徘、JCE刚盈、JAXB和JBI等。
3.第三次破壞
雙親委派模型的第三次“被破壞”是由于用戶對程序動態(tài)性的追求導致的挂脑,這里所說的“動態(tài)性”指的是當前一些非撑菏“熱門”的名詞:代碼熱替換、模塊熱部署等崭闲,簡答的說就是機器不用重啟肋联,只要部署上就能用。
OSGi實現(xiàn)模塊化熱部署的關鍵則是它自定義的類加載器機制的實現(xiàn)刁俭。每一個程序模塊(Bundle)都有一個自己的類加載器橄仍,當需要更換一個Bundle時,就把Bundle連同類加載器一起換掉以實現(xiàn)代碼的熱替換牍戚。在OSGi幻境下侮繁,類加載器不再是雙親委派模型中的樹狀結構,而是進一步發(fā)展為更加復雜的網(wǎng)狀結構如孝,當受到類加載請求時宪哩,OSGi將按照下面的順序進行類搜索:
1)將java.*開頭的類委派給父類加載器加載。
2)否則第晰,將委派列表名單內(nèi)的類委派給父類加載器加載锁孟。
3)否則彬祖,將Import列表中的類委派給Export這個類的Bundle的類加載器加載。
4)否則品抽,查找當前Bundle的ClassPath储笑,使用自己的類加載器加載。
5)否則圆恤,查找類是否在自己的Fragment Bundle中突倍,如果在,則委派給Fragment Bundle的類加載器加載哑了。
6)否則赘方,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載弱左。
7)否則,類加載器失敗炕淮。