建立 OSGi 綁定 (bundle)

工程 | Costin Leau | 2008 年 2 月 18 日 | ...

當接觸 OSGi 時,首先要學習的概念之一就是綁定 (bundle) 的概念。 在這篇文章中,我想更仔細地看看綁定實際上是什麼,以及如何將一個普通的 jar 轉換為 OSGi 綁定。 那麼,事不宜遲,

什麼是綁定 (bundle)?

OSGi 規範將綁定描述為「模組化的單元」,它「由 Java 類別和其他資源組成,它們可以共同為最終用戶提供功能」。 到目前為止還不錯,但綁定究竟是什麼? 再次引用規範

綁定是一個 JAR 檔案,它

  • 包含 [...] 資源
  • 包含一個描述 JAR 檔案內容並提供有關綁定資訊的 manifest 檔案
  • 可以在 JAR 檔案的 OSGI-OPT 目錄或其子目錄之一中包含可選的文件

簡而言之,綁定 = jar + OSGI 資訊 (在 JAR manifest 檔案中指定 - META-INF/MANIFEST.MF),不需要額外的檔案或預定義的資料夾佈局。 這意味著從 jar 建立綁定所需的一切,就是向 JAR manifest 添加一些條目。

OSGi 元數據

OSGi 元數據由 manifest 條目表示,這些條目指示 OSGi 框架綁定提供或/和需要什麼。 規範指示了大約 20 個 manifest 標頭,但我們只會看看您最有可能使用的那些。

Export-Package

顧名思義,此標頭指示匯出哪些套件 (在綁定中可用),以便它們可以被其他綁定匯入。 只有此標頭指定的套件才會被匯出,其餘的將是私有的,並且不會在包含綁定的外部看到。

Import-Package

Export-Package 類似,此標頭指示綁定匯入的套件。 同樣地,只有此標頭指定的套件才會被匯入。 預設情況下,匯入的套件是強制性的 - 如果匯入的套件不可用,則匯入綁定將無法啟動。

Bundle-SymbolicName
唯一的必需標頭,此條目根據反向網域名稱慣例 (java 套件也使用) 指定綁定的唯一識別碼。
Bundle-Name
為此綁定定義一個人類可讀的名稱,沒有空格。 建議設定此標頭,因為它可以提供比 Bundle-SymbolicName 更短、更有意義的有關綁定內容的資訊。
Bundle-Activator
BundleActivator 是一個 OSGi 專用介面,允許在 OSGi 框架啟動或停止綁定時通知 Java 程式碼。 此標頭的值應包含啟動器類別的完整名稱,該類別應為 public 且包含一個沒有任何引數的 public 建構子。
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 core 綁定 manifest,該 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 套件條目上,因為它們描述了綁定之間的關係 (即模組之間的關係)。 說到套件,沒有任何隱含的內容 - 只有提到的套件才會被匯入/匯出,其餘的則不會。 這也適用於子套件:匯出 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>,其中 majorminormicro 是數字,而 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 元數據

現在我們有了一些關於綁定是什麼的資訊,讓我們看看我們可以使用哪些工具來 osgi-fying 現有的 jar

手動

不鼓勵使用這種自己動手的方法,因為排版錯誤和額外的空格很容易潛入並使 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 綁定」。 Bnd 解析 java 類別以了解可用的和匯入的套件,因此它可以建立等效的 OSGi 條目。 Bnd 提供了一系列指令和選項,可以自訂產生的成品。 bnd.jar 本身的好處是可以從 命令列、透過專用的 tasks 由 Ant 執行,或整合到 Eclipse 作為 外掛程式

Bnd 可以從類別路徑或 Eclipse 專案中可用的類別建立 jar,或者可以透過新增所需的 OSGi 成品來 osgi-fy 現有的 jar。 此外,它可以列印和驗證有關給定 jar 的 OSGi 資訊,使其成為一個非常強大但易於使用的工具。

第一次使用的使用者可以使用 Bnd 來查看將哪些 OSGi manifest 新增到普通的 jar。 讓我們選擇一個普通的 jar,例如 c3p0 (這是一個很棒的連線池函式庫) 並發出一個 print 命令

```code java -jar bnd.jar print c3p0-0.9.1.2.jar ```

輸出非常大,包含幾個部分

  1. 通用 manifest 資訊
    [MANIFEST c3p0-0.9.1.2.jar]
    Ant-Version Apache Ant 1.7.0
    Created-By 1.5.0_07-87 ("Apple Computer, Inc.")
    Extension-Name com.mchange.v2.c3p0
    Implementation-Vendor Machinery For Change, Inc.
    Implementation-Vendor-Id com.mchange
    Implementation-Version 0.9.1.2
    Manifest-Version 1.0
    Specification-Vendor Machinery For Change, Inc.
    Specification-Version 1.0
    
  2. 套件資訊
    
    com.mchange.v2.c3p0.management   com.mchange.v1.lang com.mchange.v2.c3p0
                                                                   com.mchange.v2.c3p0.impl com.mchange.v2.debug
                                                                   com.mchange.v2.log com.mchange.v2.management
                                                                   java.sql
                                                                   javax.management
                                                                   javax.sql
    

    指示指定在 jar 中發現的套件 (在左側) 及其匯入 (在右側)。

  3. 可能的錯誤 - 通常這些指示在類別路徑中未找到但被其他類別引用的套件
     One error 1 : Unresolved references to 
    [javax.management, javax.naming, javax.naming.spi, javax.sql, javax.xml.parsers, org.apache.log4j, org.w3c.dom] 
    by class(es) on the Bundle-Classpath[Jar:c3p0-0.9.1.2.jar]: [...] 
    

    。 本節很好地說明了給定的 jar 匯入哪些套件。

讓我們使用以下命令來 OSGify 成品

java -jar bnd.jar wrap c3p0-0.9.1.2.jar 

這將會建立一個新的封存檔,其內容與原始的 jar 檔完全相同,但 MANIFEST.MF 會被修改,並包含標記為可選的 OSGi 匯入項目。目前的 Bnd 工具會將封存檔儲存為副檔名為 .jar$ 的檔案,而先前的版本則使用 .bar 作為副檔名。

我們可能會選擇透過新增版本資訊、排除某些匯出的套件,以及將某些匯入的套件標記為強制性(例如本例中的 javax.sql),來調整 jar 檔。為此,我們將建立一個 c3p0-0.9.1.2.bnd 檔案,如下所示:

version=0.9.1.2
Export-Package: com.mchange*;version=${version}
Import-Package: java.sql*,javax.sql*,*;resolution:=optional
Bundle-Version: ${version}
Bundle-Description: c3p0 connection pool
Bundle-Name: c3p0

請注意,對於版本,我們使用了變數替換。若要導入屬性檔案,請使用下列命令行:

```code java -jar bnd.jar wrap -properties c3p0-0.9.1.2.bnd c3p0-0.9.1.2.jar ```

我使用了 .bnd 副檔名,因為預設情況下,Bnd ant 任務會在執行期間尋找此檔案。

若要在 ant 中使用 Bnd 工具,只需匯入現成的任務,並在 jar 檔案建立期間調用它們即可。


<taskdef resource="aQute/bnd/ant/taskdef.properties" classpath="${lib.dir}/bnd/bnd.jar"/>
...
<bndwrap definitions="${basedir}/osgi/bnd" output="${dist.dir}">
   <fileset dir="${dist.dir}" includes="*.jar"/>    
</bndwrap>

請注意,通常會執行一個移動任務,將 .jar$ 或 .bar 成品複製到原始 jar 檔上。

Maven 的套件外掛程式

對於 Maven,Apache Felix Bundle Plug-in 提供了 Bnd 和 Maven 2 之間的良好整合。由於 Maven POM 包含有關專案的其他資訊,因此 Bnd 外掛程式可以使用專案屬性自動填寫 manifest 的其他欄位,例如 Bundle-License 或 Bundle-Version。

官方文件詳細說明了其用法,因此我在此不再重複。

為了轉換我們的 c3p0 函式庫,我將使用一個簡單的 Maven 2 pom,它將下載原始成品,然後將其包裝為一個套件。


<?xml version="1.0" encoding="UTF-8"?>
<project
        xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>my.company</groupId>
    <artifactId>c3p0.osgi</artifactId>
    <packaging>bundle</packaging>
    <version>0.9.1.2-SNAPSHOT</version>
    <name>c3p0.osgi</name>

    <properties>
        <export.packages>${export.package}*;version=${unpack.version}</export.packages>
        <import.packages>*</import.packages>
        <private.packages>!*</private.packages>
        <symbolic.name>${pom.groupId}.${pom.artifactId}</symbolic.name>
        <embed-dep>*;scope=provided;type=!pom;inline=true</embed-dep>
        <unpack-bundle>false</unpack-bundle>
    </properties>

    <build>
    <plugins>
     <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>1.2.0</version>
        <configuration>
            <unpackBundle>${unpack.bundle}</unpackBundle>
            <instructions>
                <Bundle-Name>${artifactId}</Bundle-Name>
                <Bundle-SymbolicName>${symbolic.name}</Bundle-SymbolicName>
                <Bundle-Description>${pom.name}</Bundle-Description>
                <Import-Package>${import.packages}</Import-Package>
                <Private-Package>${private.packages}</Private-Package>
                <Include-Resource>${include.resources}</Include-Resource>
                <Embed-Dependency>${embed-dep}</Embed-Dependency>
                <_exportcontents>${export.packages}</_exportcontents>
            </instructions>
        </configuration>
        <extensions>true</extensions>
     </plugin>
    </plugins>
    </build>

    <dependencies>
      <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
        <scope>provided</scope>
      </dependency>
    </dependencies>
</project>

封裝專案將建立一個 OSGi 套件,其內容與原始套件相同,除了 MANIFEST.MF 之外,MANIFEST.MF 將包含 OSGi 條目。

請注意,這裡使用了屬性來外部化外掛程式配置。在使用模組內的的多個專案時,屬性允許將通用外掛程式配置放置在頂層 pom 中,並允許每個子模組透過指定不同的屬性值來覆蓋它。一個真實的範例是 Spring-DM osgi 儲存庫。

重要的是要知道,Bnd 在建立套件時會考慮類別路徑上的所有可用類別。當從命令行使用 Bnd 時(如先前的範例所示),類別路徑僅由一個 jar 檔案組成,因此除了 c3p0 之外沒有其他額外的類別。但是,當使用 Maven 或 Ant 等建置工具時,類別路徑會大得多 - 在這種情況下,根據您的 Export/Import 套件指令,Bnd 可能會從產生的 jar 檔案中新增或捨棄類別。為了避免這種情況,請確保使用僅匹配實際包含套件的模式,即 com.mchange.* 而不是 *。

自定義內部工具

另一種方法(儘管不太可能遇到)是建立一個自定義工具,通常基於位元組碼分析。此類工具可以針對特定環境高度自定義,以提高速度或最小化記憶體佔用,或支援其他啟發法或配置文件。Spring Dynamic Modules 包含這樣一個內部的、基於 ASM 的位元組碼解析器,用於其測試框架,以有效率地即時建立 MANIFEST.MF。

但是,對於通用用途,Bnd 工具(無論是原始版本還是透過其 Maven 整合)提供了更多的選項,並且工作速度相當快。事實上,使用方式越通用,Bnd 越有可能透過其高度可自定義性來滿足需求。

使用現有的 OSGi 儲存庫

說到這裡,在將現有的函式庫包裝為 OSGi 套件之前,請檢查是否已經有人為您完成了這項工作。您可以透過檢查現有的 OSGi 儲存庫之一來做到這一點:

OSGi Bundle Repository (ORB) - OSGi Alliance 套件儲存庫,提供「套件的聯合集合」。

Eclipse Orbit - 包含可在 Eclipse 環境內使用的成品。由於 Eclipse 使用 Equinox,因此成品可能包含 Equinox 特定的 Manifest 條目。

Apache Felix Commons - 旨在「分享 [...] 套件化的成品」。

Apache OSGified projects - 一個簡單的頁面,指示已經或即將在其官方發行版本中包含 OSGi manifest 條目的 Apache Commons 專案。

希望在社群的幫助下,許多流行的 Java 函式庫預設情況下都將是 OSGi 友善的,並且無需使用單獨的儲存庫或包裝 jar 檔案。在此之前,您可以透過為您使用的專案提供修補程式或僅僅要求此功能來提供幫助。

在結束這篇文章之前,我想邀請所有對 OSGi 和 Spring Dynamic Modules 感興趣的人參加下週,2 月 27 日星期三舉行的一個即將到來的網路研討會,該研討會將涵蓋核心 OSGi 概念和 Spring DM 基礎知識。

取得 Spring 電子報

透過 Spring 電子報保持聯繫

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

獲得支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉辦的活動

查看 Spring 社群中所有即將舉辦的活動。

查看全部