了解 OSGi 的 uses 指令

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

如果您為 SpringSource dm Server 或任何其他 OSGi 平台建置應用程式,您很快就會遇到 uses 指令。 除非您清楚了解該指令的目的,否則您不會知道何時該編寫它,並且當 bundle 因 uses 衝突而無法解析時,您會開始猜測。 本文應該讓您徹底了解 uses 指令、何時使用它以及如何偵錯 uses 衝突。

Bundle 解析

OSGi 的設計是,一旦 bundle 被「解析」後,您通常不應該遇到因類型不符而導致的類別轉換例外或其他類似問題。 這非常重要,因為 OSGi 為每個 bundle 使用一個類別載入器,因此有充分的機會讓使用者接觸到各種類型不符的情況。

務必了解,任何 Java 類型,例如類別或介面,都必須由類別載入器載入,才能在執行階段使用。 執行階段類型由類型的完整類別名稱和定義該類型的類別載入器組合定義。 如果同一個完整類別名稱由兩個不同的類別載入器定義,則會產生兩個不相容的執行階段類型。 如果這兩個類型發生「接觸」,這種不相容會導致執行階段錯誤。 例如,嘗試將其中一個類型轉換為另一個類型將導致類別轉換例外。

OSGi 並非唯一基於類別載入器的 Java 模組系統,但它是目前最成熟的。 這意味著 OSGi 的設計者已經針對這些問題進行了長時間的深入思考,並將解決方案納入 OSGi 規範中。 OSGi 的設計是在應用程式程式碼執行之前,透過稱為解析的過程來消除這些問題。 解析類似於強類型程式語言(例如 Java)中的編譯,某些類別的問題會在應用程式程式碼開始執行之前就被診斷出來。 讓您的 bundle 解析有時會有點令人頭痛,但它勝過診斷執行階段錯誤,例如類別轉換例外。

那麼,解析 bundle 意味著什麼? 這意味著滿足 bundle 的依賴關係,通常是透過尋找匯出給定 bundle 匯入的套件並滿足各種約束的 bundle。 最明顯的約束是每個匯出套件的版本都應該位於套件匯入的套件版本範圍內。 另一種約束是可以在套件匯入上指定任意屬性,然後這些屬性必須與相應的套件匯出上的屬性相符。

由多個 Bundle 匯出的套件

我們很快將介紹的 uses 約束旨在消除由多個 bundle 匯出套件所導致的一類類型不符。 當一個 bundle 中的類型在需要另一個 bundle 中的類型時使用時,會發生類型不符,因為執行階段類型不相容。 例如,當嘗試使用來自另一個 bundle 的相同類別名稱的不同類型轉換一個 bundle 中的類型時,會發生類別轉換例外。 這種情況是如何發生的? 由於一個 bundle 不能從多個 bundle 匯入同一個套件,因此必須有其他方法讓衝突的類型發生接觸。 它透過類型被「傳遞」到另一個套件中的類型來發生。

類型可以透過兩種方式傳遞到另一個類型。 第一種方式是一個類型明確地引用另一個類型。 例如,org.bar 套件中 Bar 類型的方法可能會引用 org.foo 套件中的 Foo 類型: public Foo getFoo();

類型可以透過另一種類型傳遞的第二種方式是隱含地,透過超類型。 例如,方法的簽名可能會引用超類型: public Object getFoo(); 在隱含的情況下,超類型的實例將在某個時候被轉換為衝突類型。

這就是在 Java 程式碼層級發生這種類型不符的方式。 讓我們看看 bundle 資訊清單的樣子。

所需的類型 Foo 可能由匯出 org.bar 套件的同一個 bundle (B) 匯出: bundle-symbolicname: B bundle-manifestversion: 2 export-package: org.foo,org.bar 或由另一個 bundle (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 能夠在 bundle 解析期間診斷上述類型的類型不符。

uses 指令

為了在解析期間檢測上述類型的潛在類型不符,在對應的 bundle 資訊清單中宣告了 Java 程式碼層級的明確或隱含類型引用。 包含引用類型的套件的匯出會被標記為 uses 指令,該指令宣告了被引用類型的套件。

在上面的範例中,宣告 org.bar 套件的匯出 使用 org.foo 套件: ... export-package: org.bar;uses:="org.foo" ... 請注意,uses 指令中命名的套件,或多個套件,是由包含 uses 指令的 bundle 資訊清單匯出或匯入的。 因此,以下資訊清單是有效的: ... 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 將會處理傳遞性類型引用。

例如,雖然 bundle 資訊清單: ... export-package: p;uses:="q,r",q;uses:="r" import-package: r ... 是正確的,但以下 bundle 資訊清單足以捕獲從套件 pq、從 qr 以及(傳遞地)從 pr 的類型引用: ... export-package: p;uses:="q",q;uses:="r" import-package: r ...

診斷 uses 衝突

Bundle 解析過程旨在滿足所有約束,因此只有在無法滿足依賴關係以遵守所有 uses 約束時,才會報告 uses 衝突。 SpringSource dm Server 在這些情況下發出的診斷有助於精確找出問題。

讓我們看一個虛構的例子來理解這個原理。 假設我們正在開發幾個實用程式 bundle F 和 B,它們將從用戶端 bundle C 中調用。假設我們引入了第二個版本的 F,並且嘗試在伺服器上部署具有以下資訊清單的 bundle。 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)" 當我們嘗試部署 bundle C 時,dm Server 會發出以下日誌訊息: <SPDE0018E> 無法從位置 'file:/xxx/C.jar/' 安裝應用程式。 無法滿足 bundle 'C'(版本 '1.0.0')的約束。 無法解析:C 解析器報告:Bundle: C_1.0.0 - Uses 衝突:Import-Package: org.bar; version="0.0.0" 可能的提供者:B_1.0.0 - Export-Package: org.bar; version="0.0.0" 可能的衝突:org.foo . 行: Bundle: C_1.0.0 - Uses 衝突:Import-Package: org.bar; version="0.0.0" 告訴我們,存在與 bundle 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 衝突是因為 bundle C 匯入了與 bundle B 匯入的版本不同的 org.foo 套件。 肯定有什麼原因導致使用了不同的版本,當我們仔細檢查套件匯入時,我們意識到我們忘記升級 B 以使用最新版本的 F

更新 B 的 manifest 如下: 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)" 後,我們現在可以成功部署 bundle C

診斷複雜的 uses 衝突

我們虛構的 uses 衝突非常簡單,我們可以通過長時間盯著各種 bundle manifest 來找到問題。 對於更複雜的 uses 衝突,例如當 uses 指令中列出了許多可能衝突的套件時,您可以使用 Equinox 主控台(telnet 到端口 2401)來檢查已成功安裝的 bundle,從而更快地取得進展(dm Server 會解除安裝任何無法成功部署的 bundle)。

您可以通過發出以下 Equinox 主控台指令來獲取已安裝的 bundle 清單: osgi> ss 在我們虛構的問題中,它會顯示如下內容: ... 82 ACTIVE F_1.0.0 84 ACTIVE F_2.0.0 85 ACTIVE B_1.0.0

要顯示 B 的 bundle manifest,請發出指令: 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 社群中所有即將舉行的活動。

查看所有