OSGi Bundle的構建策略及實踐

來源:互聯網
上載者:User
軟體編程發展到今天可以看作是一個量變引發質變的過程。最初,程式開發面向過程,開發人員需要編寫大量的過程代碼,隨著過程代碼的不斷積累(量變產 生),從代碼維護和重用的角度,過程開發變得越來越不適應,質變產生,物件導向的開發逐漸被採用。由於物件導向的開發很好的封裝了過程,而且從物件導向的 角度可以很好的描述實際應用中的需求模型,因此物件導向的開發逐漸成為主流。同樣,隨著物件導向開發的不斷應用(量變產生),出現了大量的可複用的類及 包,維護這些類/包變得越來越困難,而且,儘管物件導向的編程機制可以很好的適應小規模應用的開發,但隨著應用系統的規模越來越大,如同用細小的沙粒構建 堤壩,物件導向的機制難於適應,質變產生,面向組件的開發被引入開發過程。面向組件的開發目前仍可以認為是處於一種探索狀態,目前還不存在一種統一的標準 可以遵循。

      從另一個角度,曆史是今天存在的基礎,沒有曆史就沒有今天。同樣的道理,應用系統採用面向組件的開發實現,組件仍需要對象來構造,而對象在一定程度上是封 裝的功能過程,三者相輔相成。不管是從上而下還是從下而上,應用系統需求模型在其實現過程中,系統設計者應該充分關注組件、對象和功能過程三個層面。

      OSGi可以看作是面向組件開發的一種思路和基礎環境。在OSGi環境中實現應用系統的需求模型要求開發人員對組件、對象開發具有充分的理解。衡量一個應 用系統實現好壞的標準之一是該系統是否是松耦合高內聚。物件導向的開發實現此目標的關鍵是介面/抽象的應用,OSGi面向組件的開發在此基礎之上充分利用 了對象類包封裝的機制。

1. Bundle的構建策略

      通常,當我們構建應用系統時,我們需要充分重視所構建應用的業務需求模型即領域模型,同樣的,在用軟體代碼實現業務需求模型時,我們也應當對軟體系統的架 構模型給予充分的重視。採用OSGi技術實現應用系統時,展現在我們面前的將是一個個的Bundle組件,此時,我們必須首先弄清楚我們要構建什麼樣的 Bundle。

1.1 明確Bundle的構建需求及實現粒度

      從需求模型的角度看,Bundle可以看作是需求模型中的一個功能模組實現,因此,在開發Bundle之前系統開發人員必須明確Bundle的功能需求。 需求模型中的功能模組的邊界可大可小,同樣,Bundle的實現粒度也是可大可小,極端情況下,小的Bundle可以僅實現一個微小的功能,大的 Bundle可以實現整個業務系統。

      以記錄日誌為例,如果記錄日誌僅輸出到控制台,則一個類就可以實現整個的功能,如果記錄日誌輸出到資料庫系統,則開發人員需要在整個功能用一個 Bundle實現,還是將日誌記錄功能和記錄資訊的資料庫儲存分為兩個不同的Bundle實現,兩個選擇甚至更多的選擇中做出決策。通常這個問題在現有的 軟體開發方式中不需要過多的重視,但在OSGi開發中,開發人員必鬚根據需求確定Bundle的實現粒度。

1.2 確定Bundle的類型

      我們在物件導向的系統開發過程中,經常會將一些通用的功能設計成為工具類,多個工具類封裝為一個工具類包。由於OSGi開發建立在物件導向的開發之上, Bundle的開發也存在這種特點。OSGi開發的另一個特點是服務機制的引入,一個Bundle可以將其提供的功能發布成為一個或多個服務,供其他 Bundle尋找使用。此外,建立在OSGi環境之上的組件開發也區別於通常的組件開發方式,因為我們可以利用OSGi環境提供的某些特性(參見下一節 充分利用OSGi環境的特性)。綜上所述,我們可以將Bundle劃分為如下幾種類型(實際開發中,並不存在清晰的邊界):

  • Utility Bundle

      這種Bundle與通常開發方式中的工具類或工具類包沒有本質的區別,僅僅是將這些工具類發布到OSGi環境中,這些工具類也不依賴任何的OSGi環境或 特性。通常,這種Bundle特別適合目前存在的眾多的第三方組件引入到OSGi環境中直接供OSGi開發人員使用。例如,我們可以將Apache開源項 目Log4j發布的工具包通過修改其META-INF目錄下的MANIFEST.MF檔案,添加Bundle的中繼資料資訊就可以直接將其封裝為可供 OSGi環境中其他Bundle使用的工具類Bundle。

  • 依賴OSGi特性的Bundle

      這類Bundle可以被其他Bundle引用,為其提供功能,但是這類Bundle不能離開OSGi運行環境,否則不能使用。舉例來說,一個提供資料緩衝 功能的Bundle可能使用Bundle的資料存放區區來快取資料。Bundle的資料存放區區的位置對Bundle開發人員來說是透明的(參見下一節 充分利用OSGi環境的特性)。如果將該Bundle的資料緩衝功能遷移到OSGi運行環境之外,則必須修改實現添加對緩衝區位置的處理功能。

  • 引用和(或)發布OSGi服務的Bundle

      這類Bundle也可以認為是依賴OSGi特性的Bundle,唯一的區別就是,該類Bundle充分利用的OSGi中服務的特性,引用其他Bundle 發布的服務和(或)向OSGi環境中發布自己的服務。OSGi環境提供的服務機制可以使得系統的實現遵循最大程度的松耦合。

1.3 隱藏Bundle的功能介面

      組件開發除了應對系統的開發規模之外,其最重要的目標是降低系統耦合。開發人員採用OSGi Bundle開發時,應盡量屏蔽Bundle所提供功能的內部實現機制,僅將為其他Bundle提供的互動介面暴露出來。在OSGi中,這可以通過兩種方 式,一種是通過Export供其他Bundle引用的類包;一種是向OSGi環境發布服務。

      在Equinox中,Bundle內部實現的類包通常以"internal"標註,如org.eclipse.equinox.internal.cm.reliablefile。標註為"internal"的類包通常不包含在Export列表中,或者通過"x-internal"屬性標記(該屬性僅在Eclipse開發環境中提供)。

      隱藏Bundle功能介面的一種良好機制是通過OSGi服務。開發人員僅將自己開發的Bundle的介面類包發布給其他Bundle可見,同時,將功能接 口的實現註冊為OSGi中的服務,其他Bundle通過尋找OSGi服務註冊表擷取該服務,通過服務的介面調用服務的功能。

1.4 儘可能應用OSGi提供的標準服務

      OSGi聯盟為一些經常用到的功能定義了標準服務,如應用程式管理服務,Log Service,事件服務,組態管理服務,使用者管理服務,HTTP服務等等。如果開發人 員採用的OSGi環境提供了上述服務的實現,且這些服務實現滿足系統的需求,則開發人員應儘可能使用這些服務,而不是為這些類似的功能定義自己的實現。

2.充分利用OSGi環境的特性 2.1 OSGi環境的動態特性

      OSGi是一個動態環境,OSGi運行環境內部狀態的變化通過事件發布/監聽機制進行互動。開發人員在構建Bundle時應充分利用OSGi環境的事件 監聽機制。如,開發人員可以在自己的Bundle中註冊Bundle事件的監聽用來處理其他Bundle的安裝,啟動,停止,卸載等事件。

      OSGi運行環境內部的事件主要包括三類:

  • 架構事件(FrameworkEvent)

STARTED   架構已經啟動
ERROR   某個Bundle啟動過程中引發錯誤
WARNING    某一Bundle引發一個警告
INFO   某一Bundle引發一個INFO類型的事件
PACKAGES_REFRESHED   PackageAdmin.refreshPackage操作執行完成
STARTLEVEL_CHANGED   StartLevel.setStartLevel操作執行完成

  • Bundle事件(BundleEvent)

INSTALLED    Bundle被安裝到OSGi環境後系統發布該事件
RESOLVED    Bundle被成功解析
LAZY_ACTIVATION    Bundle將被延遲啟用
STARTING    Bundle正在被啟用
STARTED    Bundle被成功啟用
STOPPING    Bundle被停止
STOPPED    Bundle正在被停止
UPDATED    Bundle被更新
UNRESOLVED    Bundle被UNRESOLVED 
UNINSTALLED    Bundle被卸載

  • 服務事件(ServiceEvent)

REGISTERED    服務被註冊
MODIFIED    服務被修改
UNREGISTERING   服務正在被登出

      關於上述事件的詳細資料參考OSGi API文檔。

      下述代碼展示了如何在Bundle組件中通過實現FrameworkListener,BundleListener和ServiceListener介面,並使用BundleContext註冊監聽OSGi環境的各種事件:

package com.example;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;

public class Activator implements BundleActivator, FrameworkListener,
        BundleListener, ServiceListener {

    public void start(BundleContext context) throws Exception {

        //註冊監聽
        context.addFrameworkListener(this);
        context.addBundleListener(this);
        context.addServiceListener(this);
    }

    public void stop(BundleContext context) throws Exception {
    }

    //處理架構事件

    public void frameworkEvent(FrameworkEvent event) {
        if ((event.getType() & FrameworkEvent.ERROR) != 0) {
            System.err.println("Framework ERROR: " + event.getBundle());
        }
    }

    //處理Bundle事件

    public void bundleChanged(BundleEvent event) {
        if ((event.getType() & BundleEvent.STARTED) != 0) {
            System.err.println("Bundle STARTED: " + event.getBundle());
        } else if ((event.getType() & BundleEvent.STOPPED) != 0) {
            System.err.println("Bundle STOPPED: " + event.getBundle());
        }
    }

    //處理服務事件

    public void serviceChanged(ServiceEvent event) {
        if ((event.getType() & ServiceEvent.REGISTERED) != 0) {
            System.err.println("Service REGISTERED: "
                    + event.getServiceReference());
        }
    }

}

2.2 Bundle的生命週期及OSGi上下文特性

      在Java虛擬機器運行環境中,類的生命週期開始於虛擬機器的載入,終止於被提前卸載或虛擬機器停止,通常不需要開發人員幹預。在OSGi環境中,開發人員可以參與Bundle的生命週期過程並能夠擷取Bundle在OSGi環境中啟動並執行上下文。

      通過實現org.osgi.framework.BundleActivator介面,並在Bundle的中繼資料頭屬性"Bundle- Activator"中指明該介面的實作類別,如"Bundle-Activator: com.example.Activator"。當Bundle被啟動或停止時,OSGi架構會將該Bundle的運行上下文BundleContext 注入到該實作類別執行個體中,開發人員可以在該Bundle的運行過程中使用該上下文訪問OSGi環境資訊,訪問OSGi環境中的其他Bundle,註冊事件監 聽,發布或引用服務等。

      下面的程式碼片段展示了如何利用Bundle運行上下文與OSGi環境進行互動:

public void start(BundleContext context) throws Exception {
    // 擷取OSGi環境中的所有安裝的bundle
    for (Bundle bundle : context.getBundles()) {
        System.out.println("Bundle Symbolic Name: "
                + bundle.getSymbolicName());
    }
    // 擷取OSGi運行環境中的屬性(尋找範圍包括系統屬性)
    System.out.println("osgi.framework = "
            + context.getProperty("osgi.framework"));
    //註冊其他Bundle
    context.installBundle("file:C://test_bundle_1.0.0.jar");
    //在該Bundle的資料存放區區中構建datacache.file檔案
    context.getDataFile("datacache.file");
    //註冊監聽
    context.addFrameworkListener(this);
    context.addBundleListener(this);
    context.addServiceListener(this);
}

2.3 Bundle的資源管理特性

      在通常的應用系統開發中,開發人員通常要考慮設定檔的儲存路徑,系統臨時檔案的儲存路徑等資訊。由於OSGi環境中Bundle實際上是由一組檔案資源 構成,開發人員在開發Bundle時可以充分利用這種特點,使用Bundle儲存與Bundle相關的配置資訊。OSGi環境在運行時為每一個 Bundle構建一個序列化資料存放區區,開發人員可以使用該儲存區儲存Bundle在運行時產生的臨時資訊。

      下述程式碼片段展示了如何通過Bundle提供的功能介面擷取Bundle內部的資源:

  • context.getBundle.getEntry("")      只查詢Bundle內部資源,返回Bundle的URL,該URL與實現相關,如:bundleentry://256/
  • context.getBundle.getEntry("/")      只查詢Bundle內部資源,返回Bundle的URL,該URL與實現相關
  • context.getBundle.getEntry("/build.properties")      只查詢Bundle內部資源,返回Bundle內部build.properties檔案的URL,該URL與實現相關,如:bundleentry://256/build.properties
  • context.getBundle().getResource("build.properties")    使用Bundle的ClassLoader查詢Bundle類域內的資源,如果Bundle不能被解析(RESOLVED),則只查詢Bundle內部資源,返回Bundle內部build.properties檔案的URL,該URL與實現相關。

      開發人員在擷取上述資源的URL後,可以轉換成與Bundle運行系統相關的檔案URL,如:file:C:/test_bundle_1.0.0。

下述程式碼片段展示了通過BundleContext介面擷取Bundle運行時資料存放區區資源:

  • context.getDataFile("")    返回該Bundle的運行時資料存放區區位置,如:E:/worksapce/.metadata/.plugins/org.eclipse.pde.core/CobWeb/org.eclipse.osgi/bundles/256/data
  • context.getDataFile("config.xml")    返回該Bundle的運行時資料存放區區內的config.xml檔案,如果該檔案不存在,則建立該檔案。
2.4 面向服務的組件特性

      OSGi的服務層提供了一個動態服務發布,綁定和尋找模型,所謂的服務即指實現了某個(些)介面的Java對象。在物件導向的系統開發中,介面在分離系統的耦合方面扮演至關重要的角色,OSGi環境充分利用了這一點。

      OSGi運行架構維護著一個服務註冊表,Bundle可以通過Bundle上下文向該服務註冊表中發布服務(只需指明服務實現的介面,服務的執行個體和服務的 屬性),也可以使用Bundle上下文從服務註冊表中根據介面名稱及服務的屬性尋找其他Bundle註冊的服務。在此模式下,Bundle之間的耦合關係 只存在介面層面,而與介面的實現無關。

      下述程式碼片段展示了如何向OSGi服務註冊表發布服務以及如何尋找其他Bundle發布的服務:

public void start(BundleContext context) throws Exception {
        //擷取OSGi Log服務的服務引用
        ServiceReference logSr = context.getServiceReference(LogService.class.getName());
        //根據Log服務引用擷取LogService服務執行個體
        log = (LogService) context.getService(logSr);

        //建立StringService介面服務執行個體
        StringService ss = new StringServiceImpl(log);
        //設定StringService的屬性
        Hashtable

        //使用BundleContext註冊發布StringService服務
        context.registerService(StringService.class.getName(), ss, properties);
    }

3.Bundle的開發實踐 實踐1、構建Utility類型的Bundle

      該樣本將第三方log4j工具包(版本為1.2.13)封裝為OSGi Bundle組件。操作步驟如下:

  • 步驟一:在C盤根目錄下建立名稱為"org.apache.log4j"目錄,將log4j.1.2.13.jar檔案拷貝到該目錄中;
  • 步驟二:在"org.apache.log4j"目錄下構建META-INF目錄,並在此目錄下建立名稱為"MANIFEST.MF"檔案;
  • 步驟三:編輯"MANIFEST.MF"檔案,在此檔案中添加以下內容:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Apache Log4j
Bundle-SymbolicName: org.apache.log4j
Bundle-Version: 1.2.13
Bundle-ClassPath: .;log4j.1.2.13.jar
Export-Package: org.apache.log4j;version="1.2.13",
org.apache.log4j.config;version="1.2.13",
org.apache.log4j.helpers;version="1.2.13",
org.apache.log4j.jdbc;version="1.2.13",
org.apache.log4j.jmx;version="1.2.13",
org.apache.log4j.net;version="1.2.13",
org.apache.log4j.or;version="1.2.13",
org.apache.log4j.or.jms;version="1.2.13",
org.apache.log4j.or.sax;version="1.2.13",
org.apache.log4j.spi;version="1.2.13",
org.apache.log4j.varia;version="1.2.13",
org.apache.log4j.xml;version="1.2.13"

      經過上述步驟後,名稱為org.apache.log4j的OSGi Bundle已經構建完成。開發人員可以在其他Bundle中如同平常的開發模式一樣引用Log4j的類包,擷取Logger執行個體記錄日誌。

實踐2、構建Service類型的Bundle

      該樣本Bundle由StringService介面,StringServiceImpl類和Activator類構成。該Bundle發布StringService介面服務,同時引用OSGi的標準LogService服務。

      StringService介面是該樣本Bundle發布的String服務實現介面。

package com.example;

public interface StringService {
    String concat(String str1, String str2);
}

      StringServiceImpl類是StringService介面的實作類別,該類使用OSGi的標準LogService服務記錄日誌。

package com.example.internal;

import org.osgi.service.log.LogService;

import com.example.StringService;

public class StringServiceImpl implements StringService {
    private LogService log;

    public StringServiceImpl(LogService log) {
        this.log = log;
    }

    public String concat(String str1, String str2) {
        String str = str1 + str2;
        if (log != null)
            log.log(LogService.LOG_INFO, "Concat String Result is " + str);
        return str;
    }

}

      Activator類為Bundle的生命週期入口,該類根據注入的BundleContext上下文擷取OSGi LogService服務,然後註冊StringService服務。

package com.example.internal;

import java.util.Hashtable;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;

import com.example.StringService;

public class Activator implements BundleActivator {

    private LogService log;

    public void start(BundleContext context) throws Exception {
        //擷取OSGi Log服務的服務引用
        ServiceReference logSr = context.getServiceReference(LogService.class.getName());
        //根據Log服務引用擷取LogService服務執行個體
        log = (LogService) context.getService(logSr);

        //建立StringService介面服務執行個體
        StringService ss = new StringServiceImpl(log);
        //設定StringService的屬性
        Hashtable

        //使用BundleContext註冊發布StringService服務
        context.registerService(StringService.class.getName(), ss, properties);
    }

    public void stop(BundleContext context) throws Exception {
    }

}

      Bundle的中繼資料清單如下:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Example Plug-in
Bundle-SymbolicName: com.example
Bundle-Version: 1.0.0
Bundle-Activator: com.example.internal.Activator
Bundle-Vendor: EGRID
Eclipse-LazyStart: true
Import-Package: org.osgi.framework;version="1.3.0",
org.osgi.service.log;version="1.3.0"
Export-Package: com.example

4. 結論

      通過本文的探討,開發人員可以明確在OSGi環境下進行Bundle開發時所要面臨的一些決策及注意事項。下一篇文章我們將要詳細探討OSGi環境下Bundle編程的細節。本文中的點擊此處下載。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.