領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多如果您為SpringSource dm Server或任何其他OSGi平臺構建應用程式,您遲早會遇到“uses”指令。除非您清楚地理解該指令的目的,否則您將不知道何時編寫它,並且當某個bundle由於“uses”衝突而無法解析時,您將只能猜測。本文應讓您全面瞭解“uses”指令,何時使用它,以及如何除錯“uses”衝突。
OSGi 的設計宗旨是,一旦外掛“解析”完成,您通常就不會遇到由於型別不匹配而導致的類轉型異常等問題。這一點非常重要,因為 OSGi 為每個外掛使用一個類載入器,這為使用者暴露各種型別的型別不匹配提供了充足的機會。
必須理解的是,任何 Java 型別,例如類或介面,都必須由類載入器載入後才能在執行時使用。執行時型別由型別的完全限定類名和定義該型別的類載入器的組合來定義。如果相同的完全限定類名由兩個不同的類載入器定義,那麼這將產生兩個不相容的執行時型別。如果這兩個型別發生“接觸”,這種不相容性將導致執行時錯誤。例如,嘗試將其中一種型別強制轉換為另一種型別將導致類轉型異常。
OSGi 並不是唯一基於類載入器的 Java 模組系統,但它是迄今為止最成熟的。這意味著 OSGi 的設計者們已經深入思考了這類問題,並在 OSGi 規範中提供瞭解決方案。OSGi 的設計是透過一個稱為解析的過程,在應用程式程式碼執行之前就解決這些問題。解析類似於強型別程式語言(如 Java)中的編譯,因為它能在應用程式程式碼開始執行之前診斷出某些類別的錯誤。讓您的外掛能夠解析有時可能有點棘手,但這總比診斷類轉型異常等執行時錯誤要好。
那麼,解析外掛意味著什麼?這意味著要滿足外掛的依賴項,通常是透過查詢匯出給定外掛匯入的包的外掛,並滿足各種約束。最明顯的約束是每個匯出的包版本應位於匯入的包版本範圍內。另一種約束是可以在包匯入上指定任意屬性,這些屬性然後必須匹配相應的包匯出上的屬性。
我們稍後將介紹的“uses”約束旨在消除由一個包被多個外掛匯出而引起的型別不匹配問題。當一個外掛中的型別被用在需要另一個外掛中的型別的地方時,就會發生型別不匹配,因為執行時型別是不相容的。例如,當嘗試將一個外掛中的型別轉換為另一個外掛中具有相同類名的不同型別時,會發生類轉型異常。這怎麼會發生?由於一個外掛不能從多個外掛匯入同一個包,因此必須有其他方式使衝突的型別發生接觸。這發生在一個型別透過另一個包中的型別“傳遞”時。
型別可以透過另一種型別傳遞有兩種方式。第一種方式是一個型別顯式引用另一個型別。例如,`org.bar` 包中 `Bar` 型別的某個方法可能引用 `org.foo` 包中的 `Foo` 型別:`public Foo getFoo();`
型別可以透過另一種型別隱式傳遞的第二種方式是透過超型別。例如,方法的簽名可能引用一個超型別:`public Object getFoo();` 在隱式情況下,超型別的例項將在某個時候被強制轉換為衝突的型別。
這就是 Java 程式碼層面上發生型別不匹配的方式。讓我們看看外掛清單是什麼樣的。
必需的 `Foo` 型別可能由匯出 `org.bar` 包的同一外掛(`B`)匯出:`bundle-symbolicname: B bundle-manifestversion: 2 export-package: org.foo,org.bar` 或者由另一個外掛(`F`)匯出:`bundle-symbolicname: B bundle-manifestversion: 2 export-package: org.bar import-package: org.foo` `bundle-symbolicname: F bundle-manifestversion: 2 export-package: org.foo`
引入“uses”指令是為了讓 OSGi 在外掛解析期間能夠診斷上述型別的型別不匹配。
為了在解析期間檢測上述型別的潛在型別不匹配,Java 程式碼級別的顯式或隱式型別引用在相應的外掛清單中宣告。包含引用型別的包的匯出被標記為“uses”指令,該指令聲明瞭所引用型別的包。
在上面的示例中,`org.bar` 包的匯出被宣告為“uses”`org.foo` 包:“... export-package: org.bar;uses:="org.foo" ...” 請注意,在“uses”指令中命名的包,或者包,要被包含“uses”指令的外掛清單所匯出或匯入。因此,以下清單是有效的:“... export-package: p;uses:="q,r", q import-package: r ...” 而以下清單是無效的(因為它既沒有匯出也沒有匯入 `q` 包):“... export-package: p;uses:="q,r" import-package: r ...”
型別引用是傳遞的。例如,如果型別 `A` 引用型別 `B`,而 `B` 引用另一個型別 `C`,那麼 `A` 的使用者可以透過 `B` 獲取到 `C` 的引用。
由於型別引用是傳遞的,OSGi 會自動考慮這一點。它形成了所謂的“uses”指令的“傳遞閉包”。這意味著,只需為每個型別引用編寫一個“uses”指令就足夠了,OSGi 會處理傳遞型別引用。
例如,雖然外掛清單:“... export-package: p;uses:="q,r",q;uses:="r" import-package: r ...” 是正確的,但以下外掛清單足以捕獲從包 `p` 到 `q`、從 `q` 到 `r`,以及(傳遞地)從 `p` 到 `r` 的型別引用:“... export-package: p;uses:="q",q;uses:="r" import-package: r ...”
外掛解析過程旨在滿足所有約束,因此只有在無法滿足依賴項以遵守所有“uses”約束的情況下,它才會報告“uses”衝突。在這種情況下,SpringSource dm Server 發出的診斷有助於查明問題。
讓我們看一個虛構的例子來理解這個原理。假設我們正在開發幾個實用程式外掛 F 和 B,它們將由客戶端外掛 C 呼叫。假設我們為 F 引入了第二個版本,並嘗試在伺服器上部署具有以下清單的外掛。`Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: F Bundle-Version: 1 Bundle-Name: F Bundle Export-Package: org.foo;version=1` `Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: F Bundle-Version: 2 Bundle-Name: F Bundle Export-Package: org.foo;version=2` `Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: B Bundle-Version: 1 Bundle-Name: B Bundle Export-Package: org.bar;uses:="org.foo" Import-Package: org.foo;version="[1,2)"` `Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: C Bundle-Version: 1.0.0 Bundle-Name: C Bundle Import-Package: org.bar,org.foo;version="[2,3)"` 當我們嘗試部署外掛 C 時,dm Server 發出以下日誌訊息:“`
該行:“`Possible Supplier: B_1.0.0 - Export-Package: org.bar; version="0.0.0"`” 告訴我們當時正在考慮哪個 `org.bar` 的提供者。
該行:“`Possible Conflicts: org.foo`” 告訴我們哪個包違反了“uses”約束。
從細節中抽離出來,我們知道“uses”衝突是由於外掛 `C` 匯入的 `org.foo` 包版本與外掛 `B` 匯入的版本不同。一定有什麼原因導致使用了不同的版本,當我們仔細檢查包匯入時,我們意識到我們忘記將 `B` 升級到使用 `F` 的最新版本。
更新 `B` 的清單為:“`Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: B Bundle-Version: 1 Bundle-Name: B Bundle Export-Package: org.bar;uses:="org.foo" Import-Package: org.foo;version="[2,3)"`” 之後,我們現在可以成功部署外掛 `C`。
我們虛構的“uses”衝突足夠簡單,我們可以透過長時間檢視各種外掛清單來找到問題。對於更復雜的“uses”衝突,例如當“uses”指令中列出了許多可能衝突的包時,您可以透過使用 Equinox 控制檯(`telnet` 到埠 2401)來檢查已成功安裝的外掛(dm Server 會解除安裝無法成功部署的任何外掛),從而可能更快地取得進展。
您可以透過發出以下 Equinox 控制檯命令來獲取已安裝外掛的列表:“`osgi> ss`”,在我們虛構的問題中,它顯示如下:“`... 82 ACTIVE F_1.0.0 84 ACTIVE F_2.0.0 85 ACTIVE B_1.0.0`”
要顯示 `B` 的外掛清單,請發出:“`osgi> headers 85`”,要顯示 `org.foo` 包的匯入者和匯出者,請發出:“`osgi> packages org.foo`”
您可能會認為“uses”指令的麻煩大於它的價值,但考慮到跟蹤應用程式執行時中可能晦澀難懂的類轉型異常原因的替代方案,您應該開始理解“uses”的原理。實際上,任何沒有提供等同於“uses”約束的 Java 模組系統,一旦您編寫了多個版本的應用程式模組,很可能會在執行時出現類轉型異常。OSGi“uses”指令解決了 Java 模組化中的一個普遍問題。