理解 OSGi uses 指令

工程 | Glyn Normington | 2008 年 10 月 20 日 | ...

如果您為 SpringSource dm Server 或任何其他 OSGi 平臺構建應用程式,您可能很快就會遇到 uses 指令。除非您對該指令的目的有清晰的瞭解,否則您將不知道何時編寫它,並且當捆綁包因 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 能夠在捆綁包解析期間診斷上述型別的型別不匹配問題。

uses 指令

為了在解析期間檢測上述型別的潛在型別不匹配問題,在相應的捆綁包清單檔案中聲明瞭 Java 程式碼層面的顯式或隱式型別引用。包含引用型別的包的匯出被標記了一個 uses 指令,該指令聲明瞭被引用型別的包。

在上述示例中,org.bar 包的匯出被宣告為 使用 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 ...

傳遞性 uses

型別引用是傳遞的。例如,如果型別 A 引用型別 B,而 B 又引用另一個型別 C,那麼 A 的使用者可以透過 B 獲取對 C 的引用。

由於型別引用是傳遞的,OSGi 會自動考慮這一點。它形成了所謂的 uses 指令的傳遞閉包。這意味著為每個型別引用編寫一個 uses 指令就足夠了,OSGi 會處理傳遞性型別引用。

例如,儘管捆綁包清單檔案: ... export-package: p;uses:="q,r",q;uses:="r" import-package: r ... 是正確的,但以下捆綁包清單檔案足以捕獲從包 pq、從 qr,以及(傳遞地)從 pr 的型別引用: ... export-package: p;uses:="q",q;uses:="r" import-package: r ...

診斷 uses 衝突

捆綁包解析過程旨在滿足所有約束,因此只有當它無法以滿足所有 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 發出以下日誌訊息: <SPDE0018E> Unable to install application from location 'file:/xxx/C.jar/'. Could not satisfy constraints for bundle 'C' at version '1.0.0'. Cannot resolve: C Resolver report: Bundle: C_1.0.0 - Uses Conflict: Import-Package: org.bar; version="0.0.0" Possible Supplier: B_1.0.0 - Export-Package: org.bar; version="0.0.0" Possible Conflicts: org.foo . 行: Bundle: C_1.0.0 - Uses Conflict: Import-Package: org.bar; version="0.0.0" 告訴我們存在與捆綁包 C 匯入 org.bar 包相關的 uses 約束違例。換句話說,C 試圖使用的 org.bar 的匯出具有無法滿足的 uses 約束。

行: 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 衝突,例如 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 指令的需求,它如何用於提供對某一類型別不匹配錯誤的早期診斷,以及如何使用 dm Server 診斷和 Equinox 控制檯來追查 uses 約束違例的根本原因。

您可能認為 uses 指令帶來的麻煩多於其價值,但當您考慮另一種情況,即在應用程式執行時追查可能難以察覺的類轉換異常的原因時,您就會開始看到 uses 的基本原理。實際上,任何沒有提供等效 uses 約束的 Java 模組系統,一旦您的應用程式模組有了多個版本,很可能就會在執行時出現類轉換異常。OSGi uses 指令解決了 Java 模組化的一個普遍問題。

獲取 Spring 時事通訊

訂閱 Spring 時事通訊,保持聯絡

訂閱

先行一步

VMware 提供培訓和認證,助您加速前進。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單訂閱。

瞭解更多

即將到來的活動

檢視 Spring 社群所有即將到來的活動。

檢視全部