日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用 Equinox 开发 OSGi 应用程序

發布時間:2025/3/21 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 Equinox 开发 OSGi 应用程序 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

開始之前

關于本教程

OSGi 是目前動態模塊系統的事實上的工業標準,雖然一開始只是作為嵌入式設備和家庭網關的框架來使用,但是實際上它適用于任何需要模塊化、面向服務、面向組件的應用程序。而 Equinox 則是的 Eclipse 所使用的 OSGi 框架,是 Eclipse 強大的插件體系的基礎,Eclipse 的穩定可靠性也為該框架帶來了聲譽。

本教程就將演示如何在 Eclipse 環境下利用 Equinox 框架進行 OSGi 應用開發。首先解釋了實現上述應用程序所必需了解的基本概念和基礎知識,并結合示例代碼演示 OSGi 開發的一些重要技術,最后探討了基于 OSGi 應用程序一般所采用的架構,以及如何將 Equinox OSGi 應用程序脫離 Eclipse 而部署為一個標準的 Java 應用程序。

目標

在本教程中,您將學習:

  • OSGi 及框架簡介
  • 編寫第一個 OSGi 應用程序
  • 重要的理論知識
  • 開發一個真實的 OSGi 應用程序
  • 探討 OSGi 應用架構
  • 部署 OSGi 應用程序

先決條件

本教程假設讀者熟悉基本 Java 語言以及 Eclipse 開發環境的使用。

系統需求

本教程假設您有一個可以工作的 Eclipse 3.x 環境。如果還沒有,請在?Eclipse 網站?上找到相關下載的鏈接,以幫助您在自己的系統上操作示例步驟以及運行示例代碼。

OSGi 及框架簡介

OSGi 簡介

OSGi 是目前動態模塊系統的事實上的工業標準,雖然一開始只是作為嵌入式設備和家庭網關的框架來使用,但是實際上它適用于任何需要模塊化、面向服務、面向組件的應用程序。

目前 OSGi 規范已經發展到第四版(R4), 由 OSGi 聯合組織(OSGi Alliance)負責進行維護管理,相關的規范資料也可以從該網站獲得。(參考資料)

OSGi 框架

開發基于 OSGi 的應用程序離不開實現了 OSGi 標準的框架,就好比是基于 J2EE 的開發離不開應用服務器一樣。目前比較流行的基于 OSGi R4 標準實現的 OSGi 框架有三個:

  • Equinox:這是大名鼎鼎的 Eclipse 所使用的 OSGi 框架,Eclipse 強大的插件體系就是構建在 OSGi bundles 的基礎之上,Eclipse 的穩定可靠性為該框架帶來了聲譽,而且由于有 IBM 公司的強力支持,其后續的開發和文檔資料也有了一定的保障。一般情況下,我們推薦您使用該框架進行 OSGi 開發。本教程的后續部分也將演示如何使用 Equinox 框架來進行 OSGi 應用程序的開發。

  • Makewave Knopflerfish:這是另外一個比較知名的 OSGi 框架,目前的版本已經支持 R4 規范,其特點在于為應用程序的開發提供了大量的 bundle 。

  • Apache Flex:由 Apache 基金組織開發的面向社區的 OSGi 框架實現,提供了標準的服務和一些有趣的和 OSGi 相關的服務實現。

  • Hello World!編寫第一個 OSGi 應用程序

    準備工作

  • 從附屬資料中下載 Eclipse 3.x 版本,Eclipse 3.2+ 版本已經全面支持 OSGi R4 規范。目前最佳實踐是下載 Eclipse 3.3.2 版本。(下載請見:參考資料)
  • 將 Eclipse 解壓縮到 d:\work\seclipse 目錄,開始我們的 OSGi 之旅。
  • Hello World

    一般情況下,學習一門新的技術,程序員都習慣于首先開發一個 hello world 應用程序,這似乎也是一種“工業標準”。好的,讓我們開始吧,開發一個簡單的 OSGi 應用程序并不難,步驟如下:

  • 建立一個 plug-in 工程,File > New > Project,選擇?Plug-in development > Plug-in Project

    圖 1. 新建 plug-in 工程

  • 在建立工程的第一個向導,填入工程的名稱:osgi.test.helloworld,使用缺省的工程路徑。注意目標平臺的選擇,由于我們的項目是一個通用的 OSGi bundle,所以選擇?equinox?。

    圖 2. 填入工程名及選擇目標平臺

  • 在下一個向導界面中,填入需要的一些插件信息(注意 Eclipse 中的插件概念基本類似于 OSGi 中的 bundle 的概念),這里需要填入的是 OSGi 的 provider(供應商)和 classpath 。如果沒有特別的設計,一般可以忽略這兩個字段 。最后是關于 activator 的部分,如果不是一個 fragment bundle 則需要填入,除非您的 bundle 自己實現框架的事件監聽,這個似乎也沒有必要。因此,建議使用缺省的設置,如圖 3:

    圖 3. 使用缺省設置

    Activator:這是 bundle 啟動時首先調用的程序入口,相當于 Java 模塊中的 main 函數。不同的是,main 需要通過命令行調用,而 OSGi 的 Activator 是被動的接受 OSGi 框架的調用,收到消息后才開始啟動。

    最佳實踐:不要在 Activator 中寫太多的啟動代碼,否則會影響 bundle 啟動速度,相關的服務啟動可以放到服務的監聽器中。

  • 最后一步,不使用任何的模板,所以勾掉缺省的選項,點擊完成,如圖 4:

    圖 4. 勾掉缺省的選項

  • 完成,基本的插件視圖如圖 5,Eclipse 會在工程名下建立相同路徑的 Java Package,其中包含了 Activator 類,插件的配置信息也都放在 MANIFEST.MF 文件中,將來我們相當多的工作都是在其中完成。

    圖 5. 基本的插件視圖

  • 編輯 Activator.java,輸入 hello world 語句,代碼如下:

    清單 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 {

    ?????}

    ?}

    我們可以看到每個 Activator 實際都是實現了?BundleActivator?接口,此接口使 Activator 能夠接受框架的調用。在框架啟動后,啟動每個 bundle 的時候都會調用每個 bundle 的 Activator 。

    注意:bundle 的 Activator 必須含有無參數構造函數,這樣框架才能使用?Class.newInstance()?方式反射構造 bundle 的 Activator 實例。

    這里我們在?start?方法中填入了我們希望輸出的 hello world 字符串。那么,怎么才能啟動這個 bundle 呢?

  • 執行:選擇?Run > Open Run Dialog,進入運行菜單,在 OSGi framework 中右鍵點擊選擇?new?一個新的 OSGi 運行環境,如圖:

    圖 6. 新建 OSGi 運行環境

    在右邊的運行環境對話框中,輸入運行環境的名字、start level 和依賴的插件,由于我們目前不需要其它的第三方插件,因此只需要勾上系統的 org.eclipse.osgi 插件,如果不選擇此插件,hello world 將無法運行。如圖 7,只有當您點擊了?validate bundles?按鈕 ,并且提示無問題之后,才表明您的運行環境基本 OK 了。

    圖 7. 選擇 org.eclipse.osgi插件

    依賴插件的選擇:

    圖 8. 依賴插件的選擇

    好的,如果您的運行環境已經 OK,那么就點擊?Run?吧。

    圖 9. 運行 OSGi 項目

    恭喜您,成功了!

  • OSGi 控制臺

    OSGi 控制臺對于習慣開發普通 Java 應用程序的開發人員來說,還是比較新鮮的。一般來說,通過 OSGi 控制臺,您可以對系統中所有的 bundle 進行生命周期的管理,另外也可以查看系統環境,啟動、停止整個框架,設置啟動級別等等操作。如圖 10,鍵入?SS?就可以查看所有 bundle 的狀態:

    圖 10. 查看所有 bundle 的狀態

    下面列出了主要的控制臺命令:

    表 1. Equinox OSGi 主要的控制臺命令表

    類別命令含義
    控制框架launch啟動框架
    shutdown停止框架
    close關閉、退出框架
    exit立即退出,相當于 System.exit
    init卸載所有 bundle(前提是已經 shutdown)
    setprop設置屬性,在運行時進行
    控制 bundleInstall安裝
    uninstall卸載
    Start啟動
    Stop停止
    Refresh刷新
    Update更新
    展示狀態Status展示安裝的 bundle 和注冊的服務
    Ss展示所有 bundle 的簡單狀態
    Services展示注冊服務的詳細信息
    Packages展示導入、導出包的狀態
    Bundles展示所有已經安裝的 bundles 的狀態
    Headers展示 bundles 的頭信息,即 MANIFEST.MF 中的內容
    Log展示 LOG 入口信息
    其它Exec在另外一個進程中執行一個命令(阻塞狀態)
    Fork和 EXEC 不同的是不會引起阻塞
    Gc促使垃圾回收
    Getprop得到屬性,或者某個屬性
    控制啟動級別Sl得到某個 bundle 或者整個框架的 start level 信息
    Setfwsl設置框架的 start level
    Setbsl設置 bundle 的 start level
    setibsl設置初始化 bundle 的 start level

    MANIFEST.MF

    MANIFEST.MF 可能出現在任何包括主類信息的 Jar 包中,一般位于 META-INF 目錄中,所以此文件并不是一個 OSGi 特有的東西,而僅僅是增加了一些屬性,這樣也正好保持了 OSGi 環境和普通 Java 環境的一致性,便于在老的系統中部署。表 2 列出此文件中的重要屬性及其含義:

    表 2. MANIFEST.MF 文件屬性

    屬性名字含義
    Bundle-ActivatorBundle 的啟動器
    Bundle-SymbolicName名稱,一般使用類似于 JAVA 包路徑的名字命名
    Bundle-Version版本,注意不同版本的同名 bundle 可以同時上線部署
    Export-Package導出的 package 聲明,其它的 bundle 可以直接引用
    Import-Package導入的 package
    Eclipse-LazyStart是否只有當被引用了才啟動
    Require-Bundle全依賴的 bundle,不推薦
    Bundle-ClassPath本 bundle 的 class path,可以包含其它一些資源路徑
    Bundle-RequiredExecutionEnvironment本 bundle 必須的執行環境,例如 jdk 版本聲明

    重要的理論知識

    好的,剛才我們已經從頭到尾開發了一個基于 Equinox 框架的 Hello world 應用程序。我們發現似乎并不是很困難,很多工作 Eclipse 已經幫我們做好了,例如 Activator 代碼框架和 MANIFEST.MF 文件,我們也學會了如何控制 OSGi 的控制臺和編寫 MANIFEST.MF 文件,但是,您真的明白它們是如何運行的么?下面我們將重點介紹一些 OSGi 運行必備的基礎知識。

    什么是 bundle?

    我們已經看到,編寫一個很普通的 Hello world 應用,必須首先創建一個 plug-in 工程,然后編輯其 Activator 類的?start?方法,實際我們這樣做的本質是為 OSGi 運行環境添加了一個 bundle,那么一個 bundle 必須的構成元素是哪些呢?

  • MANIFEST.MF:描述了 bundle 的所有特征,包括名字、輸出的類或者包,導入的類或者包,版本號等等,具體可以參考 表 2. MANIFEST.MF 文件屬性。
  • 代碼:包括 Activator 類和其它一些接口以及實現,這個和普通的 Java 應用程序沒有什么特殊的區別。
  • 資源:當然,一個應用程序不可能沒有資源文件,比如圖片、properties 文件、XML 文件等等,這些資源可以隨 bundle 一起存在,也可以以 fragment bundle 的方式加入。
  • 啟動級別的定義:可以在啟動前使用命令行參數指定,也可以在運行中指定,具體的 start level 的解釋,請參考 后面的說明。
  • 框架做了些什么?

    好了,我們已經明白 bundle 是什么了,也知道如何開發一個基本的 bundle 了,那么我們還必須要明白,我的 bundle 放在 Equinox 框架中,它對我們的 bundle 做了些什么?

    圖 11. Equinox 框架架構

    實際上,目標平臺已經為我們準備了 N 個 bundle,它們提供各種各樣的服務,OSGi 中,這些 bundle 的名字叫 system bundle,就好比精裝修的房子,您只需要拎包入住,不再需要自己鋪地板,裝吊頂了。

    我們的 bundle 進入 Equinox 環境后,OSGi 框架對其做的事情如下:

  • 讀入 bundle 的 headers 信息,即 MANIFEST.MF 文件;
  • 裝載相關的類和資源;
  • 解析依賴的包;
  • 調用其 Activator 的?start?方法,啟動它;
  • 為其提供框架事件、服務事件等服務;
  • 調用其 Activator 的?stop?方法,停止它;
  • Bundle 的狀態變更

    OK, 現在我們大概明白了一個 bundle 的定義和其在 OSGi 框架中的生命周期,前面我們看到控制臺可以通過?ss?命令查看所有裝載的 bundle 的狀態,那么 bundle 到底具有哪些狀態,這些狀態之間是如何變換呢?我們知道了這些狀態信息,對我們有何益處?

    首先,了解一下一個 bundle 到底有哪些狀態:

    表 3. Bundle 狀態表

    狀態名字含義
    INSTALLED就是字面意思,表示這個 bundle 已經被成功的安裝了
    ??
    RESOLVED很常見的一個狀態,表示這個 bundle 已經成功的被解析(即所有依賴的類、資源都找到了),通常出現在啟動前或者停止后
    STARTING字面意思,正在啟動,但是還沒有返回,所以您的 Activator 不要搞的太復雜
    ACTIVE活動的,這是我們最希望看到的狀態,通常表示這個 bundle 已經啟動成功,但是不意味著您的 bundle 提供的服務也是 OK 的
    STOPPING字面意思,正在停止,還沒有返回
    UNINSTALLED卸載了,狀態不能再發生變更了

    下面請看一張經典的 OSGi bundle 變更狀態的圖:

    圖 12. OSGi bundle 變更狀態圖

    Bundle 導入導出 package

    OK,到現在為止,似乎一切都是新鮮的,但是您似乎在考慮,OSGi 到底有什么優勢,下面介紹一下其中的一個特點,幾乎所有的面向組件的框架都需要這一點來實現其目的:面向服務、封裝實現。這一點在普通的 Java 應用是很難做到的,所有的類都暴露在 classpath 中,人們可以隨意的查看您的實現,甚至變更您的實現。這一點,對于希望發布組件的公司來說是致命的。

    圖 13. OSGi bundle 原理

    OSGi 很好的解決了這個問題,就像上面的圖顯示的,每個 bundle 都可以有自己公共的部分和隱藏的部分,每個 bundle 也只能看見自己的公共部分、隱藏部分和其它 bundle 的公共部分。

    bundle 的 MANIFEST.MF 文件提供了 EXPORT/IMPORT package 的關鍵字,這樣您可以僅僅 export 出您希望別人看到的包,而隱藏實現的包。并且您可以為它們編上版本號,這樣可以同時發布不同版本的包。

    Bundle class path

    這一點比較難理解,一般情況下您不需要關心這個事情,除非事情出現了問題,您發現明明這個類就在這里,怎么就是報告 ClassNotFoundException/NoClassDefExcpetion 呢?在您垂頭喪氣、準備砸掉電腦顯示器之前,請看一下 bundle 中的類是如何查找的:

  • 首先,它會找 JRE,這個很明顯,這個實際是通過系統環境的?JAVA_HOME?中找到的,路徑一般是 JAVA_HOME/lib/rt.jar、tools.jar 和 ext 目錄,endorsed 目錄。
  • 其次,它會找 system bundle 導出的包。
  • 然后,它會找您的 import 的包,這個實際包含兩種:一種是直接通過 require-bundle 的方式全部導入的,還有一種就是前面講的通過 import package 方式導入的包。
  • 查找它的 fragment bundle,如果有的話。
  • 如果還沒有找到,則會找自己的 classpath 路徑(每個 bundle 都有自己的類路徑)。
  • 最后它會嘗試根據 DynamicImport-Package 屬性查找的引用。
  • 啟動級別 Start level

    在 Equinox 環境中,我們在配置 hello world 應用的時候,看到我們將 framework start level 保持為 4,將 Hello world bundle 的 start level 設置為 5 。 start level 越大,表示啟動的順序越靠后。在實際的應用環境中,我們的 bundle 互相有一定的依賴關系,所以在啟動的順序上要有所區別,好比蓋樓,要從打地基開始。

    實際上,OSGi 框架最初的 start level 是 0,啟動順序如下:

  • 將啟動級別加一,如果發現有匹配的 bundle(即 bundle 的啟動級別和目前的啟動級別相等),則啟動這個 bundle;
  • 繼續第一步,直到發現已經啟動了所有的 bundle,且活動啟動級別和最后的啟動的 bundle 啟動級別相同。
  • 停止順序,也是首先將系統的 start level 設置為 0:

  • 由于系統當前活動啟動級別大于請求的 start level,所以系統首先停止等于當前活動啟動級別的 bundle;
  • 將活動啟動級別減一,繼續第一步,直到發現活動啟動級別和請求級別相等,都是 0。
  • 開發一個真實的 OSGi 應用程序

    我們不能只停留在 hello world 的層面,雖然那曾經對我們很重要 ,但是現實需要我們能夠使用 OSGi 寫出激動人心的應用程序,它能夠被客戶接受,被架構師認可,被程序員肯定。好的,那我們開始吧。下面將會著重介紹一些現實的應用程序可能需要的一些 OSGi 應用場景。

    發布和使用服務

    由于 OSGi 框架能夠方便的隱藏實現類,所以對外提供接口是很自然的事情,OSGi 框架提供了服務的注冊和查詢功能。好的,那么我們實際操作一下,就在 Hello world 工程的基礎上進行。

    我們需要進行下列的步驟:

  • 定義一個服務接口,并且 export 出去供其它 bundle 使用;
  • 定義一個缺省的服務實現,并且隱藏它的實現;
  • Bundle 啟動后,需要將服務注冊到 Equinox 框架;
  • 從框架查詢這個服務,并且測試可用性。
  • 好的,為了達到上述要求,我們實際操作如下:

  • 定義一個新的包?osgi.test.helloworld.service?,用來存放接口。單獨一個 package 的好處是,您可以僅僅 export 這個 package 給其它 bundle 而隱藏所有的實現類
  • 在上述的包中新建接口?IHello,提供一個簡單的字符串服務,代碼如下:

    清單 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();

    }

  • 再新建一個新的包?osgi.test.helloworld.impl,用來存放實現類。
  • 在上述包中新建?DefaultHelloServiceImpl?類,實現上述接口:

    清單 3. IHello 接口實現

    1

    2

    3

    4

    5

    6

    7

    8

    public class DefaultHelloServiceImpl implements IHello {

    ?

    ????@Override

    ????public String getHello() {

    ????????return "Hello osgi,service";

    ????}

    ?

    ?}

  • 注冊服務,OSGi 框架提供了兩種注冊方式,都是通過?BundleContext?類實現的:
  • registerService(String,Object,Dictionary)?注冊服務對象?object?到接口名?String?下,可以攜帶一個屬性字典?Dictionary;
  • registerService(String[],Object,Dictionary)?注冊服務對象?object?到接口名數組?String[]?下,可以攜帶一個屬性字典?Dictionary,即一個服務對象可以按照多個接口名字注冊,因為類可以實現多個接口;
  • 我們使用第一種注冊方式,修改?Activator?類的?start?方法,加入注冊代碼:

    清單 4. 加入注冊代碼

    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);

    ?????????

    }

  • 為了讓我們的服務能夠被其它 bundle 使用,必須在 MANIFEST.MF 中對其進行導出聲明,雙擊 MANIFEST.MF,找到?runtime > exported packages > 點擊 add,如圖,選擇?service?包即可:

    圖 14. 選擇導出的服務包

  • 另外新建一個類似于 hello world 的 bundle 叫:osgi.test.helloworld2,用于測試osgi.test.helloworld?bundle 提供的服務的可用性;
  • 添加 import package:在第二個 bundle 的 MANIFEST.MF 文件中,找到?dependencies > Imported packages > Add …,選擇我們剛才 export 出去的?osgi.test.helloworld.service?包:

    圖 15. 選擇剛才 export 出去的 osgi.test.helloworld.service 包

  • 查詢服務:同樣,OSGi 框架提供了兩種查詢服務的引用?ServiceReference?的方法:
  • getServiceReference(String):根據接口的名字得到服務的引用;
  • getServiceReferences(String,String):根據接口名和另外一個過濾器名字對應的過濾器得到服務的引用;
  • 這里我們使用第一種查詢的方法,在?osgi.test.helloworld2?bundle 的?Activator?的?start?方法加入查詢和測試語句:

    清單 5. 加入查詢和測試語句

    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());

    }

  • 修改運行環境,因為我們增加了一個 bundle,所以說也需要在運行配置中加入對新的 bundle 的配置信息,如下圖所示:

    圖 16. 加入對新的 bundle 的配置信息

  • 執行,得到下列結果:

    圖 17. 執行結果

  • 恭喜您,成功了!

    使用事件管理服務 EventAdmin

    前面講過,OSGi 規范定義了很多可用的 bundle,您盡管使用它們完成您的工作,而不必另外再發明輪子,OSGi 框架定義的事件管理服務,類似于 JMS,但是使用上比 JMS 簡單。

    OSGi 整個框架都離不開這個服務 ,因為框架里面全都依靠事件機制進行通信,例如 bundle 的啟動、停止,框架的啟動、停止,服務的注冊、注銷等等等等都是會發布事件給監聽者,同時也在監聽其它模塊發來的自己關心的事件。 OSGi 框架的事件機制主要核心思想是:

  • 用戶(程序員)可以自己按照接口定義自己的事件類型
  • 用戶可以監聽自己關心的事件或者所有事件
  • 用戶可以將事件同步的或者異步的提交給框架,由框架負責同步的或者異步的分發給監聽者
  • 說明:框架提供的事件服務、事件提供者、事件監聽者之間的關系如下:

    圖 18. 事件服務、事件提供者、事件監聽者之間的關系

    事件提供者 Publisher 可以獲取 EventAdmin 服務,通過 sendEvent 同步(postEvent 異步)方式提交事件,EventAdmin 服務負責分發給相關的監聽者 EventHandler,調用它們的?handleEvent?方法。

    這里要介紹一個新的概念 Topics,其實在 JMS 里面也有用,也就是說一個事件一般都有一個主題,這樣我們的事件接收者才能按照一定的主題進行過濾處理,例如只處理自己關心的主題的事件,一般情況下主題是用類似于 Java Package 的命名方式命名的。

    同步提交(sendEvent)和異步提交(postEvent) 事件的區別是,同步事件提交后,等框架分發事件給所有事件接收者之后才返回給事件提交者,而異步事件則一經提交就返回了,分發在另外的線程進行處理。

    下面的程序演示了事件的定義、事件的發布、事件處理,同時還演示了同步和異步處理的效果,以及運行環境的配置。

    (約定?osgi.test.helloworld?為 bundle1,osgi.test.helloworld2?為 bundle2)

    圖 19. 同步和異步處理演示

  • 在 bundle1 中的 MANIFEST.MF 的 dependency 頁面中定義引入新的包:org.osgi.service.event。
  • 在 bundle1 中的?osgi.test.helloworld.event?包中定義新的類?MyEvent,如下(注意其中的 topic 定義的命名方式):

    清單 6. 定義新的類 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";

    ????}

    ?}

  • 在 bundle1 的?DefaultHelloServiceHandler?類的?getHello?方法中,加入提交事件的部分,這樣 bundle2 在調用這個服務的時候,將觸發一個事件,由于采用了 Post 方式,應該是立刻返回的,所以在?postEvent?前后打印了語句進行驗證。

    清單 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";

    }

  • 定義監聽者,在 bundle2 中,也引入 osgi 的事件包,然后定義一個新的類:MyEventHandler?類,用來處理事件,這里故意加入了一個延遲,是為了測試異步事件的調用,實現如下:

    清單 8. MyEventHandler 類

    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);

    ?????}

    ?}

  • 注冊監聽器,有了事件處理器,還需要注冊到監聽器中,這里在 bundle2 的?Activator?類中加入此監聽器,也就是調用?context.registerService?方法注冊這個監聽服務,和普通服務的區別是要帶一個監聽事件類型的 topic,這里列出?Activator?類的?start?方法:

    清單 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());

    }

  • 為了使用框架的事件服務,需要修改運行環境,加入兩個系統 bundle,分別是:
  • org.eclipse.osgi.services
  • org.eclipse.equinox.event
  • 好了一切準備好了,執行:

    圖 20. 執行

    可以看到,post?事件后,不等事件真的被處理完成,就返回了,事件處理在另外的線程執行,最后才打印處理完成的語句。然后?ss?看一下,目前我們已經有五個 bundle 在運行了:

    圖 21. ss 查詢

  • OK,修改代碼以測試同步調用的情況,我們只需要把提交事件的代碼由?postEvent?修改為sendEvent?即可。其它不變,測試結果如下:

    圖 22. 同步調用測試結果

  • 使用 Http 服務 HttpService

    OSGi 的 HTTP 服務為我們提供了展示 OSGi 的另外一個途徑,即我們可以專門提供一個 bundle 用來作為我們應用的 UI,當然這個還比較簡單,只能提供基本的 HTML 服務和基本的 Servlet 服務。如果想提供復雜的 Jsp/Struts/WebWorks 等等,或者想用現有的 Web 中間件服務器例如 Tomcat/Resin/WebSphere Application Server 等,都需要另外的途徑來實現,目前我提供一些基本的使用 HTTP 服務的方式。

    要使用 HTTP 服務,必然有三個步驟

  • 獲取 HttpService,可以像 上述方式 那樣通過 context 的?getService?方法獲得引用;
  • 使用 HttpService 的引用注冊資源或者注冊 Servlet:
  • registerResources:注冊資源,提供本地路徑、虛擬訪問路徑和相關屬性即可完成注冊,客戶可以通過虛擬訪問路徑 + 資源名稱訪問到資源
  • registerServlet:注冊 Servlet,提供標準 Servlet 實例、虛擬訪問路徑、相關屬性以及 HttpContext(可以為 null)后即可完成注冊,客戶可以直接通過虛擬訪問路徑獲取該 Servlet 的訪問
  • 修改運行環境,加入支持 http 服務的 bundle
  • 那么,接下來我們實際操作一下:

  • 首先,在 bundle1 的 src 中建立一個新的 package,名字叫 pages,用來存放一些 HTML 的資源文件,為了提供一個基本的 HTTP 服務,我們需要提供一個 index.html,內容如下:

    1

    2

    3

    <html>

    ????<h1>hello osgi http service</h1>

    </html>

  • 第二步,注冊資源服務,首先我們要為 bundle1 加入 HTTP 服務的 package 引用,即修改 MANIFEST.MF 文件的 dependencies,加入包:org.osgi.service.http;version="1.2.0",然后在?Activator?類的?start?方法中加入 HTTP 資源的注冊:

    清單 10. 加入 HTTP 資源的注冊代碼

    1

    2

    3

    httpService = (HttpService)context.getService

    ????(context.getServiceReference(HttpService.class.getName()));

    httpService.registerResources("/", "/pages", null);

  • 修改運行環境,在?target platform?的 bundle 列表中加入:org.eclipse.equinox.http?和javax.servlet?這兩個 bundle 保證了 HttpService 的可用性:

    圖 23. 加入 HttpService bundle

  • 運行,然后打開 IE 訪問本機?http://localhost/index.html:

    圖 24. 運行結果

  • 加入 servlet,首先在 bundle1 建立一個包:osgi.test.hellworld.servlet,建立一個新的類:MyServlet,要從?HttpServlet?基類繼承,實現其?doGet?方法,如下:

    清單 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 {

    ????/**

    ?????* 實現測試 .

    ?????* @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());

    ????}

    ?}

  • 注冊 servlet,在?Activator?類的?start?方法中加入注冊 servlet 的代碼,如下:

    清單 12. 注冊 servlet 的代碼

    1

    2

    MyServlet ms = new MyServlet();

    httpService.registerServlet("/ms", ms, null, null);

  • 運行,打開 IE 訪問?http://localhost/ms?后得到結果:

    圖 25. 運行結果

  • 分布式部署的實現

    分布式部署的實現方式一般可以通過 Web 服務、RMI 等方式,這里簡單介紹一下基于 RMI 方式的分布式實現。

    在 OSGi 環境中,并沒有直接提供分布式部署的支持,我們可以采用 J2SE 提供的 RMI 方式來實現,但是要考慮 OSGi 的因素,即如果您希望您的服務既可以本地使用,也可以被遠程訪問,那么您應該這樣定義接口和類:

    圖 26. 以被遠程訪問需要定義的接口和類

    說明:

  • Remote?接口是 J2SE 定義的遠程對象必須實現的接口;
  • IAppService?接口是 OSGi 服務接口,繼承了?Remote?接口,即定義方式為:

    1

    public interface IAppService extends Remote

  • AppServiceImpl?實現了?IAppService?接口,此外注意里面的方法都拋出?RemoteException?異常;
  • 實際操作如下:

  • 在 bundle1 的?service?包中加入?IAppService?接口的定義,繼承自?Remote?接口,定義個方法:

    清單 13. IAppService 接口定義

    1

    2

    3

    4

    5

    6

    7

    8

    public interface IAppService extends Remote {

    ????/**

    ?????* 得到一個遠程服務的名稱 .

    ?????* @return .

    ?????* @throws RemoteException .

    ?????*/

    ????String getAppName() throws RemoteException;

    ?}

  • 把這個接口注冊為 OSGi 標準服務以及一個 RMI 服務對象如下:

    注冊為標準服務:

    清單 14. 注冊為標準服務

    1

    2

    3

    4

    5

    IAppService appService = new DefaultAppServiceImpl(context);

    context.registerService(

    ????IAppService.class.getName(),

    ????appService,

    ????null);

    注冊為遠程對象:

    清單 15. 注冊為遠程對象

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    /**

    * 啟動 rmi server .

    * @param service the service.

    * @throws RemoteException re.

    */

    private void startRmiServer(IAppService service) throws RemoteException {

    ????if(registry == null) {

    ????????registry = LocateRegistry.createRegistry(1099);

    ????}

    ????// 注冊 appService 遠程服務 .

    ????IAppService theService =

    ????????(IAppService)UnicastRemoteObject.exportObject(service,0);

    ????registry.rebind("appService", theService);

    }

  • 在 bundle2 中通過 OSGi 方式使用這個服務:

    清單 16. 使用服務

    1

    2

    3

    4

    IAppService appService =

    ????(IAppService)context.getService(

    ????????context.getServiceReference(IAppService.class.getName()));

    System.out.println(appService.getAppName());

  • 通過 RMI 方式使用這個服務:

    清單 17. 通過 RMI 方式使用服務

    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. 運行結果

  • 探討 OSGi 應用架構

    設計思路

    到目前為止,我們已經涉及到了 OSGi 的諸多方面,那么在實際進行應用程序的架構設計的時候我們要考慮哪些因素呢,這一節我們詳細討論一下這個問題。

    應用架構的設計應該充分考慮到可靠性、可擴展性、可維護性等因素,使用了 OSGi 框架后,我們可以更加容易的實現系統分層,組件化的設計方式。通過使用 HTTP 服務我們可以設計出一個基于 HTTP 服務的程序維護平臺。架構如下:

    圖 28. 基于 HTTP 服務的程序維護平臺

    說明:

  • 通用第三方庫層:這一層包括了常用的第三方庫,例如 apache commons,jfreechart,xml 包等等,這一層需要將這些包全部 export 出去,這樣上層就可以直接通過 require bundle 的方式使用這些包。
  • 業務模型定義層:這一層依賴于(require-bundle)通用第三方庫層,定義了應用的業務模型,例如各種 JavaBeans,也可以在這一層提供額外的應用統一配置服務,即為上層應用提供配置文件的管理服務。
  • 業務邏輯實現層:這一層依賴于(require-bundle)通用第三方庫層,還依賴于 (import package) 業務模型定義層提供的業務模型,定義了應用的業務邏輯,這一層可以細分為:
  • DAO(Database Access Object)服務層:即為上層應用提供數據庫存取服務的層,其 export 出去的接口全部都是和數據庫操作相關的;
  • Service 層:為 UI 層提供的業務邏輯的封裝層,這樣 UI 層只需要執行 service 層的接口即可,可以將更多的精力放在 UI 的設計;
  • 展現維護層:這一層依賴于(require-bundle)通用第三方庫層,和下面各層提供的管理服務接口,基于 HTTP 服務的方式提供應用的維護,例如配置文件的在線修改、服務的管理,bundle 的管理,日志的管理,內存的管理等等,這些都可以以“ RUNTIME ”的方式展現,管理員或者維護人員操作的就是 Equinox 運行環境。還可以實現大部分的操作不需要重啟 JVM,這一點類似于 JMX。
  • 事件服務:事件服務層是 OSGi 框架提供的標準服務之一,為除了通用第三方庫層以外的各層提供事件服務,包括同步、異步的通知各種事件、發布各種事件等。通過事件服務,可以實現各層之間的聯動。
  • 這種架構的優勢在于:

  • 各層只用關心自己的業務,例如通用第三方庫層只需要 export,其它事情不用管,它也沒有自己的 Activator 類,業務模型定義層只需要關心業務模型,而不必關心業務的流程,業務邏輯層中的 DAO 層則只需要關心數據庫操作,service 層則負責組合業務流程。各司其職,這樣才能精于自己的模塊;
  • 較好的可維護性:最上層的維護展現層,為管理員提供了一個 OSGi 應用的管理窗口,提供在線重啟服務、管理各個 bundle 和服務的能力,提供了類似于 JMX 的能力;
  • 統一的事件管理框架:為各層定義了統一的事件管理接口,基于 TOPIC 方式的事件監聽機制能夠有效的過濾事件,而且提供了異步、同步兩種方式對事件進行處理,可以說有相當大的靈活性。
  • 可維護性的考慮

    一般的應用架構可能都比較多的考慮可靠性、靈活性、可擴展性等,對可維護性卻沒有提供太多的關注,使用 OSGi 后,將對可維護性提供類似于 JMX 的支持,當然這不需要您實現 MBEAN,就像上述介紹的架構設計,我們在最上層可以設計一個基于 HTTP 的維護層,這樣,提供了一個小的 Web 控制臺,供管理員進行維護。

    維護的方面包括:

  • 系統維護
  • Bundle 的管理:包括每個 bundle 的更新、停止、啟動;
  • 服務的管理:包括運行環境注冊的服務的列表、停止、啟動;
  • 系統所有服務的重啟、停止、啟動;
  • 系統狀態的監控
  • 對各個業務層提供的服務的狀態進行實時監視、統計;
  • 對各個業務層提供服務的狀態進行控制,通過 OSGi 事件的方式進行通知;
  • 系統日志的管理
  • 對系統中各個層的日志進行統一列表、查看;
  • 對系統中所有操作進行統一日志記錄、管理;
  • 配置管理
  • 對各個業務模塊需要的配置文件進行統一展示;
  • 對各個業務模塊提供的配置文件提供在線編輯、提交功能;
  • 對修改后的配置文件提供實時上線的功能;
  • 其它
  • 維護系統的登錄、登出;
  • 維護系統自審計;
  • 維護系統權限控制;
  • 部署 OSGi 應用程序

    我們的 bundle 不會只能在 Eclipse 環境運行,我們需要能夠將 bundle 部署到實際的操作系統中,可能是 Windows/Linux/Unix 等環境,這要求我們按照下列步驟進行:

  • 發布 bundle,即將我們的 plug-in 工程發布為可以執行的 Jar 文件或者其它格式;
  • 配置 config.ini,指出 bundle 的運行環境,啟動順序等;
  • 啟動腳本編寫,編寫能夠運行在各種操作系統的腳本;
  • 發布 Bundle

    發布 bundle 的工作其實很簡單,通過 eclipse 平臺即可完成:

  • 選擇 Eclipse 的?plug-in?視圖的?File -> Export,從彈出的窗口中選擇?Deployable plug-ins and fragments

    圖 29. 選擇 Deployable plug-ins and fragments

  • 在下一個窗口中,選擇想要發布的 bundle,這里我們選擇?osgi.test.helloworldosgi.test.helloworld2?工程,下面的 options 里面選擇“打包為一個 Jar ”,目標目錄選擇為?osgi.test.deploy?目錄(在當前的 workspace 下面);
  • 選擇確定,發布后的目錄結構如下圖所示,eclipse 幫我們在部署根目錄下建立了一個新的子目錄 plugins(類似于 eclipse,因為 eclipse 就是基于 OSGi 的):

    圖 30. 發布后的目錄結構

  • 好了,到這里,發布工作完成。
  • Config.ini

    為了讓我們的 Jar 文件跑起來,需要 OSGi 的運行環境支持,所以我們需要拷貝一些 system bundle 到 plugins 目錄中,包括:

    圖 31. OSGi 的運行環境支持

    然后,把 eclipse 目錄的 org.eclipse.osgi_3.3.2.R33x_v20080105 文件拷貝到 osgi.test.deploy 根目錄,重命名為 equinox.jar 文件。

    在 osgi.test.deploy 目錄新建子目錄 configuration,新建一個文本文件 config.ini,用來配置 bundle 的啟動環境,配置如下:

    圖 32. config.ini 配置文件

    注意最后兩個 bundle 的啟動順序配置格式為:bundle@start_leve:start。

    好了,config.ini 也已經準備好了。

    啟動腳本

    下面進行啟動腳本編寫,這個和普通的 Java 程序沒有什么大的區別,都是調用 Java 程序執行一個 jar 文件,關鍵是其中的一些參數定義:

    圖 33. 啟動腳本

    注意?1/2/3/117/118?參數都是 OSGi 環境特有的。

    運行

    雙擊 run.bat,可以看到如下結果:

    圖 34. 運行結果

    總結

    通過閱讀本文您應該已經掌握了使用 Equinox 開發基于 OSGi 的應用程序的方法,了解了其關鍵的理論知識,還學習了如何開發分層的, 模塊化的、分布式的應用程序,掌握了在 Windows 平臺部署基于 Equinox 平臺的 OSGi 應用程序的方法??傮w上看,OSGi 能夠有效的降低模塊 之間的耦合程度,將軟件設計的開閉原則(Open-Close Principle)提高到一個新的水平,另外 OSGi 也為系統架構設計提供了更大的靈活性,使得我們開發出像 Eclipse 那樣插件化的平臺系統不再遙不可及。

    相關主題

    • Eclipse.org:獲得有關 Eclipse 的更多詳細資料。
    • Equinox:獲得有關 Equinox 框架的更多詳細資料。
    • OSGi Alliance Service Platform:了解更多關于 OSGi 的信息,包括 OSGi Release 4 規范等信息。
    • Help – Eclipse SDK:獲得在 Eclipse 下進行開發的詳細幫助文檔。
    • “Eclipse 平臺入門 -- 使用 Eclipse 插件來編輯、編譯和調試應用程序”(developerWorks,2004 年 2 月):本文為您提供關于 Eclipse 平臺的概述,包括其起源和體系結構。
    • “了解 Eclipse 插件如何使用 OSGi”(developerWorks,2006 年 9 月):闡明了 Eclipse 與 OSGi 的關系,還解釋了 OSGi manifest.mf 文件選項以及通過 Eclipse 提供的添加項。
    • “基于 OSGi 的面向服務的組件編程”(developerWorks,2007 年 8 月):本文介紹了基于 OSGi 開發一個應用程序的過程,讀者可以學習如何基于 OSGi 開發自己的應用。
    • “探索 OSGi 框架的組件運行機制”(developerWorks,2008 年 7 月):本文介紹了 OSGi 框架中的組件(Bundle)的運行機制,并結合實際示例加以說明。
    • developerWorks Eclipse 技術資源中心:這里匯集了大量和 Eclipse 開發平臺相關的技術文章和教程。
    • developerWorks Java 技術專區:這里有數百篇關于 Java 編程方方面面的文章。
    • 下載?Eclipse。
    • 下載?Equinox OSGi 框架。

    from:https://www.ibm.com/developerworks/cn/education/opensource/os-eclipse-osgi/index.html?

    總結

    以上是生活随笔為你收集整理的使用 Equinox 开发 OSGi 应用程序的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。