在接觸 OSGi 時,首先必須學習的概念之一就是bundle的概念。在這篇文章中,我想仔細看看 bundle 到底是什麼,以及一個普通的 jar 如何轉換為 OSGi bundle。 那麼,話不多說,
什麼是 bundle?
OSGi 規範將 bundle 描述為“模組化單元”,它“由 Java 類和其他資源組成,這些資源共同可以為終端使用者提供功能。”到目前為止還好,但 bundle 究竟是什麼?再次引用規範
一個 bundle 是一個 JAR 檔案,它
- 包含 [...] 資源
- 包含一個 manifest 檔案,描述 JAR 檔案的內容並提供關於 bundle 的資訊
- 可以在 JAR 檔案的 OSGI-OPT 目錄或其子目錄中包含可選文件
簡而言之,一個 bundle = jar + OSGi 資訊(在 JAR manifest 檔案 - META-INF/MANIFEST.MF 中指定),不需要額外的檔案或預定義的資料夾佈局。這意味著從一個 jar 建立一個 bundle 所需的全部工作就是向 JAR manifest 新增一些條目。
OSGi 元資料
OSGi 元資料由 manifest 條目表示,這些條目向 OSGi 框架指示 bundle 提供和/或需要什麼。規範指出了大約 20 個 manifest 頭,但我們只會看一些您最有可能使用的頭。
Export-Package
顧名思義,此頭指示匯出哪些(在 bundle 中可用的)包,以便其他 bundle 可以匯入它們。只有此頭指定的包會被匯出,其餘的將是私有的,並且在包含此 bundle 的外部不可見。
Import-Package
與 Export-Package 類似,此頭指示 bundle 匯入的包。同樣,只有此頭指定的包會被匯入。預設情況下,匯入的包是強制性的——如果匯入的包不可用,匯入的 bundle 將無法啟動。
Bundle-SymbolicName
唯一的必選頭,此條目根據反向域名約定(Java 包也使用此約定)為 bundle 指定一個唯一的識別符號。
Bundle-Name
為此 bundle 定義一個人類可讀的名稱,不含空格。建議設定此頭,因為它比
Bundle-SymbolicName 能提供更短、更有意義的關於 bundle 內容的資訊。
Bundle-Activator
該
BundleActivator 是一個 OSGi 特定的介面,它允許 Java 程式碼在 bundle 由 OSGi 框架啟動或停止時接收通知。此頭的值應包含啟用器類的完全限定名,該類應是公共的且包含一個不帶任何引數的公共建構函式。
Bundle-Classpath
當 jar 包含各種資料夾下的嵌入式庫或類包時,此頭非常方便,它透過擴充套件預設的 bundle 類路徑(預設類路徑期望類直接位於 jar 根目錄下)來實現。
Bundle-ManifestVersion
這個鮮為人知的頭指示用於讀取此 bundle 的 OSGi 規範版本。
1 表示 OSGi Release 3,而
2 表示 OSGi Release 4 及更高版本。由於
1 是預設版本,強烈建議指定此頭,因為 OSGi Release 4 的 bundle 在 OSGi Release 3 下無法按預期工作。
下面是一個示例,取自 Spring 2.5.x 核心 bundle 的 manifest 檔案,使用了上面提到的一些頭
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 包條目,因為它們描述了 bundle 之間的關係(即您的模組之間的關係)。對於包來說,沒有任何隱式規則——只有提及的包才會被匯入/匯出,其餘的則不會。這也適用於子包:匯出 org.mypackage 將只匯出這個包,而不是其他任何東西(例如 org.mypackage.util)。匯入也是一樣——即使一個包在 OSGi 空間中可用,除非某個 bundle 明確匯入它,否則它將不可見。
總結一下,如果 bundle A 匯出包 org.mypackage 並且 bundle B 想要使用它,那麼 bundle A 的 META-INF/MANIFEST.MF 應在其 Export-Package 頭中指定該包,而 bundle 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 是字母數字。
該版本含義完全由 bundle 提供者決定,但建議使用流行的編號方案,例如 Apache APR 專案的方案,其中
- <major> - 表示重大更新,不保證相容性
- <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 元資料
既然我們已經瞭解了 bundle 的一些資訊,接下來看看可以使用哪些工具來將現有 jar OSGi 化
手動方式
不建議採用這種手動方式,因為很容易出現打字錯誤和多餘空格,導致 manifest 檔案失效。即使使用智慧編輯器,manifest 格式本身也可能引發一些問題,因為它規定每行最多 72 個空格,如果超出則可能導致一些難以理解的問題。手動建立或更新 jar 也不是一個好主意,因為 jar 格式要求 META-INF/MANIFEST.MF 條目必須是歸檔中的第一個——如果不是,即使它存在於 jar 中,manifest 檔案也不會被讀取。手動方式只在沒有其他替代方案的情況下才真正推薦使用。
然而,如果確實想要/需要直接處理 manifest 檔案,則應使用能夠處理 UNIX/DOS 空格的編輯器,並結合適當的 jar 建立工具(例如 JDK 自帶的 jar 工具),以滿足所有 MANIFEST 要求。
Bnd
Bnd 是 BuNDle tool 的縮寫,是一個由 Peter Kriens(OSGi 技術官員)建立的實用工具,它“幫助 [...] 建立和診斷 OSGi R4 bundle”。Bnd 解析 Java 類以瞭解可用和匯入的包,從而建立相應的 OSGi 條目。Bnd 提供了一系列指令和選項,可以自定義生成的 artifact。bnd.jar 本身的優點在於它可以從命令列執行,透過 Ant 的專用任務執行,或作為外掛整合到 Eclipse 中。
Bnd 可以從類路徑中或 Eclipse 專案內的類建立 jar,也可以透過新增所需的 OSGi artifacts 來將現有 jar OSGi 化。此外,它可以列印和驗證給定 jar 的 OSGi 資訊,使其成為一個功能強大但易於使用的工具。
初次使用的使用者可以使用 Bnd 檢視普通 jar 會被新增哪些 OSGi manifest 條目。讓我們選擇一個普通 jar,例如 c3p0(這是一個出色的連線池庫),然後執行列印命令
```code java -jar bnd.jar print c3p0-0.9.1.2.jar ```
輸出內容相當多,包含多個部分
- 通用 manifest 資訊
[MANIFEST c3p0-0.9.1.2.jar]
Ant…