Java雙親委派模型及破壞
阿新 • • 發佈:2019-02-10
另外一種就是其它所有的類載入器,這些類載入器都由Java語言實現,獨立於虛擬機器外部,並且全部繼承自java.lang.ClassLoader。
從Java開發人員的角度看,類載入器還可以劃分得更細一些,如下:
1.啟動類載入器(Bootstrap ClassLoader):這個類載入器負責將放置在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定路徑中的,並且是虛擬機器能識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫即使放置在lib目錄中也不會被載入)類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接使用。
2.擴充套件類載入器(Extension ClassLoader):這個類載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。
3.應用程式類載入器(Application ClassLoader):這個類載入器由sum.misc.Launcher.$AppClassLoader來實現。由於這個類載入器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也被稱為系統類載入器。它負責載入使用者類路徑上所指定的類庫,開發者可以直接使用這個類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
應用程式由這三種類載入器互相配合進行載入的,如果有必須,還可以加入自己定義的類載入器。這些類載入器之間的關係一般如下圖:
上圖中展示的類載入器之間的層次關係,就稱為類載入器的雙親委派模型(Parents Delegation Model)。雙親委派模型要求除了頂層的啟動類載入器之外,其餘的類載入器都應當有自己的父類載入器。這裡的類載入器之間的父子關係一般不會以繼承的關係來實現,而是使用組合關係來複用父載入器的程式碼。
雙親委派模型的式作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父載入器反饋自己無法完全這個載入請求時,子載入器才會嘗試自己去載入。
雙親委派模型的破壞
雙親委派模型的第一次“被破壞”其實發生在雙親委派模型出現之前--即JDK1.2釋出之前。由於雙親委派模型是在JDK1.2之後才被引入的,而類載入器和抽象類java.lang.ClassLoader則是JDK1.0時候就已經存在,面對已經存在 的使用者自定義類載入器的實現程式碼,Java設計者引入雙親委派模型時不得不做出一些妥協。為了向前相容,JDK1.2之後的java.lang.ClassLoader添加了一個新的proceted方法findClass(),在此之前,使用者去繼承java.lang.ClassLoader的唯一目的就是重寫loadClass()方法,因為虛擬在進行類載入的時候會呼叫載入器的私有方法loadClassInternal(),而這個方法的唯一邏輯就是去呼叫自己的loadClass()。JDK1.2之後已不再提倡使用者再去覆蓋loadClass()方法,應當把自己的類載入邏輯寫到findClass()方法中,在loadClass()方法的邏輯裡,如果父類載入器載入失敗,則會呼叫自己的findClass()方法來完成載入,這樣就可以保證新寫出來的類載入器是符合雙親委派模型的。
雙親委派模型的第二次“被破壞”是這個模型自身的缺陷所導致的,雙親委派模型很好地解決了各個類載入器的基礎類統一問題(越基礎的類由越上層的載入器進行載入),基礎類之所以被稱為“基礎”,是因為它們總是作為被呼叫程式碼呼叫的API。但是,如果基礎類又要呼叫使用者的程式碼,那該怎麼辦呢。
這並非是不可能的事情,一個典型的例子便是JNDI服務,它的程式碼由啟動類載入器去載入(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查詢,它需要呼叫獨立廠商實現部部署在應用程式的classpath下的JNDI介面提供者(SPI, Service Provider Interface)的程式碼,但啟動類載入器不可能“認識”之些程式碼,該怎麼辦?
為了解決這個困境,Java設計團隊只好引入了一個不太優雅的設計:執行緒上下檔案類載入器(Thread Context ClassLoader)。這個類載入器可以通過java.lang.Thread類的setContextClassLoader()方法進行設定,如果建立執行緒時還未設定,它將會從父執行緒中繼承一個;如果在應用程式的全域性範圍內都沒有設定過,那麼這個類載入器預設就是應用程式類載入器。了有執行緒上下文類載入器,JNDI服務使用這個執行緒上下文類載入器去載入所需要的SPI程式碼,也就是父類載入器請求子類載入器去完成類載入動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類載入器,已經違背了雙親委派模型,但這也是無可奈何的事情。Java中所有涉及SPI的載入動作基本上都採用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。
雙親委派模型的第三次“被破壞”是由於使用者對程式的動態性的追求導致的,例如OSGi的出現。在OSGi環境下,類載入器不再是雙親委派模型中的樹狀結構,而是進一步發展為網狀結構。
從Java開發人員的角度看,類載入器還可以劃分得更細一些,如下:
1.啟動類載入器(Bootstrap ClassLoader):這個類載入器負責將放置在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定路徑中的,並且是虛擬機器能識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫即使放置在lib目錄中也不會被載入)類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接使用。
2.擴充套件類載入器(Extension ClassLoader):這個類載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。
3.應用程式類載入器(Application ClassLoader):這個類載入器由sum.misc.Launcher.$AppClassLoader來實現。由於這個類載入器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也被稱為系統類載入器。它負責載入使用者類路徑上所指定的類庫,開發者可以直接使用這個類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
應用程式由這三種類載入器互相配合進行載入的,如果有必須,還可以加入自己定義的類載入器。這些類載入器之間的關係一般如下圖:
上圖中展示的類載入器之間的層次關係,就稱為類載入器的雙親委派模型(Parents Delegation Model)。雙親委派模型要求除了頂層的啟動類載入器之外,其餘的類載入器都應當有自己的父類載入器。這裡的類載入器之間的父子關係一般不會以繼承的關係來實現,而是使用組合關係來複用父載入器的程式碼。
雙親委派模型的式作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父載入器反饋自己無法完全這個載入請求時,子載入器才會嘗試自己去載入。
雙親委派模型的破壞
雙親委派模型的第一次“被破壞”其實發生在雙親委派模型出現之前--即JDK1.2釋出之前。由於雙親委派模型是在JDK1.2之後才被引入的,而類載入器和抽象類java.lang.ClassLoader則是JDK1.0時候就已經存在,面對已經存在 的使用者自定義類載入器的實現程式碼,Java設計者引入雙親委派模型時不得不做出一些妥協。為了向前相容,JDK1.2之後的java.lang.ClassLoader添加了一個新的proceted方法findClass(),在此之前,使用者去繼承java.lang.ClassLoader的唯一目的就是重寫loadClass()方法,因為虛擬在進行類載入的時候會呼叫載入器的私有方法loadClassInternal(),而這個方法的唯一邏輯就是去呼叫自己的loadClass()。JDK1.2之後已不再提倡使用者再去覆蓋loadClass()方法,應當把自己的類載入邏輯寫到findClass()方法中,在loadClass()方法的邏輯裡,如果父類載入器載入失敗,則會呼叫自己的findClass()方法來完成載入,這樣就可以保證新寫出來的類載入器是符合雙親委派模型的。
雙親委派模型的第二次“被破壞”是這個模型自身的缺陷所導致的,雙親委派模型很好地解決了各個類載入器的基礎類統一問題(越基礎的類由越上層的載入器進行載入),基礎類之所以被稱為“基礎”,是因為它們總是作為被呼叫程式碼呼叫的API。但是,如果基礎類又要呼叫使用者的程式碼,那該怎麼辦呢。
這並非是不可能的事情,一個典型的例子便是JNDI服務,它的程式碼由啟動類載入器去載入(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查詢,它需要呼叫獨立廠商實現部部署在應用程式的classpath下的JNDI介面提供者(SPI, Service Provider Interface)的程式碼,但啟動類載入器不可能“認識”之些程式碼,該怎麼辦?
為了解決這個困境,Java設計團隊只好引入了一個不太優雅的設計:執行緒上下檔案類載入器(Thread Context ClassLoader)。這個類載入器可以通過java.lang.Thread類的setContextClassLoader()方法進行設定,如果建立執行緒時還未設定,它將會從父執行緒中繼承一個;如果在應用程式的全域性範圍內都沒有設定過,那麼這個類載入器預設就是應用程式類載入器。了有執行緒上下文類載入器,JNDI服務使用這個執行緒上下文類載入器去載入所需要的SPI程式碼,也就是父類載入器請求子類載入器去完成類載入動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類載入器,已經違背了雙親委派模型,但這也是無可奈何的事情。Java中所有涉及SPI的載入動作基本上都採用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。
雙親委派模型的第三次“被破壞”是由於使用者對程式的動態性的追求導致的,例如OSGi的出現。在OSGi環境下,類載入器不再是雙親委派模型中的樹狀結構,而是進一步發展為網狀結構。