破壞雙親委派模型


上次說了類加載器以及它的雙親委派模型,同樣提到了雙親委派模型並不是一種強制的約束,而是推薦給開發者的類加載器的實現方式,在java中,大部分類加載器都會遵循這個模型,但是也有例外,到目前為止,雙親委派模型主要出現過3次較大規模的“被破壞的”情況。

第一次:

  發生在雙親委派模型出現之前,即JDK1.2之前,由於雙親委派模型在JDK1.2之后才被引入,而類加載器和抽象類java.Lang.ClassLoader則在JDK1.0時代就已經存在了,面對已經存在的用戶自定義類加載器的實現代碼,java設計者引入雙親委派模型時不得不做出一些妥協。為了向前兼容,JDK1.2之后的java.Lang.ClassLoader添加了一個新的protected方法findClass(),在此之前,用戶都是去重寫loadClass()方法,因為虛擬機在進行類加載的時候會調用加載器的私有方法loadClassInternal(),而這個方法的唯一邏輯就是去調用自己的loadClass().

  我們之前也說了loadClass()方法的代碼,雙親委派的具體邏輯就實現在這個方法之中,JDK1.2之后已不再提倡用戶去覆蓋loadClass方法,而是把自己的類加載邏輯 寫到findClass()方法中,在loadClass()方法的邏輯里,如果父類加載失敗,則會調用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是復合雙親委派模型的。

第二次:

  是由這個模型的自身的缺陷導致的,雙親委派能夠很好地解決了各個類加載器的基礎類的統一問題(越基礎的類由越上層的加載器進行加載),基礎類之所以成為基礎,是因為它們總是作為被用戶代碼調用的API,但世事往往沒有絕對的完美,如果基礎類又要調用回用戶的代碼,那該怎么辦呢?

  那jdk又是怎么做的呢?他們引入了:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.Lang.Thread類的setContextClassLoader()方法進行設置,如果創建線程時還未設置,他將會從父線程中繼承一個,如果在應用程序的全局范圍都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。Java 提供了很多服務提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實現。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等,這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實現代碼則是作為 Java 應用所依賴的 jar 包被包含進類路徑(CLASSPATH)里。SPI接口中的代碼經常需要加載具體的實現類。那么問題來了,SPI的接口是Java核心庫的一部分,是由啟動類加載器來加載的;SPI的實現類是由系統類加載器來加載的。啟動類加載器是無法找到 SPI 的實現類的,因為依照雙親委派模型,BootstrapClassloader無法委派AppClassLoader來加載類。有了線程上下文類加載器,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上已經打破了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型的一般性原則。

一個典型的例子就是JNDI服務,它的代碼由啟動類加載器去加載(在JDK1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查找,他需要調用由獨立廠商實現並部署在應用程序的ClassPath下的JNDI接口提供者(SPI)的代碼,但是啟動類加載器不可能認識這些代碼,

這里稍微說下什么是JNDI呢?

https://blog.csdn.net/wn084/article/details/80729230 JNDI到底是什么?

從上面的文章中可以看出: 
1、JNDI 提出的目的是為了解藕,避免了程序與數據庫之間的緊耦合,使應用更加易於配置、易於部署
2、JNDI 在j2ee系統中的角色是“交換機”,是J2EE組件在運行時 間接地查找其他組件、資源或服務的通用機制。

舉一個具體實現的例子:比如說我們要加載數據庫信息,如果是小工程的做法,一般來說就把數據庫信息直接寫死在代碼中,這種情況我們現在看來是有問題的,因為數據庫的加載和數據庫的具體信息是可以分割開的,在實現數據庫代碼實現時,可以不考慮數據庫連接的信息。

那么我們應該把這些信息放在哪里呢?JDNI認為應該同容器放在一起,由於我沒有用過jboss,這里簡單用tomcat說下,這些數據庫信息應該方法server.xml中,再去加載。

(我們目前的線上項目使用的是放在properties文件中,使用spring來管理這些配置信息)個人認為把這些信息放在tomcat中其實也不好。

https://blog.csdn.net/w605283073/article/details/89164675 Class.forName 詳解

https://juejin.im/post/5af952fdf265da0b9e652de3  java SPI機制講解

第三次:

  是由於開發者對程序動態性的追求而導致的,這里說的“動態性”指的是當前一些非常熱門的名詞,代碼熱替換、模塊熱部署等,說白了就是希望應用程序能夠像我們的計算機外設那樣,接上鼠標、U盤不用重啟機器就能使用,鼠標有問題就換個鼠標,不用停機也不用重啟。對於個人計算機說來,重啟一次其實沒有什么,但是對於一些生產系統來說,關機重啟一次可能要被列為生產事故,這種情況下熱部署就有很大的吸引力。

  這里要說到的就是OSGi,先簡單了解下什么是OSGi,OSGI(Open Service Gateway Initiative,直譯為“開放服務網關”)OSGi聯盟給出的最新OSGi定義是The Dynamic Module System for Java,即面向Java的動態模塊化系統。

(圖片來自網絡)

 

它實現模塊化熱部署的關鍵就是它自定義的類加載器機制的實現,每一個程序模塊(在OSGi中稱為Bundle)都有一個自己的類加載器,當需要更換一個Bundlle時,就把Bundle連同類加載器一起換掉以實現代碼的熱替換。

我們這里簡單描述下OSGi收到類加載請求的處理過程

1.將以java.*開頭的類委派給父類加載器加載。

2.否則,將委派列表名單內的類委派給父類加載器加載

3.否則將Import列表中得類委派給Export這個類的Bundle的類加載器加載。

4.否則,查找當前Bundle的ClassPath,使用自己的類加載器加載。

5.否則,查找類是否在自己的Fragment Bundle中,如果在,則委派給FragmentBundle的類加載器加載。

6.否則,查找Dynamic Import列表的Bundle,委派給對應的Bundle的類加載器加載。

7.否則,類查找失敗

上面的查詢順序雖然只有開頭兩點復合雙親委派模型的規則,其余的類查找都是在平級的類加載器中進行的。

 

如果想要更了解OSGi的話,這里我找了篇文章,https://blog.51cto.com/9291927/2125230 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM