天天看點

破壞雙親委派模型的了解

《深入了解Java虛拟機:JVM進階特性與最佳實踐》一書中,有這樣一段:

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

對于這一段話一直不是很了解,上層加載器加載的基礎類為什麼要回調下層加載器加載的代碼?類加載機制更全面一點的描述可以是全盤負責與雙親委托,這樣可能更好了解上面的話。

全盤負責:是指當一個ClassLoader裝載一個類時,除非顯示地使用另一個ClassLoader,則該類所依賴及引用的類也由這個CladdLoader載入。

雙親委派:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,是以所有的加載請求最終都應該傳送到頂層的啟動類加載器中,隻有當父加載器回報自己無法完成這個加載請求(它的搜尋範圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

通過上面兩個原則,上層加載器依賴及引用的類預設情況下也要該類加載器加載,比如連接配接資料庫時使用JDBC。JDBC依賴具體的實作類,但是JDBC是上層類加載器加載完成的,他要加載下層類加載器才能加載的類是無法完成的,此時就需要在調用回使用者的代碼。

再來看書裡的舉出的一個例子JNDI服務,JNDI現在已經是Java的标準服務,它的代碼由啟動類加載器去加載(在JDK1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它需要調用由獨立廠商實作并部署在應用程式的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,啟動類加載器加載JNDI類時,根據全盤負責機制,還需要加載JNDI引用的ClassPath下的類,但是此時根據雙親委派機制同樣會造成無法加載。

為了解決上述問題,Java設計團隊隻好引入了一個不太優雅的設計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進行設定,如果建立線程時還未設定,它将會從父線程中繼承一個,如果在應用程式的全局範圍内都沒有設定過的話,那這個類加載器預設就是應用程式類加載器,此時便可以通過獲得線程上下文類加載器,進而使用應用程式類加載器加載ClassPath下的類,與此同時,雙親委派模型也被破壞了。

後面書中提到的:

如果有10個Web應用程式都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程式共享。Spring要對使用者程式的類進行管理,自然要能通路到使用者程式的類,而使用者的程式顯然是放在/WebApp/WEB-INF目錄中的,那麼被CommonClassLoader或SharedClassLoader加載的Spring如何通路并不在其加載範圍内的使用者程式呢?

這個應該也可以很容易明白Spring通過使用線程上下文類加載器的機制完成上述功能。