1. 程式人生 > >Java類載入器ClassLoader總結

Java類載入器ClassLoader總結

  OSGi是 Java 上的動態模組系統。它為開發人員提供了面向服務和基於元件的執行環境,並提供標準的方式用來管理軟體的生命週期。OSGi 已經被實現和部署在很多產品上,在開源社群也得到了廣泛的支援。Eclipse就是基於OSGi 技術來構建的。   OSGi 中的每個模組(bundle)都包含 Java 包和類。模組可以宣告它所依賴的需要匯入(import)的其它模組的 Java 包和類(通過 Import-Package),也可以宣告匯出(export)自己的包和類,供其它模組使用(通過 Export-Package)。也就是說需要能夠隱藏和共享一個模組中的某些 Java 包和類。這是通過 OSGi 特有的類載入器機制來實現的。OSGi 中的每個模組都有對應的一個類載入器。它負責載入模組自己包含的 Java 包和類。當它需要載入 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類載入器(通常是啟動類載入器)來完成。當它需要載入所匯入的 Java 類時,它會代理給匯出此 Java 類的模組來完成載入。
模組也可以顯式的宣告某些 Java 包和類,必須由父類載入器來載入。只需要設定系統屬性 org.osgi.framework.bootdelegation的值即可。   假設有兩個模組 bundleA 和 bundleB,它們都有自己對應的類載入器 classLoaderA 和 classLoaderB。在 bundleA 中包含類 com.bundleA.Sample,並且該類被宣告為匯出的,也就是說可以被其它模組所使用的。bundleB 聲明瞭匯入 bundleA 提供的類 com.bundleA.Sample,幷包含一個類 com.bundleB.NewSample繼承自 com.bundleA.Sample。在 bundleB 啟動的時候,其類載入器 classLoaderB 需要載入類 com.bundleB.NewSample,進而需要載入類 com.bundleA.Sample。由於 bundleB 聲明瞭類 com.bundleA.Sample是匯入的,classLoaderB 把載入類 com.bundleA.Sample的工作代理給匯出該類的 bundleA 的類載入器 classLoaderA。classLoaderA 在其模組內部查詢類 com.bundleA.Sample並定義它,所得到的類 com.bundleA.Sample例項就可以被所有宣告匯入了此類的模組使用。對於以 java開頭的類,都是由父類載入器來載入的。如果聲明瞭系統屬性 org.osgi.framework.bootdelegation=com.example.core.*,那麼對於包 com.example.core中的類,都是由父類載入器來完成的。   OSGi 模組的這種類載入器結構,使得一個類的不同版本可以共存在 Java 虛擬機器中,帶來了很大的靈活性。
不過它的這種不同,也會給開發人員帶來一些麻煩,尤其當模組需要使用第三方提供的庫的時候。下面提供幾條比較好的建議:   (1)如果一個類庫只有一個模組使用,把該類庫的 jar 包放在模組中,在 Bundle-ClassPath中指明即可。   (2)如果一個類庫被多個模組共用,可以為這個類庫單獨的建立一個模組,把其它模組需要用到的 Java 包宣告為匯出的。其它模組宣告匯入這些類。   (3)如果類庫提供了 SPI 介面,並且利用執行緒上下文類載入器來載入 SPI 實現的 Java 類,有可能會找不到 Java 類。如果出現了 NoClassDefFoundError異常,首先檢查當前執行緒的上下文類載入器是否正確。通過 Thread.currentThread().getContextClassLoader()就可以得到該類載入器。該類載入器應該是該模組對應的類載入器。如果不是的話,可以首先通過 class.getClassLoader()來得到模組對應的類載入器,再通過 Thread.currentThread().setContextClassLoader()來設定當前執行緒的上下文類載入器。