在接觸 OSGi 時,首先要掌握的概念之一就是“捆綁包”(bundle)。在這篇文章中,我想更詳細地探討一下捆綁包究竟是什麼,以及一個普通的 jar 檔案如何能被轉換成一個 OSGi 捆綁包。那麼,廢話不多說,
什麼是捆綁包?
OSGi 規範將捆綁包描述為“模組化的單元”,它“由 Java 類和其他資源組成,這些資源可以一起為終端使用者提供功能”。到目前為止都還好,但捆綁包究竟*是什麼*?再次引用規範:
捆綁包是一個 JAR 檔案,它
- 包含 [...] 資源
- 包含一個清單檔案,描述 JAR 檔案的內容並提供有關捆綁包的資訊
- 可以在 JAR 檔案內的 OSGI-OPT 目錄或其子目錄中包含可選文件
總之,捆綁包 = jar + OSGI 資訊(在 JAR 清單檔案 - META-INF/MANIFEST.MF 中指定),不需要額外的檔案或預定義的資料夾佈局。這意味著要從一個 jar 建立一個捆綁包,只需在 JAR 清單中新增一些條目即可。
OSGi 元資料
OSGi 元資料由清單條目表示,這些條目告訴 OSGi 框架捆綁包提供或/和需要什麼。規範中列出了大約 20 個清單頭,但我們將只關注你最有可能使用的那些。
Export-Package
顧名思義,此頭指定了捆綁包中要匯出的包,以便其他捆綁包可以匯入。*只有*此頭指定的包會被匯出,其餘的將是私有的,不會在包含捆綁包外部可見。
Import-Package
與 Export-Package 類似,此頭指定了由捆綁包匯入的包。同樣,*只有*此頭指定的包才會被匯入。預設情況下,匯入的包是強制性的——如果匯入的包不可用,匯入捆綁包將無法啟動。
Bundle-SymbolicName
這是唯一必需的頭,此條目指定了一個捆綁包的唯一識別符號,基於反向域名約定(也用於 Java 包)。
Bundle-Name
為此捆綁包定義一個不帶空格的可讀名稱。建議設定此頭,因為它比
Bundle-SymbolicName 能提供更短、更有意義的捆綁包內容資訊。
Bundle-Activator
BundleActivator 是一個 OSGi 特定的介面,允許 Java 程式碼在捆綁包被 OSGi 框架啟動或停止時收到通知。此頭的值應包含啟用器類的完全限定名,該類應為公共的幷包含一個沒有引數的公共建構函式。
Bundle-Classpath
當 jar 包含嵌入式庫或不同資料夾下的類包時,此頭非常有用,它可以擴充套件預設的捆綁包類路徑(預設情況下,類期望直接在 jar 根目錄下可用)。
Bundle-ManifestVersion
這個鮮為人知的頭指定了用於讀取此捆綁包的 OSGi 規範版本。
1 表示 OSGi release 3,而
2 表示 OSGi release 4 及更高版本。由於
1 是預設版本,強烈建議指定此頭,因為 OSGi release 4 捆綁包在 OSGi release 3 下可能無法按預期工作。
下面是一個示例,取自 Spring 2.5.x 核心捆綁包清單,使用了上面提到的一些頭。
Bundle-Name: spring-core
Bundle-SymbolicName: org.springframework.bundle.spring.core
Bundle-ManifestVersion: 2
Export-Package:org.springframework.core.task;uses:="org.springframework.core,org.springframework.util";version=2.5.1 org.springframework.core.type;uses:=org.springframework.core.annotation;version=2.5.1[...]
Import-Package:org.apache.commons.logging,edu.emory.mathcs.backport.java.util.concurrent;resolution:=optional[...]
在 OSGi 元資料上花費的大部分時間可能都在 Export/Import 包條目上,因為它們描述了捆綁包(即你的模組)之間的關係。對於包來說,沒有任何東西是隱式的——只有提到的包才會被匯入/匯出,其他的則不會。這同樣適用於子包:匯出 org.mypackage 只會匯出*此*包,而不會匯出其他任何東西(如 org.mypackage.util)。匯入也是如此——即使一個包在 OSGi 空間中可用,除非被某個捆綁包明確匯入,否則該捆綁包也看不到它。
總而言之,如果捆綁包 A 匯出了 org.mypackage 包,而捆綁包 B 想使用它,那麼捆綁包 A 的 META-INF/MANIFEST.MF 應該在其 Export-Package 頭中指定該包,而捆綁包 B 應該在其 Import-Package 條目中包含它。
包的考慮
雖然匯出相當直接,但匯入稍微複雜一些。應用程式通常會透過搜尋環境中可用的庫來優雅地降級,並僅使用可用的庫,或者庫會包含使用者未使用的程式碼。例如,日誌(使用 JDK 1.4 或 Log4j)、正則表示式(Jakarta ORO 或 JDK 1.4+)或併發實用程式(JDK 5 中的 java.util 或 JDK 1.4 的 backport-util-concurrent 庫)。
在 OSGi 術語中,根據包的可用性來依賴包相當於一個*可選*的 Package-Import。你在前面的示例中已經見過這樣的包
```code Import-Package: [...]edu.emory.mathcs.backport.java.util.concurrent;resolution:=optional ```
由於在 OSGi 中,同一類的多個版本可以存在,因此最好在匯出和匯入包時都指定類包的版本。這是透過新增到每個包聲明後的 version 屬性來完成的。OSGi 支援的版本格式為 <major>.<minor>.<micro>.<qualifier>,其中 major、minor 和 micro 是數字,而 qualifier 是字母數字。
版本*的含義*完全由捆綁包提供者決定,但是,建議使用流行的編號方案,例如 Apache APR 專案的方案,其中
- <major> - 表示不保證相容性的重大更新
- <minor> - 表示保留與舊 minor 版本相容的更新
- <micro> - 從使用者角度來看,表示微不足道的更新,與舊版本完全相容
- <qualifier> - 是使用者定義的字串 - 它不常用,可以為版本號提供一個額外的標籤,例如構建號或目標平臺,但沒有標準化含義。
預設版本(如果屬性缺失)是 "0.0.0"。
雖然匯出的包必須指定一個特定版本,但匯入者可以使用數學區間表示法來指定一個範圍——例如
[1.0.4, 2.0) 將匹配版本 1.0.42 及以上到 2.0(不包括)。請注意,僅指定版本而不是區間將匹配所有大於或等於指定版本的軟體包,即
Import-Package: com.mypackage;version="1.2.3"
等同於
Import-Package: com.mypackage;version="[1.2.3, ∞)"
最後一個提示是,無論版本是範圍還是單個版本,請*務必*在指定版本時使用引號。
使用 OSGi 元資料
現在我們已經瞭解了捆綁包是什麼,讓我們看看我們可以使用哪些工具來將現有的 jar 檔案 osgi 化。
手動
這種 DIY 方法並不推薦,因為很容易出現拼寫錯誤和多餘的空格,這可能會導致清單無效。即使使用智慧編輯器,清單格式本身也可能導致一些問題,因為它每行限制為 72 個空格,如果超過此限制,可能會導致難以理解的問題。手動建立或更新 jar 不是一個好主意,因為 jar 格式要求 META-INF/MANIFEST.MF 條目是歸檔檔案中的第一個條目——如果不是,即使它存在於 jar 中,清單檔案也不會被讀取。手動方法確實推薦用於沒有其他選擇的情況。
但是,如果有人確實想/需要直接處理清單,那麼應該使用一個可以處理 UNIX/DOS 空格的編輯器,並配合一個合適的 jar 建立實用程式(例如 JDK 自帶的 jar 工具)來處理所有 MANIFEST 要求。
Bnd
Bnd 代表 BuNDle tool,是 Peter Kriens(OSGi 技術官)建立的一個好用的工具,它“幫助 [...] 建立和診斷 OSGi R4 捆綁包”。Bnd 解析 Java 類以瞭解可用的和匯入的包,從而可以建立相應的 OSGi 條目。Bnd 提供一系列指令和選項,可以自定義生成的工件。bnd.jar 本身的好處在於它可以從 命令列執行,透過 Ant 的專用 任務執行,或者作為 外掛整合到 Eclipse 中。
Bnd 可以從類路徑或 Eclipse 專案中的類建立 jar,或者透過新增所需的 OSGi 工件來 osgi 化現有的 jar。此外,它可以列印和驗證給定 jar 的 OSGi 資訊,使其成為一個強大而易於使用的工具。
首次使用的使用者可以使用 Bnd 來檢視將新增到普通 jar 中的 OSGi 清單。讓我們選擇一個普通 jar,例如 c3p0(一個優秀的連線池庫),然後發出一個列印命令。
```code java -jar bnd.jar print c3p0-0.9.1.2.jar ```
輸出相當大,包含幾個部分。
- 通用清單資訊
[MANIFEST c3p0-0.9.1.2.jar]
Ant…