探索 OSGi 框架的组件运行机制
在目前的 Java 開發(fā)平臺中,對于組件開發(fā)過程,比如打包、部署和驗證等,并沒有一個統(tǒng)一的標(biāo)準(zhǔn)。正因如此,許多 Java 項目,例如 JBoss 和 Net Beans,都擁有一套自定義的組件開發(fā)規(guī)范和框架,但是實際上這些解決方案都是基于為組件分配獨立的類加載器 (Class Loader) 的思想。 OSGi 框架為基于 Java 的組件開發(fā)提供了一套通用的和標(biāo)準(zhǔn)的解決方案,并已經(jīng)成為事實上的工業(yè)標(biāo)準(zhǔn)。
OSGi 組件框架
在 OSGi 框架中,組件被稱為 Bundle,它是由一些提供 Bundle 自身功能的資源 ( 如 Java 類文件、配置文件等 )、MANIFEST.MF 文件和其它資源文件構(gòu)成。在運行時環(huán)境中,每個 Bundle 都有一個獨立的 Class Loader,Bundle 之間通過不同的類加載器和在 MANIFEST.MF 文件中定義的包約束條件就可以輕松實現(xiàn)包級別的資源隱藏和共享,從而實現(xiàn)基于組件方式的開發(fā)和運行。 Class Loader 是實現(xiàn)這種運行方式的關(guān)鍵機制,每個 Bundle 的 Class Loader 必須在此 Bundle 得到正確的解析 ( Resolving ) 之后,再由 OSGi 框架創(chuàng)建。只有當(dāng)一個 Bundle 中的所有包約束條件都滿足時,它才被正確解析完畢。
Bundle 解析
Bundle 的解析是通過分析定義在 MANIFEST.MF 文件中的元數(shù)據(jù) ( 主要定義了包約束條件 ),查找與包約束條件相匹配的 Bundle 并建立關(guān)聯(lián)關(guān)系的過程。 MANIFEST.MF 文件中的包約束條件主要是通過 Import-Package、DynamicImport-Package、Export-Package 和 Require-Bundle 這四種表達方式來實現(xiàn)。下面簡單介紹一下它們:
在 Bundle 得到正確解析后,OSGi 框架將會生成此 Bundle 的依賴關(guān)系表。在實際運行過程中,框架就可以通過此關(guān)系表找到 Bundle 依賴的外部 Class Loader,從而實現(xiàn)外部類資源的加載和運行 hg 。 Bundle 的關(guān)系圖可以在 OSGi 的控制臺中通過內(nèi)部命令" bundle "來查看,如下圖所示:
圖 1. Bundle 依賴關(guān)系表
Bundle 運行
Bundle 的運行主要依靠于 OSGi 框架為其創(chuàng)建的類加載器(Class Loader),加載器負責(zé)查找和加載 Bundle 自身或所依賴的類資源。 ClassLoader 之間的依賴關(guān)系構(gòu)成了一個有向圖,如下圖所示:
圖 2. Class Loader 依賴關(guān)系圖
Bundle 的 Class Loader 能加載的所有類的集合構(gòu)成了 Bundle 的類空間 (Class Space) 。類空間包含的類資源主要來自于以下幾個方面:
在實際運行環(huán)境中,Bundle 的 Class Loader 根據(jù)如下規(guī)則去搜索類資源。規(guī)則簡要介紹如下:
如果在經(jīng)過上面一系列步驟后,仍然沒有正確地加載到類資源,則 OSGi 框架會向外拋出類未 發(fā)現(xiàn)異常。
確保 Bundle 類空間的完整性
從上述對 OSGi 組件框架的剖析中,MANIFEST.MF 文件中定義的元數(shù)據(jù)對 Bundle 的解析和運行起著至關(guān)重要的作用。 Bundle 所需要的類資源應(yīng)該完全被其類空間所覆蓋,否則將會在運行時環(huán)境中拋出類或資源未發(fā)現(xiàn)異常,從而導(dǎo)致 Bundle 無法正常工作。下面給出一個實際例子來說明這種情況。
在 Eclipse 環(huán)境中創(chuàng)建兩個插件開發(fā)項目 , 并命名為 Client 和 HelloService. HelloService 向外提供服務(wù) , Client 通過注冊在 OSGi 控制臺中的命令來調(diào)用 HelloService 的服務(wù)。 HelloSerive 定義的服務(wù)接口為 :
清單 1. IHello.java
| 1 2 3 4 | package com.ibm.helloservice; public interface IHello { ????public void sayHello(String addition); } |
清單 2. ILabelProvider.java
| 1 2 3 4 5 | package com.ibm.helloservice; ? public interface ILabelProvider { ????public String getLabel(); } |
同時 HelloService 還給出了具體實現(xiàn) :
清單 3. HelloImpl.java 和 Resource.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.ibm.helloservice.impl; import com.ibm.helloservice.IHello; public class HelloImpl implements IHello { ????public void sayHello(String addition) { ????????System.out.println ("Hello ” + addition + "!"); ????} } ? package com.ibm.helloservice.resource; import com.ibm.helloservice.ILabelProvider; public class Resource implements ILabelProvider { ????public String getLabel() { ????????return "Test Label"; ????} } |
由于此 Bundle 向外提供了服務(wù)和資源信息,因此需要在 MANIFEST.MF 文件中將它們所屬于的包導(dǎo)出:
清單 4. MANIFEST.MF
| 1 2 3 4 5 6 7 8 9 10 | Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: HelloService Plug-in Bundle-SymbolicName: HelloService Bundle-Version: 1.0.0 Bundle-Activator: com.ibm.helloservice.impl.Activator Bundle-Localization: plugin Export-Package: com.ibm.helloservice, com.ibm.helloservice.resource Import-Package: org.osgi.framework; version="1.3.0" |
在 BundleActivator 類中,向 OSGi 框架注冊了 IHello 的 service,具體實現(xiàn)代碼十分簡單,在此略過。服務(wù)使用方 (Client Bundle),是一個控制臺程序,通過注冊在 OSGi 框架中的命令調(diào)用 HelloService Bundle 提供的服務(wù)。具體實現(xiàn)參見如下代碼:
清單 5. Activator.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | package com.ibm.client; ? import java.io.IOException; import java.io.InputStream; import java.util.Properties; ? import org.eclipse.osgi.framework.console.CommandInterpreter; import org.eclipse.osgi.framework.console.CommandProvider; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; ? import com.ibm.helloservice.IHello; import com.ibm.helloservice.ILabelProvider; ? public class Activator implements BundleActivator, ????ServiceTrackerCustomizer, CommandProvider? { ? ????private ServiceTracker helloServiceTracker; ????private ServiceRegistration registration; ????private BundleContext bundleContext; ????private IHello hello; ????? ????private static Properties config = null; ? ????static { ????????config = new Properties(); ????????try { ????????????InputStream is = Activator.class.getClassLoader() ????????????????.getResourceAsStream("LabelProvider.properties"); ????????????config.load(is); ????????} catch (IOException e) { ????????????e.printStackTrace(); ????????} ????} ????? ????public void start(BundleContext context) throws Exception { ????????bundleContext = context; ????????helloServiceTracker = new ServiceTracker(context, IHello.class.getName(), this); ????????helloServiceTracker.open(); ????????registration = context.registerService(CommandProvider.class.getName(), ????????this, null); ????} ? ????public void stop(BundleContext context) throws Exception { ????????helloServiceTracker.close(); ????????registration.unregister(); ????????bundleContext = null; ????} ? ????public Object addingService(ServiceReference servRef) { ????????Object service = bundleContext.getService(servRef); ????????if(service instanceof IHello) { ????????????hello = (IHello)service; ????????????return hello; ????????}?? ????????return service; ????} ? ????public void modifiedService(ServiceReference servRef, Object service) { } ? ????public void removedService(ServiceReference servRef, Object service) { ????????if(service instanceof IHello) { ????????????hello = null; ????????} ????????bundleContext.ungetService(servRef); ????} ? ????public String getHelp() { ????????return "\tcall - say hello"; ????} ? ????public void _call(CommandInterpreter ci) throws Exception { ????????hello.sayHello(newLabelInstance().getLabel()); ????} ????? ????private ILabelProvider newLabelInstance() throws Exception { ????????String className = config.getProperty("ProviderClass"); ????????Class<?> labelClass = this.getClass().getClassLoader().loadClass(className); ????????ILabelProvider label = (ILabelProvider)labelClass.newInstance(); ????????return label; ????} ?} |
在以上代碼中,Bundle 通過 ServiceTracker 得到 IHello 這個服務(wù)接口,并同時向 OSGi 框架注冊一個名為" call "的命令。在這個命令的執(zhí)行過程中,Bundle 通過自身的 ClassLoader 去加載定義在 LabelProvider.properties 文件中的 ILabelProvider 實現(xiàn)類,并打印 Label Provider 中的內(nèi)容。從以上代碼中,我們會很容易地定義出此 Bundle 需要導(dǎo)入哪些包,如下 MANIFEST.MF 文件所示 :
代碼清單 6: MANIFEST.MF
| 1 2 3 4 5 6 7 8 9 10 11 12 | Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Client Plug-in Bundle-SymbolicName: Client Bundle-Version: 1.0.0 Bundle-Activator: com.ibm.client.Activator Bundle-Localization: plugin Import-Package: com.ibm.helloservice, org.eclipse.core.runtime, org.eclipse.osgi.framework.console;version="1.0.0", org.osgi.framework;version="1.3.0", org.osgi.util.tracker;version="1.3.1" |
從表面上看 , 這個文件沒有任何遺漏 , 在 Eclipse IDE 環(huán)境中能夠正確解釋通過 , 并且在啟動 OSGi 框架后 , Client Bundle 也能夠被正確解析和啟動。但是在敲入 call 命令的后 , 就會顯示如下錯誤 :
圖 3. 資源加載異常
原來缺少了一個依賴包 com.ibm.helloservice.resource.Resource。在 MANIFEST.MF 文件中加入對它的定義后 , call 命令就能夠正確運行了 :
圖 4. 運行結(jié)果
這個例子模擬了在實際 Bundle 開發(fā)過程中普遍遇到的問題,即許多類資源定義在 plugin.xml 文件中,然后由其他組件動態(tài)的創(chuàng)建和調(diào)用,而在 Eclipse IDE 開發(fā)環(huán)境中,并沒有相應(yīng)的機制去檢查非 Java 代碼里的包引用,從而 Bundle 缺失對某些包依賴的定義,最后就造成了 Bundle 類空間的不完整性。通常這種情況下,Bundle 能夠被正確解析,但是在運行的時候就會拋出類未找到異常。所以在開發(fā)一個 Bundle 的時候,一定要仔細檢查類空間是否已經(jīng)將整個 Bundle 所需要的類資源覆蓋,這樣才能避免運行時的異常發(fā)生。
合理使用 Manifest 文件
Import-Package 與 Require-Bundle
OSGi 框架提供了兩種導(dǎo)入包依賴的方式,即 Import-Package 和 Require-Bundle 。從下圖中我們可以看出 Require-Bundle 會對整個 Bundle 產(chǎn)生依賴,也就是說 Bundle 所 Export 出的包都會被 A 加入到自己的類空間,而 Import-Package 只會對指定的包產(chǎn)生依賴關(guān)系。
圖 5. Bundle 依賴關(guān)系圖
在大多數(shù)情況下,都應(yīng)該使用 Import-Package 而不是 Require-Bundle 。 Import-Package 比 Require-Bundle 更利于 Bundle 的部署和版本維護,同時在查找類的時候有更高的效率。
Eclipse-LazyStart
在 OSGi R4 版本中,通過對 Eclipse-LazyStart 屬性的設(shè)置,可以指定 Bundle 是否支持慢啟動功能。當(dāng) Eclipse-LazyStart 被設(shè)置為 true 的時候,Bundle 不會被默認啟動,而是當(dāng) Bundle 的類或其它資源被第一次調(diào)用時,由框架自動激活 Bundle 。這樣就會使得 OSGi 框架在啟動的時候,只啟動必須的 Bundle 而延遲啟動其它的 Bundle,從而確保了啟動時間和效率。在默認情況下,Eclipse-LazyStart 為 false 。
Bundle-ManifestVersion
Bundle-ManifestVersion 指定了 Bundle 應(yīng)該遵循 OSGi 規(guī)范的版本號。其默認值是 1,即 R3 版本;值為 2 的時候,表示為 R4 版本。當(dāng) Bundle 需要用到 R4 中新功能的時候,如 Bundle 的慢啟動,則必須顯示設(shè)置 Bundle-ManifestVersion 為 2 。
類不一致性問題
在 OSGi 框架中,多個 Bundle 可以導(dǎo)出相同的包,如果在某個 Bundle 的類空間中存在來自于不同 Bundle 的相同類信息,就會導(dǎo)致類的不一致性問題。示例如下:
圖 6. Bundle 依賴關(guān)系圖
Bundle A 向外 Export 兩個包 p 和 r,其中 p 對 q 存在約束關(guān)系,即 q 須為 Bundle B 中的 1.0 版本。同時,Bundle C 又分別導(dǎo)入了 p 和 q 兩個包,包 p 來自于 A,而包 q 為 Bundle D 中的 2.0 版本。雖然 C 中并沒有顯示地定義對 B 中包 q 的依賴關(guān)系,但是由于 A 中的包 p 綁定了 B 中版本為 1.0 的包 q,故 C 在解析對包 p 的依賴關(guān)系的時候也會自動把 B 中 1.0 版本的包 q 導(dǎo)入到自己的類空間中。這樣在 C 的類空間中,就存在著兩個不同版本、來自于不同 Bundle 的包 q,進而就會存在兩個不同的 Class Loader 對應(yīng)著包 q,故在 Bundle C 的運行過程中就會出現(xiàn)類不一致性的異常。
小結(jié)
本文介紹了 OSGi 框架的組件運行機制,包括 Bundle 的解析、運行等,并結(jié)合實際的示例演示了在基于 OSGi 平臺開發(fā) Bundle 的過程中應(yīng)該注意的一些問題。
相關(guān)主題
- 獲得有關(guān) Eclipse 和 Equinox 的更多詳細資料:Eclipse.org,Equinox。
- 閱讀“類裝入問題解密”(developerWorks,2005 年 12 月):了解 Java Class Loader 的相關(guān)知識:。
- 閱讀 Scott Delap 撰寫的“了解 Eclipse 插件如何使用 OSGi”(developerWorks,Scott Delap,2006 年 9 月):闡明了 Eclipse 與 OSGi 的關(guān)系。
- 有關(guān) Eclipse 和 OSGi 的簡介,請閱讀“利用 OSGi 解決 Eclipse 插件難題”。
- 訪問?OSGi Alliance Service Platform, 了解更多關(guān)于 OSGi 的信息,包括 OSGi Release 4 規(guī)范等信息。
- 要獲得在 Eclipse 下進行開發(fā)的詳細幫助文檔,請訪問?Help – Eclipse SDK。
- 要獲得關(guān)于 Eclipse 平臺的介紹性文章,請參閱“Eclipse 平臺入門”。
- 訪問 IBM developerWorks?Eclipse 項目資源,擴展您的 Eclipse 技術(shù)。
- developerWorks Java 技術(shù)專區(qū):數(shù)百篇關(guān)于 Java 編程各個方面的文章。
from:https://www.ibm.com/developerworks/cn/java/j-lo-osgi/index.html?
總結(jié)
以上是生活随笔為你收集整理的探索 OSGi 框架的组件运行机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Equinox 开发 OSGi 应
- 下一篇: 基于 OSGi 的面向服务的组件编程