程式扎記: [ OSGi In Action ] Introducing OSGi : Mastering modularity (2)

標籤

2011年7月13日 星期三

[ OSGi In Action ] Introducing OSGi : Mastering modularity (2)

Defining bundles with metadata :
這一節我們要深入討論 OSGi bundle 的 metadata. 並且我們將以之前介紹的 GUI 程是當作範例來說明如何使用 bundle 的 metadata. 基本上來說 bundle 的 metadata 是用來讓 OSGi framework 來管控 modularity 相關的特性包括相依性的解析與封裝. 基本上 metadata 的功能可以由下面三項說明含括 :
* Human-readable information—Optional information intended purely as an aid to humans who are using the bundle
* Bundle identification—Required information to identify a bundle
* Code visibility—Required information for defining which code is internally visible and which internal code is externally visible

我們會針對上面三項對 bundle 的 metadata 進行介紹, 因為 bundle 的 metadata 是寫在 manifest file, 所以語法也和一般 Jar 檔的 manifest file 相同.

Human-readable information :
大多數的 bundle metadata 主要是用來描述 modularity 的特性, 但是有些 metadata 與 modularity 無關而僅僅是提供 bundle 辨識與可讀性. 這些 metadata 通常是 optional 的, 且通常會直接被 OSGi 忽略. 底下我們以 GUI 其中一個 bundle (org.foo.shape) 的 metadata來說明 :
Bundle-Name: Simple Paint API
Bundle-Description: Public API for a simple paint program.
Bundle-DocURL: http://www.manning.com/osgi-in-action/
Bundle-Category: example, library
Bundle-Vendor: OSGi in Action
Bundle-ContactAddress: 1234 Main Street, USA
Bundle-Copyright: OSGi in Action

第一個 attribute Bundle-Name 是用來表達 bundle 的名稱, 相當於人的名字 (名字有可能重覆). 提供了我們為該 bundle 的初步認識. 而並沒有相關規定告訴你如何取一個 Bundle-Name, 只要你看了這個 Bundle-Name 就大概知道這個 bundle 的用途就夠了. 接著是 Bundle-Description attribute, 它的用途跟 Bundle-Name 都是用來描述這個 bundle, 只是這裡你可以用比較長的句子來解釋. Bundle-DocURL 允許你提供一個 URL 來參考到你的線上文件, 而提供更詳細的說明. Bundle-Category 提供了一個以逗號區隔的分類清單. 你可以根據自己的需求定義自己的分類. 接下來的 attributes Bundle-VendorBundle-ContactAddress, 與 Bundle-Copyright 都是看字面上名字就知道用途了.

這些可讀與友善的 metadata 用途很直覺也很有用. 但是通常 OSGi framework 直接就是忽略它們, 並且不會影響到 bundle 的 modularity. 所以你可以很隨性的使用它們. 接下來我們要介紹的 metadata 是用來識別 bundles, 就像你的身分證號碼獨一無二.

Bundle identification :
前面介紹的 human-readable metadata 基本上是給 ”人” 看的, 並且皆是 optional. 但有些 human-readable 的 metadata 是給 OSGi 看的. 之前介紹的 Bundle-Name 似乎可以當作一個 bundle 的識別, 但其實不然, Bundle-Name 是允許重覆的, 而要做為 bundle 的識別必須是獨一無二的. 因此有了 Bundle-SymbolicName.

相對於 Bundle-NameBundle-SymbolicName 是設計給 OSGi framework 來辨識 bundle 使用的. 通常我們會以 package 來當作某個 bundle 的 Bundle-SymbolicName 來避免名稱衝突 (通常不同的 bundle 會是不同的 package). 而有些建議使用 reverse-domain-name 的方式來命名 Bundle-SymbolicName, 不過不管是哪一種, 只要 unique 就足以滿足 OSGi framework 的要求.

- IDENTIFYING THE PAINT PROGRAM (PART 1)
前面介紹的 GUI 程式是根據 packages 進行 bundle 的切割, 所以你可以使用 package 當作 symbolic name. 例如做為 public API 的 bundle, 你可以宣程該 bundle 的 symbolic name 如下 : :
Bundle-SymbolicName: org.foo.shape

雖然只要我們保證 Bundle-SymbolicName 在 OSGi framework 裡是 unique 的, 就足夠當作 bundle 的識別. 但請考慮如果你有多個版本的 bundle 必須在 OSGi 裡運行, 拿org.foo.shape 當例子, 那麼為了不讓 Bundle-SymbolicName 名稱衝突, 因此第二版你可能就會取名為 org.foo.shapeV2. 雖然這樣就可以解決名稱重覆的問題, 但似乎不是個好辦法. 因此 OSGi 提供了另一個 header Bundle-Version 專門來解決這個問題. 因此在 OSGi framework 中同時以 Bundle-SymbolicName 與 Bundle-Version 來辨識一個獨一的 bundle. 除此之外, 當有同樣版本, 同樣 Bundle-SymbolicName 的 bundle, OSGi 也可以透過時間標簽來判斷是否用戶更新的 bundle 而進行更新. 另外 Bundle-Version 必須符合 OSGi version number 格式, 請參考如下 :
OSGi version number format
One important concept you’ll visit over and over again in OSGi is a version number, which appears here in the bundle-identification metadata. The OSGi specification defines a common version number format that’s used in a number of places throughout the specification. For this reason, it’s worth spending a few paragraphs exploring exactly what a version number is in the OSGi world

A version number is composed of three separate numerical component values separated by dots; for example, 1.0.0 is a valid OSGi version number. The first value is referred to as the major number, the second value as the minor number, and the third value as the micro number. These names reflect the relative precedence of each component value and are similar to other version-numbering schemes, where version-number
ordering is based on numerical comparison of version-number components in decreasing order of precedence: in other words, 2.0.0 is newer than 1.2.0, and 1.10.0 is newer than 1.9.9.

A fourth version component is possible, which is called a qualifier. The qualifier can contain alphanumeric characters; for example, 1.0.0.alpha is a valid OSGi version number with a qualifier. When comparing version numbers, the qualifier is compared using string comparison. As the following figure shows, this doesn’t always lead to intuitive results; for example, although 1.0.0.beta is newer than 1.0.0.alpha, 1.0.0 is older than both.


in the metadata where a version is expected, if it’s omitted, it defaults to 0.0.0. If a numeric component of the version number is omitted, it defaults to 0, and the qualifier defaults to an empty string. For example, 1.2 is equivalent to 1.2.0. One tricky aspect is that it isn’t possible to have a qualifier without specifying all the numeric components of the version. So you can’t specify 1.2.build-59; you must specify 1.2.0.build-59.

OSGi uses this common version-number format for versioning both bundles and Java packages. In chapter 9, we’ll discuss high-level approaches for managing version numbers for your packages, bundles, and applications.

- IDENTIFYING THE PAINT PROGRAM (PART 2)
舉例來說, 如果 org.foo.shape 有出第二版, 則你可以在 manifest 檔案定義 metadata如下 :
Bundle-SymbolicName: org.foo.shape
Bundle-Version: 2.0.0

其實 Bundle-SymbolicName 與 Bundle-Version 是在 OSGi R4 版的規格中要求的, 而我怎麼決定當前的 bundle 是符合哪一版 OSGi 的規定呢? 透過 Bundle-ManifestVersion=2 就可以告訴 OSGi framework 你的 bundle 是適用於 R4 版(含以後). 當你沒有給 Bundle-ManifestVersion 的話, 預設上你可以是前一版的設定, 也就是說你可以給不是 unique 的Bundle-SymbolicName 與 Bundle-Version.

- IDENTIFYING THE PAINT PROGRAM (PART 3)
底下在 manifest 檔案定義的 metadata, 告訴 OSGi 這個 bundle 適用於 OSGi R4 版的規定 :
Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.foo.shape
Bundle-Version: 2.0.0

針對 bundle 的識別與敘述相關的 metadata 已經說明完畢, 接下來要介紹的 metadata 是與 bundle 的 code-visibility 有關, 也是 metadata 中最重要的部分.

Code visibility :
可讀與辨識用的 metadata 很有用, 但是對於 bundle 的 modularity 並沒有多大幫助. OSGi 的 規格有訂了不少詳細且實用的 metadata 讓你可以定義 code 的 可見度, 讓你可以定義某些 code 只有在 bundle 裡可見, 而 export 出去的 code 對所有外部的 bundle 則都可見. 而這些與 code 可見度相關的 metadata 在 OSGi 可分為下面幾種 :
* Internal bundle class path—The code forming the bundle
* Exported internal code—Explicitly exposed code from the bundle class path for sharing with other bundles
* Imported external code—External code on which the bundle class path code depends

上面的分類都與 Java 類別相對與本身 bundle 與外部的 bundle 的可見度有關, 我們會逐一說明. 但在開始說明前我們會先了解一般 Jar 檔與 class path 的關係並進而了解為什麼 OSGi 會定義這些 code 可見度的 metadata 的背景與想要解決的問題.

- CODE VISIBILITY IN STANDARD JAR FILES AND THE CLASS PATH
一般開發Java 程式來說, 你會將 .java 編譯成 .class, 再用包裝 jar 的工具將這些 .class 包裝成一個 Jar 檔. 接著如果你有在 manifest 檔案定義 header Main-Class, 則你可以如下命令執行該 Jar 檔 :
java -jar app.jar

如果沒有定義 attribute Main-Class, 你還是可以透過參數 –cp 告訴 Java 你的 classpath 包含該 Jar 檔, 在以下面的命令執行該 Jar 檔 :
java -cp app.jar org.foo.Main

底下圖 Figure 2.9 是 JVM 在載入 Main class 的流程圖, 首先它會檢查命令列是否使用 Jar 檔, 接著檢查該 Jar 檔的 manifest 是否有定義 header Main-Class, 如果有的話則載入並呼叫方法 static public void main(String[]). 而如果是使用 class path, 則檢查命令列的完整類別名並呼叫入口函式. 接著當程式執行起來後, 任何需要用到的類別都會在執行過程被載入 (包含 JRE 的類別).

Figure 2.9 Flow diagram showing the steps the JVM goes through to execute a Java program from the class path

以上是一個簡略的概念告訴我們 Java 如何執行一個 Jar 檔, 但在這背後其實還有一些問題值得我們去思考, 如下面兩點 :
* Where to search inside the JAR file for a requested class?
* Which internal classes should be externally exposed?

針對第一個問題, Jar 檔的不成文規定就是任何 class 的搜尋路徑都是從當前路徑開始, 也就是如果你要求的類別是 org.foo.Bar, 那麼搜尋路徑便是 ./org/foo/Bar.class (Inside the Jar file). 接著第二個問題標準 Jar 檔不成文規定所有內部的類別對外部與內部的要求都是可見的. 這也是 modularity 在可見度上的預設 policy. 因此當你的 Jar 檔出現在 class path 上, 該 Jar 檔內部的所有類別對該執行程式都是可見的!

請參考下圖 figure 2.10, 當執行時, JVM 會動態從 classpath 上找尋需要的類別. 那麼 class path 相對於 modularity 又扮演什麼角色? 簡單來說 class path 上記錄了這個程式可能找到需要類別的路徑清單, 也就是說只要在 class path 上出現的 Jar 檔, 路徑上包含的類別對這個應用程式都是可見的, 即使某些類別根本不會被用到!

Figure 2.10 Flow diagram showing the steps the JVM goes through to load a class from the class path

有了對標準 Jar 檔與 class path 的認識, 接著我們再來看看 OSGi 如何來處理 code-visibility 的問題. 我們會從 bundle 內部開始, 如何在 bundle 裡找尋類別? 接著了解 OSGi 如何允許 bundle 開放內部的 code 到如何從 bundle 內部引用到外部 bundle 的 code.

- INTERNAL BUNDLE CLASS PATH
當標準 Jar 檔預設開放內部所有的類別為可見時, OSGi 有不同的做法. 透過使用 bundle class path (類似 標準 Java class path 概念, 但是範圍侷限於 bundle 內部.) 來指定 bundle 內部可見的類別, 如果沒有設定情況下, 預設是 bundle 內部的所有類別對bundle 內部為可見. 底下為 bundle class path 的定義.
BUNDLE-CLASSPATH An ordered, comma-separated list of relative bundle JAR file locations to be searched for class and resource requests.

當一個 bundle 內部類別需要同一個 bundle 的其他類別時, 可以透過設定 manifest header 的 Bundle-ClassPath 來設定多個在這個 bundle 可搜尋到的路徑清單, 並以逗點區隔. 這個設定與剛剛介紹的 global class path 的搜尋邏輯相同, 唯一不同的是搜尋的範圍. 所以你可以參考 figure 2.10 來了解 Bundle-ClassPath 的行為. 底下是一個設定範例 :
Bundle-ClassPath: .,other-classes/,embedded.jar

上面的設定告訴 bundle 在搜尋內部使用的類別時, 首先搜尋以 root 為相對路徑的 package (.), 接著搜尋以目錄 “other-classes” 為相對路徑的 package (other-classes/), 最後便是內嵌的 Jar 檔 (embedded.jar), 而內嵌的 Jar 檔搜尋邏輯與 global class path 相同. 而在 Bundle-ClassPath 設定的路徑順序就是類別載入器搜尋的路徑.

Bundle-ClassPath 如果不存在於 manifest 檔案中時, 一般來說 OSGi manifest headers 並不會提供預設值, 但因為標準的 Jar 檔在 class path 的搜尋邏輯上就是以當前路徑為 root 相對搜尋路徑, 相對於 Bundle-ClassPath 的設定就是 “.” . 這會讓我們有錯覺以為 Bundle-ClassPath 有預設值為 “.”. 因此我們可以知道標準 Jar 檔與 bundle 的搜尋 policy 是相同的.

- EXPORTING INTERNAL CODE
Bundle-ClassPath 影響的類別可視效果僅限於 bundle 內部, 但如果你想要使用的類別是在別的 bundle 或是你想要讓外部的 bundles 使用當前 bundle 內部的類別呢? 第一步便是開放內部的 package 給外部 bundles 使用.

對外部有用的類別通常是一些包含 public API 的類別, 而那些針對開放 API 實作的類別往往是我們希望隱藏的部分. 而我們知道對介面(開放API) 實作可以幫助我們降低對實作類別的耦合以提高程式的彈性. 但在標準的 Jar 檔並無法提供有效的機制來幫助我們區隔開放API 的類別與實作的類別, 他一視同仁的開放從 root 為相對路徑的所有 packages. 但是 OSGi bundle 預設是不開放任何的內部類別, 而必須透過 manifest header 設定 Export-Package 來開放內部的類別給外部的 bundle 使用, 底下是 Export-Package 簡單的說明 :
EXPORT-PACKAGE A comma-separated list of internal bundle packages to expose for sharing with other bundles.

在使用 Export-Package 顧名思義就是以 package 為單位, 只要你把想要開放的 packages 清單放在這個 header, OSGi 便會將這個清單裡所有 public 的類別開放給外部的 bundles 去使用. 後續我們還會看到一些工具幫我們自動找出這個清單而不用自己手動一筆筆的加入. 首先我們就來看一個例子, 在之前我們的範例 GUI 需要開放的 public API 就可以定義無下 : (figure 2.11 為設定的示意圖)
Export-Package: org.foo.shape


Figure 2.11 Graphical depiction of an exported package

上面的設定會開發每一個在 org.foo.shape package 裡的類別. 你可能想要 export 不只一筆 package, 你可以使用逗點來區隔多筆的 packages :
Export-Package: org.foo.shape,org.foo.other

問題是可能有多個 bundles 開放同名的 package, 這時你可以使用 attributes (如 vender etc) 來區別其它 bundle 開放出來的同名 package, 參考範例如下 :
Export-Package: org.foo.shape; vendor="Manning", org.foo.other; vendor="Manning"

上面的 attribute (vender) 是我們自己定義的, 非 OSGi 的規格, 當我們在討論如何 import 外部 bundles 開放的 packages 便會提到剛剛設置的 attribute (vender) 如何區隔不同 bundles 開放出同名的 packages. 另外如果你有多個開放的 packages 使用相同的 attribute 設定時, 你可以考慮以下的寫法可以更省力些:
Export-Package: org.foo.shape; org.foo.other; vendor="Manning"

上面透過自定義的 attribute 雖然可以解決同名的 packages 造成 import 的困擾, 但是 OSGi 提供一個更有效且容易理解的方法來區隔同名的 packages, 那就是 : 版本管控.

我們寫的 code 一直在更新而 packages 包含的類別也可以增加或減少. 因此一個版本管控的機制可以幫我們記錄這樣的變動. 透過 OSGi 平台開發出來的 Java 程式可以利用 OSGi 特有的 bundle attribute 來達成版本的管控, 除了之前介紹的 bundle 版本(Bundle-Version)外, 還有針對 package 的版本管控機制. 底下為針對範例 Paint GUI 使用 attribute:version宣告兩個 packages 的版本為 “2.0.0” :
Export-Package: org.foo.shape; org.foo.other; version="2.0.0"

上面例子使用 attribute:version 定義了 packages 的版本為 “2.0.0”, 而除了attribute:version 本身是 OSGi 的規範外, 如何撰寫版本號也必須滿足 OSGi 的規範. 你可能注意到之前在設定 Export-Package 時並沒有給版本號, 而當沒有給版本號時, 預設的版本號就是 “0.0.0”. 但使用預設版本號並不被建議, 更深入的版本號討論可以參考第九章.

透過 Bundle-ClassPath 與 Export-Package, 你可以很好的管理 bundle 內部類別 的 visibility. 但是有時候你需要的類別存在外部的 bundles, 而不是在本身的 bundle 裡, 在接下來你將會學到如何定義本身 bundle 的外部相依性.

- IMPORTING EXTERNAL CODE
Bundle-ClassPath 與 Export-Package 都是用來定義與 bundle 內部的類別相關的可見度. 但是有時候 bundle 會相依於外部的 bundles. 因此你便需要某種方法來定義與引入外部所需要的類別到當前的 bundle. 以標準的 Jar 檔來說, 這樣的要求便是在 class path 的概念中實現 (任何在 class path 出現的 Jar 檔, 外部類別路徑等, 對自身的程式都是可見的). 但是在 OSGi 並不使用 class path 的概念來引入外部 bundle 包含的類別, 而是透過 metadata 的定義來滿足對外部 bundle 的類別的需求.

從外部 bundles 引入內部 bundle 需要的類別很簡單但是當引入的 packages 多時就會變得很繁瑣. 另外唯一一個例外你需要的外部類別不用引入的就是 java.* package. 它在 OSGi 平台內對所有的 bundles 都是可見的. 而如何引入 bundle 需要的外部類別呢, 答案是透過 header Import-Package :
IMPORT-PACKAGE A comma-separated list of packages needed by internal bundle code from other bundles.



接著我們使用 paint GUI 當作範例來說明. 首先我們知道 paint GUI 的 main bundle 需要用到 package org.foo.shape, 但是該 package 已經被切到別的 bundle 了, 因此在 main bundle 需要將它引入使用. 下圖 figure 2.12 為引入示意圖 :

Figure 2.12 Graphical depiction of an imported package

Import-Package: org.foo.shape

上面的設定告訴 OSGi 平台目前的 bundle 需要使用到 package org.foo.shape. 但是要注意的是使用 Import-Package 引入的 packages 並不包含它們的子 packages. 因此當你需要使用到 org.foo.shape 與 org.foo.shape.other, 你必須同時在 Import-Package 設定這兩個 packages, 而複數的 packages 使用逗號區隔, 參考範例設定如下:
Import-Package: org.foo.shape,org.foo.shape.other

回想一下之前我們曾在 Export-Package 提過如果有多個同名的開放 packages, 我們可以透過自訂義的 attribute 來區隔所要使用的開放 package. 再開始說明如何引入含特定 attribute 的開放 package 之前, 先來看看之前的 Export-Package :
Export-Package: org.foo.shape; org.foo.other; vendor="Manning"

知道了你要引入的 package 有什麼特定而其它同名的 packages 沒有的 attribute 時候, 你便可以使用那個自訂義的 attribute 來避免引用到預期外的 packages. 如下我們設定要引入 package org.foo.shape 並且含 attribute:vender 為 “Manning” :
Import-Package: org.foo.shape; vendor="Manning"

透過上面的範例知道, 在 Import-Package 設定的 attributes 就像一個 filter 一樣, 任何想要被引用的外部開發的 packages 必須符合 Import-Package 所定義的 attributes (and 邏輯), 缺一不可. 而定義在 attributes 的值則是使用 exactly match. 差一個字都不可以. 如果你是使用 OSGi 規範的 attribute:version , 也可以達成同樣的效果, 但是更有彈性. 我們來看一個設定範例如下:
Import-Package: org.osgi.framework; version="1.3.0"

上面的設定告訴 OSGi 平台引入一個版本從 1.3.0 到 infinity 的 package org.osgi.framework這樣的設定通常隱含後面的版本都要向前相容. 如果你想要引入一個版本的範圍, 則你可以使用字元 “[“與” ]” 來說明包括邊際的版本 或 “(“ 與 “)” 來說明不包括邊際的版本. 參考範例如下 :
Import-Package: org.osgi.framework; version="[1.3.0,2.0.0)"

上面的設定說明要 OSGi 引入一個版本範圍從 1.3.0 並包含該版本到版本 2.0.0 但是不包含 2.0.0. 下表更詳細的說明 “[]” 與 “()” 的使用差別 :


透過上面的說明知道, 如果你想要使用某個特定版本的 package 如 1.0.1, 那透過 attribute:version 的寫法就是 “[1.0.1, 1.0.1]”. 另外之前的 Import-Package 範例在沒有給 version 的情況下預設是 “0.0.0”, 也就是說引用的 package 版本的 range 是從 “0.0.0” 到 infinity!

到目前為止我們已經知道了構成 OSGi module layer 的主要 headers. 包括 Bundle-ClassPathExport-Package 與 Import-Package等. 接著我們將以 paint GUI 當作範例來複習剛剛我們學的東西, 並真的把切完的 bundles 跑起來. 但在這之前我們先來看看這樣的 visibility policy 是怎麼在 OSGi 裡 work 的.

Class-search order :
我們一直提到 code visibility, 但是到底它怎麼跟我們之前談的 OSGi metadata 互相作用? 這涉及 OSGi 如何解析 metadata 與搜尋 bundles 需要的(內部或外部)類別的邏輯. 當一個 bundle 透過 OSGi 向外部的 bundle 要求引用package 時, OSGi 會透過 class loader 來完成, 細節並不是這裡的重點, 但是我們必須知道這些 class 搜尋的流程與順序.

當一個 bundle 在執行時期需要用到某個類別時, OSGi 會依以下的順序搜尋該類別 :
1. If the class is from a package starting with java., the parent class loader is asked for the class. If the class is found, it’s used. If there is no such class, the search ends with an exception.
2. If the class is from a package imported by the bundle, the framework asks the exporting bundle for the class. If the class is found, it’s used. If there is no such class, the search ends with an exception.
3. The bundle class path is searched for the class. If it’s found, it’s used. If there is no such class, the search ends with an exception.
除了上述有關 modular level 的OSGi metadata , 還有其它的進階的部分我們會在第五章討論. 接著我們會以 paint GUI 當作範例說明並將它跑起來, 接著再來回顧目前為止設計與進度. 


Supplement :
[ OSGi In Action ] Introducing OSGi : Mastering modularity (1)
[ OSGi In Action ] Introducing OSGi : Mastering modularity (2)
[ OSGi In Action ] Introducing OSGi : Mastering modularity (3)
Source code for osgi-in-action

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!