使用 Equinox 开发 OSGi 应用程序
開(kāi)始之前
關(guān)于本教程
OSGi 是目前動(dòng)態(tài)模塊系統(tǒng)的事實(shí)上的工業(yè)標(biāo)準(zhǔn),雖然一開(kāi)始只是作為嵌入式設(shè)備和家庭網(wǎng)關(guān)的框架來(lái)使用,但是實(shí)際上它適用于任何需要模塊化、面向服務(wù)、面向組件的應(yīng)用程序。而 Equinox 則是的 Eclipse 所使用的 OSGi 框架,是 Eclipse 強(qiáng)大的插件體系的基礎(chǔ),Eclipse 的穩(wěn)定可靠性也為該框架帶來(lái)了聲譽(yù)。
本教程就將演示如何在 Eclipse 環(huán)境下利用 Equinox 框架進(jìn)行 OSGi 應(yīng)用開(kāi)發(fā)。首先解釋了實(shí)現(xiàn)上述應(yīng)用程序所必需了解的基本概念和基礎(chǔ)知識(shí),并結(jié)合示例代碼演示 OSGi 開(kāi)發(fā)的一些重要技術(shù),最后探討了基于 OSGi 應(yīng)用程序一般所采用的架構(gòu),以及如何將 Equinox OSGi 應(yīng)用程序脫離 Eclipse 而部署為一個(gè)標(biāo)準(zhǔn)的 Java 應(yīng)用程序。
目標(biāo)
在本教程中,您將學(xué)習(xí):
- OSGi 及框架簡(jiǎn)介
- 編寫(xiě)第一個(gè) OSGi 應(yīng)用程序
- 重要的理論知識(shí)
- 開(kāi)發(fā)一個(gè)真實(shí)的 OSGi 應(yīng)用程序
- 探討 OSGi 應(yīng)用架構(gòu)
- 部署 OSGi 應(yīng)用程序
先決條件
本教程假設(shè)讀者熟悉基本 Java 語(yǔ)言以及 Eclipse 開(kāi)發(fā)環(huán)境的使用。
系統(tǒng)需求
本教程假設(shè)您有一個(gè)可以工作的 Eclipse 3.x 環(huán)境。如果還沒(méi)有,請(qǐng)?jiān)?Eclipse 網(wǎng)站?上找到相關(guān)下載的鏈接,以幫助您在自己的系統(tǒng)上操作示例步驟以及運(yùn)行示例代碼。
OSGi 及框架簡(jiǎn)介
OSGi 簡(jiǎn)介
OSGi 是目前動(dòng)態(tài)模塊系統(tǒng)的事實(shí)上的工業(yè)標(biāo)準(zhǔn),雖然一開(kāi)始只是作為嵌入式設(shè)備和家庭網(wǎng)關(guān)的框架來(lái)使用,但是實(shí)際上它適用于任何需要模塊化、面向服務(wù)、面向組件的應(yīng)用程序。
目前 OSGi 規(guī)范已經(jīng)發(fā)展到第四版(R4), 由 OSGi 聯(lián)合組織(OSGi Alliance)負(fù)責(zé)進(jìn)行維護(hù)管理,相關(guān)的規(guī)范資料也可以從該網(wǎng)站獲得。(參考資料)
OSGi 框架
開(kāi)發(fā)基于 OSGi 的應(yīng)用程序離不開(kāi)實(shí)現(xiàn)了 OSGi 標(biāo)準(zhǔn)的框架,就好比是基于 J2EE 的開(kāi)發(fā)離不開(kāi)應(yīng)用服務(wù)器一樣。目前比較流行的基于 OSGi R4 標(biāo)準(zhǔn)實(shí)現(xiàn)的 OSGi 框架有三個(gè):
Equinox:這是大名鼎鼎的 Eclipse 所使用的 OSGi 框架,Eclipse 強(qiáng)大的插件體系就是構(gòu)建在 OSGi bundles 的基礎(chǔ)之上,Eclipse 的穩(wěn)定可靠性為該框架帶來(lái)了聲譽(yù),而且由于有 IBM 公司的強(qiáng)力支持,其后續(xù)的開(kāi)發(fā)和文檔資料也有了一定的保障。一般情況下,我們推薦您使用該框架進(jìn)行 OSGi 開(kāi)發(fā)。本教程的后續(xù)部分也將演示如何使用 Equinox 框架來(lái)進(jìn)行 OSGi 應(yīng)用程序的開(kāi)發(fā)。
Makewave Knopflerfish:這是另外一個(gè)比較知名的 OSGi 框架,目前的版本已經(jīng)支持 R4 規(guī)范,其特點(diǎn)在于為應(yīng)用程序的開(kāi)發(fā)提供了大量的 bundle 。
Apache Flex:由 Apache 基金組織開(kāi)發(fā)的面向社區(qū)的 OSGi 框架實(shí)現(xiàn),提供了標(biāo)準(zhǔn)的服務(wù)和一些有趣的和 OSGi 相關(guān)的服務(wù)實(shí)現(xiàn)。
Hello World!編寫(xiě)第一個(gè) OSGi 應(yīng)用程序
準(zhǔn)備工作
Hello World
一般情況下,學(xué)習(xí)一門(mén)新的技術(shù),程序員都習(xí)慣于首先開(kāi)發(fā)一個(gè) hello world 應(yīng)用程序,這似乎也是一種“工業(yè)標(biāo)準(zhǔn)”。好的,讓我們開(kāi)始吧,開(kāi)發(fā)一個(gè)簡(jiǎn)單的 OSGi 應(yīng)用程序并不難,步驟如下:
圖 1. 新建 plug-in 工程
圖 2. 填入工程名及選擇目標(biāo)平臺(tái)
圖 3. 使用缺省設(shè)置
Activator:這是 bundle 啟動(dòng)時(shí)首先調(diào)用的程序入口,相當(dāng)于 Java 模塊中的 main 函數(shù)。不同的是,main 需要通過(guò)命令行調(diào)用,而 OSGi 的 Activator 是被動(dòng)的接受 OSGi 框架的調(diào)用,收到消息后才開(kāi)始啟動(dòng)。
最佳實(shí)踐:不要在 Activator 中寫(xiě)太多的啟動(dòng)代碼,否則會(huì)影響 bundle 啟動(dòng)速度,相關(guān)的服務(wù)啟動(dòng)可以放到服務(wù)的監(jiān)聽(tīng)器中。
圖 4. 勾掉缺省的選項(xiàng)
圖 5. 基本的插件視圖
清單 1. 編輯 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 | package osgi.test.helloworld; ? import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; ? public class Activator implements BundleActivator { ? ?????/* ?????* (non-Javadoc) ?????* @see org.osgi.framework.BundleActivator ?????*???? #start(org.osgi.framework.BundleContext) ?????*/ ?????public void start(BundleContext context) throws Exception { ????????System.out.println("hello world"); ?????} ? ?????/* ?????* (non-Javadoc) ?????* @see org.osgi.framework.BundleActivator ?????*???? #stop(org.osgi.framework.BundleContext) ?????*/ ?????public void stop(BundleContext context) throws Exception { ?????} ?} |
我們可以看到每個(gè) Activator 實(shí)際都是實(shí)現(xiàn)了?BundleActivator?接口,此接口使 Activator 能夠接受框架的調(diào)用。在框架啟動(dòng)后,啟動(dòng)每個(gè) bundle 的時(shí)候都會(huì)調(diào)用每個(gè) bundle 的 Activator 。
注意:bundle 的 Activator 必須含有無(wú)參數(shù)構(gòu)造函數(shù),這樣框架才能使用?Class.newInstance()?方式反射構(gòu)造 bundle 的 Activator 實(shí)例。
這里我們?cè)?start?方法中填入了我們希望輸出的 hello world 字符串。那么,怎么才能啟動(dòng)這個(gè) bundle 呢?
圖 6. 新建 OSGi 運(yùn)行環(huán)境
在右邊的運(yùn)行環(huán)境對(duì)話框中,輸入運(yùn)行環(huán)境的名字、start level 和依賴(lài)的插件,由于我們目前不需要其它的第三方插件,因此只需要勾上系統(tǒng)的 org.eclipse.osgi 插件,如果不選擇此插件,hello world 將無(wú)法運(yùn)行。如圖 7,只有當(dāng)您點(diǎn)擊了?validate bundles?按鈕 ,并且提示無(wú)問(wèn)題之后,才表明您的運(yùn)行環(huán)境基本 OK 了。
圖 7. 選擇 org.eclipse.osgi插件
依賴(lài)插件的選擇:
圖 8. 依賴(lài)插件的選擇
好的,如果您的運(yùn)行環(huán)境已經(jīng) OK,那么就點(diǎn)擊?Run?吧。
圖 9. 運(yùn)行 OSGi 項(xiàng)目
恭喜您,成功了!
OSGi 控制臺(tái)
OSGi 控制臺(tái)對(duì)于習(xí)慣開(kāi)發(fā)普通 Java 應(yīng)用程序的開(kāi)發(fā)人員來(lái)說(shuō),還是比較新鮮的。一般來(lái)說(shuō),通過(guò) OSGi 控制臺(tái),您可以對(duì)系統(tǒng)中所有的 bundle 進(jìn)行生命周期的管理,另外也可以查看系統(tǒng)環(huán)境,啟動(dòng)、停止整個(gè)框架,設(shè)置啟動(dòng)級(jí)別等等操作。如圖 10,鍵入?SS?就可以查看所有 bundle 的狀態(tài):
圖 10. 查看所有 bundle 的狀態(tài)
下面列出了主要的控制臺(tái)命令:
表 1. Equinox OSGi 主要的控制臺(tái)命令表
| 控制框架 | launch | 啟動(dòng)框架 |
| shutdown | 停止框架 | |
| close | 關(guān)閉、退出框架 | |
| exit | 立即退出,相當(dāng)于 System.exit | |
| init | 卸載所有 bundle(前提是已經(jīng) shutdown) | |
| setprop | 設(shè)置屬性,在運(yùn)行時(shí)進(jìn)行 | |
| 控制 bundle | Install | 安裝 |
| uninstall | 卸載 | |
| Start | 啟動(dòng) | |
| Stop | 停止 | |
| Refresh | 刷新 | |
| Update | 更新 | |
| 展示狀態(tài) | Status | 展示安裝的 bundle 和注冊(cè)的服務(wù) |
| Ss | 展示所有 bundle 的簡(jiǎn)單狀態(tài) | |
| Services | 展示注冊(cè)服務(wù)的詳細(xì)信息 | |
| Packages | 展示導(dǎo)入、導(dǎo)出包的狀態(tài) | |
| Bundles | 展示所有已經(jīng)安裝的 bundles 的狀態(tài) | |
| Headers | 展示 bundles 的頭信息,即 MANIFEST.MF 中的內(nèi)容 | |
| Log | 展示 LOG 入口信息 | |
| 其它 | Exec | 在另外一個(gè)進(jìn)程中執(zhí)行一個(gè)命令(阻塞狀態(tài)) |
| Fork | 和 EXEC 不同的是不會(huì)引起阻塞 | |
| Gc | 促使垃圾回收 | |
| Getprop | 得到屬性,或者某個(gè)屬性 | |
| 控制啟動(dòng)級(jí)別 | Sl | 得到某個(gè) bundle 或者整個(gè)框架的 start level 信息 |
| Setfwsl | 設(shè)置框架的 start level | |
| Setbsl | 設(shè)置 bundle 的 start level | |
| setibsl | 設(shè)置初始化 bundle 的 start level |
MANIFEST.MF
MANIFEST.MF 可能出現(xiàn)在任何包括主類(lèi)信息的 Jar 包中,一般位于 META-INF 目錄中,所以此文件并不是一個(gè) OSGi 特有的東西,而僅僅是增加了一些屬性,這樣也正好保持了 OSGi 環(huán)境和普通 Java 環(huán)境的一致性,便于在老的系統(tǒng)中部署。表 2 列出此文件中的重要屬性及其含義:
表 2. MANIFEST.MF 文件屬性
| Bundle-Activator | Bundle 的啟動(dòng)器 |
| Bundle-SymbolicName | 名稱(chēng),一般使用類(lèi)似于 JAVA 包路徑的名字命名 |
| Bundle-Version | 版本,注意不同版本的同名 bundle 可以同時(shí)上線部署 |
| Export-Package | 導(dǎo)出的 package 聲明,其它的 bundle 可以直接引用 |
| Import-Package | 導(dǎo)入的 package |
| Eclipse-LazyStart | 是否只有當(dāng)被引用了才啟動(dòng) |
| Require-Bundle | 全依賴(lài)的 bundle,不推薦 |
| Bundle-ClassPath | 本 bundle 的 class path,可以包含其它一些資源路徑 |
| Bundle-RequiredExecutionEnvironment | 本 bundle 必須的執(zhí)行環(huán)境,例如 jdk 版本聲明 |
重要的理論知識(shí)
好的,剛才我們已經(jīng)從頭到尾開(kāi)發(fā)了一個(gè)基于 Equinox 框架的 Hello world 應(yīng)用程序。我們發(fā)現(xiàn)似乎并不是很困難,很多工作 Eclipse 已經(jīng)幫我們做好了,例如 Activator 代碼框架和 MANIFEST.MF 文件,我們也學(xué)會(huì)了如何控制 OSGi 的控制臺(tái)和編寫(xiě) MANIFEST.MF 文件,但是,您真的明白它們是如何運(yùn)行的么?下面我們將重點(diǎn)介紹一些 OSGi 運(yùn)行必備的基礎(chǔ)知識(shí)。
什么是 bundle?
我們已經(jīng)看到,編寫(xiě)一個(gè)很普通的 Hello world 應(yīng)用,必須首先創(chuàng)建一個(gè) plug-in 工程,然后編輯其 Activator 類(lèi)的?start?方法,實(shí)際我們這樣做的本質(zhì)是為 OSGi 運(yùn)行環(huán)境添加了一個(gè) bundle,那么一個(gè) bundle 必須的構(gòu)成元素是哪些呢?
框架做了些什么?
好了,我們已經(jīng)明白 bundle 是什么了,也知道如何開(kāi)發(fā)一個(gè)基本的 bundle 了,那么我們還必須要明白,我的 bundle 放在 Equinox 框架中,它對(duì)我們的 bundle 做了些什么?
圖 11. Equinox 框架架構(gòu)
實(shí)際上,目標(biāo)平臺(tái)已經(jīng)為我們準(zhǔn)備了 N 個(gè) bundle,它們提供各種各樣的服務(wù),OSGi 中,這些 bundle 的名字叫 system bundle,就好比精裝修的房子,您只需要拎包入住,不再需要自己鋪地板,裝吊頂了。
我們的 bundle 進(jìn)入 Equinox 環(huán)境后,OSGi 框架對(duì)其做的事情如下:
Bundle 的狀態(tài)變更
OK, 現(xiàn)在我們大概明白了一個(gè) bundle 的定義和其在 OSGi 框架中的生命周期,前面我們看到控制臺(tái)可以通過(guò)?ss?命令查看所有裝載的 bundle 的狀態(tài),那么 bundle 到底具有哪些狀態(tài),這些狀態(tài)之間是如何變換呢?我們知道了這些狀態(tài)信息,對(duì)我們有何益處?
首先,了解一下一個(gè) bundle 到底有哪些狀態(tài):
表 3. Bundle 狀態(tài)表
| INSTALLED | 就是字面意思,表示這個(gè) bundle 已經(jīng)被成功的安裝了 |
| ? | ? |
| RESOLVED | 很常見(jiàn)的一個(gè)狀態(tài),表示這個(gè) bundle 已經(jīng)成功的被解析(即所有依賴(lài)的類(lèi)、資源都找到了),通常出現(xiàn)在啟動(dòng)前或者停止后 |
| STARTING | 字面意思,正在啟動(dòng),但是還沒(méi)有返回,所以您的 Activator 不要搞的太復(fù)雜 |
| ACTIVE | 活動(dòng)的,這是我們最希望看到的狀態(tài),通常表示這個(gè) bundle 已經(jīng)啟動(dòng)成功,但是不意味著您的 bundle 提供的服務(wù)也是 OK 的 |
| STOPPING | 字面意思,正在停止,還沒(méi)有返回 |
| UNINSTALLED | 卸載了,狀態(tài)不能再發(fā)生變更了 |
下面請(qǐng)看一張經(jīng)典的 OSGi bundle 變更狀態(tài)的圖:
圖 12. OSGi bundle 變更狀態(tài)圖
Bundle 導(dǎo)入導(dǎo)出 package
OK,到現(xiàn)在為止,似乎一切都是新鮮的,但是您似乎在考慮,OSGi 到底有什么優(yōu)勢(shì),下面介紹一下其中的一個(gè)特點(diǎn),幾乎所有的面向組件的框架都需要這一點(diǎn)來(lái)實(shí)現(xiàn)其目的:面向服務(wù)、封裝實(shí)現(xiàn)。這一點(diǎn)在普通的 Java 應(yīng)用是很難做到的,所有的類(lèi)都暴露在 classpath 中,人們可以隨意的查看您的實(shí)現(xiàn),甚至變更您的實(shí)現(xiàn)。這一點(diǎn),對(duì)于希望發(fā)布組件的公司來(lái)說(shuō)是致命的。
圖 13. OSGi bundle 原理
OSGi 很好的解決了這個(gè)問(wèn)題,就像上面的圖顯示的,每個(gè) bundle 都可以有自己公共的部分和隱藏的部分,每個(gè) bundle 也只能看見(jiàn)自己的公共部分、隱藏部分和其它 bundle 的公共部分。
bundle 的 MANIFEST.MF 文件提供了 EXPORT/IMPORT package 的關(guān)鍵字,這樣您可以?xún)H僅 export 出您希望別人看到的包,而隱藏實(shí)現(xiàn)的包。并且您可以為它們編上版本號(hào),這樣可以同時(shí)發(fā)布不同版本的包。
Bundle class path
這一點(diǎn)比較難理解,一般情況下您不需要關(guān)心這個(gè)事情,除非事情出現(xiàn)了問(wèn)題,您發(fā)現(xiàn)明明這個(gè)類(lèi)就在這里,怎么就是報(bào)告 ClassNotFoundException/NoClassDefExcpetion 呢?在您垂頭喪氣、準(zhǔn)備砸掉電腦顯示器之前,請(qǐng)看一下 bundle 中的類(lèi)是如何查找的:
啟動(dòng)級(jí)別 Start level
在 Equinox 環(huán)境中,我們?cè)谂渲?hello world 應(yīng)用的時(shí)候,看到我們將 framework start level 保持為 4,將 Hello world bundle 的 start level 設(shè)置為 5 。 start level 越大,表示啟動(dòng)的順序越靠后。在實(shí)際的應(yīng)用環(huán)境中,我們的 bundle 互相有一定的依賴(lài)關(guān)系,所以在啟動(dòng)的順序上要有所區(qū)別,好比蓋樓,要從打地基開(kāi)始。
實(shí)際上,OSGi 框架最初的 start level 是 0,啟動(dòng)順序如下:
停止順序,也是首先將系統(tǒng)的 start level 設(shè)置為 0:
開(kāi)發(fā)一個(gè)真實(shí)的 OSGi 應(yīng)用程序
我們不能只停留在 hello world 的層面,雖然那曾經(jīng)對(duì)我們很重要 ,但是現(xiàn)實(shí)需要我們能夠使用 OSGi 寫(xiě)出激動(dòng)人心的應(yīng)用程序,它能夠被客戶(hù)接受,被架構(gòu)師認(rèn)可,被程序員肯定。好的,那我們開(kāi)始吧。下面將會(huì)著重介紹一些現(xiàn)實(shí)的應(yīng)用程序可能需要的一些 OSGi 應(yīng)用場(chǎng)景。
發(fā)布和使用服務(wù)
由于 OSGi 框架能夠方便的隱藏實(shí)現(xiàn)類(lèi),所以對(duì)外提供接口是很自然的事情,OSGi 框架提供了服務(wù)的注冊(cè)和查詢(xún)功能。好的,那么我們實(shí)際操作一下,就在 Hello world 工程的基礎(chǔ)上進(jìn)行。
我們需要進(jìn)行下列的步驟:
好的,為了達(dá)到上述要求,我們實(shí)際操作如下:
清單 2. IHello
| 1 2 3 4 5 6 7 8 9 | package osgi.test.helloworld.service; ? public interface IHello { ????/** ?????* 得到 hello 信息的接口 . ?????* @return the hello string. ?????*/ ????String getHello(); } |
清單 3. IHello 接口實(shí)現(xiàn)
| 1 2 3 4 5 6 7 8 | public class DefaultHelloServiceImpl implements IHello { ? ????@Override ????public String getHello() { ????????return "Hello osgi,service"; ????} ? ?} |
我們使用第一種注冊(cè)方式,修改?Activator?類(lèi)的?start?方法,加入注冊(cè)代碼:
清單 4. 加入注冊(cè)代碼
| 1 2 3 4 5 6 7 8 9 | public void start(BundleContext context) throws Exception { ????????? ????System.out.println("hello world"); ????context.registerService( ????????IHello.class.getName(), ????????new DefaultHelloServiceImpl(), ????????null); ????????? } |
圖 14. 選擇導(dǎo)出的服務(wù)包
圖 15. 選擇剛才 export 出去的 osgi.test.helloworld.service 包
清單 5. 加入查詢(xún)和測(cè)試語(yǔ)句
| 1 2 3 4 5 6 7 8 9 10 11 | public void start(BundleContext context) throws Exception { ????System.out.println("hello world2"); ????????? ????/** ????????* Test hello service from bundle1. ????*/ ????IHello hello1 = ????????(IHello) context.getService( ????????context.getServiceReference(IHello.class.getName())); ????????System.out.println(hello1.getHello()); } |
圖 16. 加入對(duì)新的 bundle 的配置信息
圖 17. 執(zhí)行結(jié)果
恭喜您,成功了!
使用事件管理服務(wù) EventAdmin
前面講過(guò),OSGi 規(guī)范定義了很多可用的 bundle,您盡管使用它們完成您的工作,而不必另外再發(fā)明輪子,OSGi 框架定義的事件管理服務(wù),類(lèi)似于 JMS,但是使用上比 JMS 簡(jiǎn)單。
OSGi 整個(gè)框架都離不開(kāi)這個(gè)服務(wù) ,因?yàn)榭蚣芾锩嫒家揽渴录C(jī)制進(jìn)行通信,例如 bundle 的啟動(dòng)、停止,框架的啟動(dòng)、停止,服務(wù)的注冊(cè)、注銷(xiāo)等等等等都是會(huì)發(fā)布事件給監(jiān)聽(tīng)者,同時(shí)也在監(jiān)聽(tīng)其它模塊發(fā)來(lái)的自己關(guān)心的事件。 OSGi 框架的事件機(jī)制主要核心思想是:
說(shuō)明:框架提供的事件服務(wù)、事件提供者、事件監(jiān)聽(tīng)者之間的關(guān)系如下:
圖 18. 事件服務(wù)、事件提供者、事件監(jiān)聽(tīng)者之間的關(guān)系
事件提供者 Publisher 可以獲取 EventAdmin 服務(wù),通過(guò) sendEvent 同步(postEvent 異步)方式提交事件,EventAdmin 服務(wù)負(fù)責(zé)分發(fā)給相關(guān)的監(jiān)聽(tīng)者 EventHandler,調(diào)用它們的?handleEvent?方法。
這里要介紹一個(gè)新的概念 Topics,其實(shí)在 JMS 里面也有用,也就是說(shuō)一個(gè)事件一般都有一個(gè)主題,這樣我們的事件接收者才能按照一定的主題進(jìn)行過(guò)濾處理,例如只處理自己關(guān)心的主題的事件,一般情況下主題是用類(lèi)似于 Java Package 的命名方式命名的。
同步提交(sendEvent)和異步提交(postEvent) 事件的區(qū)別是,同步事件提交后,等框架分發(fā)事件給所有事件接收者之后才返回給事件提交者,而異步事件則一經(jīng)提交就返回了,分發(fā)在另外的線程進(jìn)行處理。
下面的程序演示了事件的定義、事件的發(fā)布、事件處理,同時(shí)還演示了同步和異步處理的效果,以及運(yùn)行環(huán)境的配置。
(約定?osgi.test.helloworld?為 bundle1,osgi.test.helloworld2?為 bundle2)
圖 19. 同步和異步處理演示
清單 6. 定義新的類(lèi) MyEvent
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import java.util.Dictionary; import org.osgi.service.event.Event; ? public class MyEvent extends Event { ????public static final String MY_TOPIC = "osgi/test/helloworld/MyEvent"; ????public MyEvent(String arg0, Dictionary arg1) { ????????super(MY_TOPIC, arg1); ????} ????public MyEvent() { ????????super(MY_TOPIC, null); ????} ? ????public String toString() { ????????return "MyEvent"; ????} ?} |
清單 7. getHello 方法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.event.EventAdmin; ? @Override public String getHello() { ????????? ????//post a event ????ServiceReference ref = ????????context.getServiceReference(EventAdmin.class.getName()); ????if(ref!=null) { ????????eventAdmin = (EventAdmin)context.getService(ref); ????????if(eventAdmin!=null) { ????????????System.out.println("post event started"); ????????????eventAdmin.postEvent(new MyEvent()); ????????????System.out.println("post event returned"); ????????} ????} ????????? ????return "Hello osgi,service"; } |
清單 8. MyEventHandler 類(lèi)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; ? public class MyEventHandler implements EventHandler { ? ????@Override ????public void handleEvent(Event event) { ????????System.out.println("handle event started--"+event); ????????try { ????????????Thread.currentThread().sleep(5*1000); ????????} catch (InterruptedException e) { ????????????? ????????} ????????System.out.println("handle event ok--"+event); ?????} ?} |
清單 9. start 方法
| 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 | import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.Hashtable; ? import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.service.event.EventConstants; import org.osgi.service.event.EventHandler; ? import osgi.test.helloworld.event.MyEvent; import osgi.test.helloworld.service.IAppService; import osgi.test.helloworld.service.IHello; ? public void start(BundleContext context) throws Exception { ????? ????System.out.println("hello world2"); ????????? ????/** ????* 添加事件處理器 . ????*/ ????String[] topics = new String[] {MyEvent.MY_TOPIC}; ????Hashtable<String,String[]> ht = new Hashtable<String,String[]>(); ????ht.put(EventConstants.EVENT_TOPIC, topics); ????EventHandler myHandler = new MyEventHandler(); ????context.registerService( ????????EventHandler.class.getName(), ????????myHandler, ????????ht); ????System.out.println("event handler registered"); ????????? ????/** ????* Test hello service from bundle1. ????*/ ????IHello hello1 = ????????(IHello) context.getService( ????????context.getServiceReference(IHello.class.getName())); ????System.out.println(hello1.getHello()); } |
圖 20. 執(zhí)行
可以看到,post?事件后,不等事件真的被處理完成,就返回了,事件處理在另外的線程執(zhí)行,最后才打印處理完成的語(yǔ)句。然后?ss?看一下,目前我們已經(jīng)有五個(gè) bundle 在運(yùn)行了:
圖 21. ss 查詢(xún)
圖 22. 同步調(diào)用測(cè)試結(jié)果
使用 Http 服務(wù) HttpService
OSGi 的 HTTP 服務(wù)為我們提供了展示 OSGi 的另外一個(gè)途徑,即我們可以專(zhuān)門(mén)提供一個(gè) bundle 用來(lái)作為我們應(yīng)用的 UI,當(dāng)然這個(gè)還比較簡(jiǎn)單,只能提供基本的 HTML 服務(wù)和基本的 Servlet 服務(wù)。如果想提供復(fù)雜的 Jsp/Struts/WebWorks 等等,或者想用現(xiàn)有的 Web 中間件服務(wù)器例如 Tomcat/Resin/WebSphere Application Server 等,都需要另外的途徑來(lái)實(shí)現(xiàn),目前我提供一些基本的使用 HTTP 服務(wù)的方式。
要使用 HTTP 服務(wù),必然有三個(gè)步驟
那么,接下來(lái)我們實(shí)際操作一下:
| 1 2 3 | <html> ????<h1>hello osgi http service</h1> </html> |
清單 10. 加入 HTTP 資源的注冊(cè)代碼
| 1 2 3 | httpService = (HttpService)context.getService ????(context.getServiceReference(HttpService.class.getName())); httpService.registerResources("/", "/pages", null); |
圖 23. 加入 HttpService bundle
圖 24. 運(yùn)行結(jié)果
清單 11. MyServlet 代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import java.io.IOException; import java.util.Date; ? import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; ? public class MyServlet extends HttpServlet { ????/** ?????* 實(shí)現(xiàn)測(cè)試 . ?????* @param request the req. ?????* @param response the res. ?????* @throws IOException io exception. ?????*/ ????public void doGet( ????????????HttpServletRequest request, ????????????HttpServletResponse response ????????????) throws IOException { ????????response.getWriter() ????????????.write("hello osgi http servlet.time now is "+new Date()); ????} ?} |
清單 12. 注冊(cè) servlet 的代碼
| 1 2 | MyServlet ms = new MyServlet(); httpService.registerServlet("/ms", ms, null, null); |
圖 25. 運(yùn)行結(jié)果
分布式部署的實(shí)現(xiàn)
分布式部署的實(shí)現(xiàn)方式一般可以通過(guò) Web 服務(wù)、RMI 等方式,這里簡(jiǎn)單介紹一下基于 RMI 方式的分布式實(shí)現(xiàn)。
在 OSGi 環(huán)境中,并沒(méi)有直接提供分布式部署的支持,我們可以采用 J2SE 提供的 RMI 方式來(lái)實(shí)現(xiàn),但是要考慮 OSGi 的因素,即如果您希望您的服務(wù)既可以本地使用,也可以被遠(yuǎn)程訪問(wèn),那么您應(yīng)該這樣定義接口和類(lèi):
圖 26. 以被遠(yuǎn)程訪問(wèn)需要定義的接口和類(lèi)
說(shuō)明:
| 1 | public interface IAppService extends Remote |
實(shí)際操作如下:
清單 13. IAppService 接口定義
| 1 2 3 4 5 6 7 8 | public interface IAppService extends Remote { ????/** ?????* 得到一個(gè)遠(yuǎn)程服務(wù)的名稱(chēng) . ?????* @return . ?????* @throws RemoteException . ?????*/ ????String getAppName() throws RemoteException; ?} |
注冊(cè)為標(biāo)準(zhǔn)服務(wù):
清單 14. 注冊(cè)為標(biāo)準(zhǔn)服務(wù)
| 1 2 3 4 5 | IAppService appService = new DefaultAppServiceImpl(context); context.registerService( ????IAppService.class.getName(), ????appService, ????null); |
注冊(cè)為遠(yuǎn)程對(duì)象:
清單 15. 注冊(cè)為遠(yuǎn)程對(duì)象
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /** * 啟動(dòng) rmi server . * @param service the service. * @throws RemoteException re. */ private void startRmiServer(IAppService service) throws RemoteException { ????if(registry == null) { ????????registry = LocateRegistry.createRegistry(1099); ????} ????// 注冊(cè) appService 遠(yuǎn)程服務(wù) . ????IAppService theService = ????????(IAppService)UnicastRemoteObject.exportObject(service,0); ????registry.rebind("appService", theService); } |
清單 16. 使用服務(wù)
| 1 2 3 4 | IAppService appService = ????(IAppService)context.getService( ????????context.getServiceReference(IAppService.class.getName())); System.out.println(appService.getAppName()); |
清單 17. 通過(guò) RMI 方式使用服務(wù)
| 1 2 3 4 5 6 7 8 9 | String host = "127.0.0.1"; int port = 1099; try { ????Registry registry = LocateRegistry.getRegistry(host,port); ????appServiceStub = (IAppService) registry.lookup("appService"); } catch (Exception e) { ????e.printStackTrace(); } System.out.println("rmi:"+appServiceStub.getAppName()); |
圖 27. 運(yùn)行結(jié)果
探討 OSGi 應(yīng)用架構(gòu)
設(shè)計(jì)思路
到目前為止,我們已經(jīng)涉及到了 OSGi 的諸多方面,那么在實(shí)際進(jìn)行應(yīng)用程序的架構(gòu)設(shè)計(jì)的時(shí)候我們要考慮哪些因素呢,這一節(jié)我們?cè)敿?xì)討論一下這個(gè)問(wèn)題。
應(yīng)用架構(gòu)的設(shè)計(jì)應(yīng)該充分考慮到可靠性、可擴(kuò)展性、可維護(hù)性等因素,使用了 OSGi 框架后,我們可以更加容易的實(shí)現(xiàn)系統(tǒng)分層,組件化的設(shè)計(jì)方式。通過(guò)使用 HTTP 服務(wù)我們可以設(shè)計(jì)出一個(gè)基于 HTTP 服務(wù)的程序維護(hù)平臺(tái)。架構(gòu)如下:
圖 28. 基于 HTTP 服務(wù)的程序維護(hù)平臺(tái)
說(shuō)明:
這種架構(gòu)的優(yōu)勢(shì)在于:
可維護(hù)性的考慮
一般的應(yīng)用架構(gòu)可能都比較多的考慮可靠性、靈活性、可擴(kuò)展性等,對(duì)可維護(hù)性卻沒(méi)有提供太多的關(guān)注,使用 OSGi 后,將對(duì)可維護(hù)性提供類(lèi)似于 JMX 的支持,當(dāng)然這不需要您實(shí)現(xiàn) MBEAN,就像上述介紹的架構(gòu)設(shè)計(jì),我們?cè)谧钌蠈涌梢栽O(shè)計(jì)一個(gè)基于 HTTP 的維護(hù)層,這樣,提供了一個(gè)小的 Web 控制臺(tái),供管理員進(jìn)行維護(hù)。
維護(hù)的方面包括:
部署 OSGi 應(yīng)用程序
我們的 bundle 不會(huì)只能在 Eclipse 環(huán)境運(yùn)行,我們需要能夠?qū)?bundle 部署到實(shí)際的操作系統(tǒng)中,可能是 Windows/Linux/Unix 等環(huán)境,這要求我們按照下列步驟進(jìn)行:
發(fā)布 Bundle
發(fā)布 bundle 的工作其實(shí)很簡(jiǎn)單,通過(guò) eclipse 平臺(tái)即可完成:
圖 29. 選擇 Deployable plug-ins and fragments
圖 30. 發(fā)布后的目錄結(jié)構(gòu)
Config.ini
為了讓我們的 Jar 文件跑起來(lái),需要 OSGi 的運(yùn)行環(huán)境支持,所以我們需要拷貝一些 system bundle 到 plugins 目錄中,包括:
圖 31. OSGi 的運(yùn)行環(huán)境支持
然后,把 eclipse 目錄的 org.eclipse.osgi_3.3.2.R33x_v20080105 文件拷貝到 osgi.test.deploy 根目錄,重命名為 equinox.jar 文件。
在 osgi.test.deploy 目錄新建子目錄 configuration,新建一個(gè)文本文件 config.ini,用來(lái)配置 bundle 的啟動(dòng)環(huán)境,配置如下:
圖 32. config.ini 配置文件
注意最后兩個(gè) bundle 的啟動(dòng)順序配置格式為:bundle@start_leve:start。
好了,config.ini 也已經(jīng)準(zhǔn)備好了。
啟動(dòng)腳本
下面進(jìn)行啟動(dòng)腳本編寫(xiě),這個(gè)和普通的 Java 程序沒(méi)有什么大的區(qū)別,都是調(diào)用 Java 程序執(zhí)行一個(gè) jar 文件,關(guān)鍵是其中的一些參數(shù)定義:
圖 33. 啟動(dòng)腳本
注意?1/2/3/117/118?參數(shù)都是 OSGi 環(huán)境特有的。
運(yùn)行
雙擊 run.bat,可以看到如下結(jié)果:
圖 34. 運(yùn)行結(jié)果
總結(jié)
通過(guò)閱讀本文您應(yīng)該已經(jīng)掌握了使用 Equinox 開(kāi)發(fā)基于 OSGi 的應(yīng)用程序的方法,了解了其關(guān)鍵的理論知識(shí),還學(xué)習(xí)了如何開(kāi)發(fā)分層的, 模塊化的、分布式的應(yīng)用程序,掌握了在 Windows 平臺(tái)部署基于 Equinox 平臺(tái)的 OSGi 應(yīng)用程序的方法。總體上看,OSGi 能夠有效的降低模塊 之間的耦合程度,將軟件設(shè)計(jì)的開(kāi)閉原則(Open-Close Principle)提高到一個(gè)新的水平,另外 OSGi 也為系統(tǒng)架構(gòu)設(shè)計(jì)提供了更大的靈活性,使得我們開(kāi)發(fā)出像 Eclipse 那樣插件化的平臺(tái)系統(tǒng)不再遙不可及。
相關(guān)主題
- Eclipse.org:獲得有關(guān) Eclipse 的更多詳細(xì)資料。
- Equinox:獲得有關(guān) Equinox 框架的更多詳細(xì)資料。
- OSGi Alliance Service Platform:了解更多關(guān)于 OSGi 的信息,包括 OSGi Release 4 規(guī)范等信息。
- Help – Eclipse SDK:獲得在 Eclipse 下進(jìn)行開(kāi)發(fā)的詳細(xì)幫助文檔。
- “Eclipse 平臺(tái)入門(mén) -- 使用 Eclipse 插件來(lái)編輯、編譯和調(diào)試應(yīng)用程序”(developerWorks,2004 年 2 月):本文為您提供關(guān)于 Eclipse 平臺(tái)的概述,包括其起源和體系結(jié)構(gòu)。
- “了解 Eclipse 插件如何使用 OSGi”(developerWorks,2006 年 9 月):闡明了 Eclipse 與 OSGi 的關(guān)系,還解釋了 OSGi manifest.mf 文件選項(xiàng)以及通過(guò) Eclipse 提供的添加項(xiàng)。
- “基于 OSGi 的面向服務(wù)的組件編程”(developerWorks,2007 年 8 月):本文介紹了基于 OSGi 開(kāi)發(fā)一個(gè)應(yīng)用程序的過(guò)程,讀者可以學(xué)習(xí)如何基于 OSGi 開(kāi)發(fā)自己的應(yīng)用。
- “探索 OSGi 框架的組件運(yùn)行機(jī)制”(developerWorks,2008 年 7 月):本文介紹了 OSGi 框架中的組件(Bundle)的運(yùn)行機(jī)制,并結(jié)合實(shí)際示例加以說(shuō)明。
- developerWorks Eclipse 技術(shù)資源中心:這里匯集了大量和 Eclipse 開(kāi)發(fā)平臺(tái)相關(guān)的技術(shù)文章和教程。
- developerWorks Java 技術(shù)專(zhuān)區(qū):這里有數(shù)百篇關(guān)于 Java 編程方方面面的文章。
- 下載?Eclipse。
- 下載?Equinox OSGi 框架。
from:https://www.ibm.com/developerworks/cn/education/opensource/os-eclipse-osgi/index.html?
總結(jié)
以上是生活随笔為你收集整理的使用 Equinox 开发 OSGi 应用程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: JBoss Modules – Modu
- 下一篇: 探索 OSGi 框架的组件运行机制