上次說了類加載器以及它的雙親委派模型,同樣提到了雙親委派模型并不是一種強制的限制,而是推薦給開發者的類加載器的實作方式,在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呢?
從上面的文章中可以看出:
1、JNDI 提出的目的是為了解藕,避免了程式與資料庫之間的緊耦合,使應用更加易于配置、易于部署
2、JNDI 在j2ee系統中的角色是“交換機”,是J2EE元件在運作時 間接地查找其他元件、資源或服務的通用機制。
舉一個具體實作的例子:比如說我們要加載資料庫資訊,如果是小工程的做法,一般來說就把資料庫資訊直接寫死在代碼中,這種情況我們現在看來是有問題的,因為資料庫的加載和資料庫的具體資訊是可以分割開的,在實作資料庫代碼實作時,可以不考慮資料庫連接配接的資訊。
那麼我們應該把這些資訊放在哪裡呢?JDNI認為應該同容器放在一起,由于我沒有用過jboss,這裡簡單用tomcat說下,這些資料庫資訊應該方法server.xml中,再去加載。
(我們目前的線上項目使用的是放在properties檔案中,使用spring來管理這些配置資訊)個人認為把這些資訊放在tomcat中其實也不好。
第三次:
是由于開發者對程式動态性的追求而導緻的,這裡說的“動态性”指的是目前一些非常熱門的名詞,代碼熱替換、子產品熱部署等,說白了就是希望應用程式能夠像我們的計算機外設那樣,接上滑鼠、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.否則,類查找失敗
上面的查詢順序雖然隻有開頭兩點複合雙親委派模型的規則,其餘的類查找都是在平級的類加載器中進行的。