OSGi (第一部分): Bundles入門(續,翻譯)

OSGi (第一部分): Bundles入門(續,翻譯)

4. 依賴性管理
OSGi允許您把您的應用程序分成多個模塊,並能管理這些模塊之間的依賴性。爲了達到這個目的,它引入了Bundle訪問域的概念。Bundle中類的缺省訪問範圍只對本Bundle內部可見,但對其它任何Bundle都是不可見的;在Bundle內部,類的可訪問性遵循Java語言的一般規範。那麼,您如果想要從一個Bundle中訪問另一個Bundle中的類,您應該怎麼辦呢?解決方法是將源Bundle中的包導出來,然後把它們導入到目標Bundle中。在本小結中,我們將通過一個示例程序說明這個概念。
首先,我們新建一個名com.javaworld.sample.HelloServiceBundle,並從其中導出一個包,然後將該包導入到我們的com.javaworld.sample.HelloWorld Bundle中。
4.1. 導出Java
我們開始新建一個com.javaworld.sample.HelloServiceBundle,並從其中導出一個Java包,具體步驟如下:
1) 新建com.javaworld.sample.HelloService Bundle,具體步驟請參見上小節中新建com.javaworld.sample.HelloWorldBundle的步驟;
2) HelloService Bundle中,新建一個com.javaworld.sample.service.HelloService.java接口,其源代碼如清單3所示。

源代碼清單3. HelloService.java
package com.javaworld.sample.service;
public interface HelloService {
public String sayHello();
}

3) 新建類com.javaworld.sample.service.impl.HelloServiceImpl.java,該類實現HelloService接口,其源代碼如清單4所示。

源代碼清單4. HelloServiceImpl.java
package com.javaworld.sample.service.impl;
import com.javaworld.sample.service.HelloService;
public class HelloServiceImpl implements HelloService {
public StringsayHello() {
System.out.println("InsideHelloServiceImple.sayHello()");
return"Say Hello";
}
}
4) 請在您的Eclipse Manifest編輯器中打開HelloService包中的MANIFEST.MF文件,點擊“Runtime(運行時) 標籤,在“導出包”小節,單擊“Add(添加)”按鈕,並選擇com.javaworld.sample.service包。這時,HelloServiceBundle中的MANIFEST.MF文件代碼應如源代碼清單5所示。

源代碼清單5. HelloService Bundle中的Manifest文件
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloService Plug-in
Bundle-SymbolicName:com.javaworld.sample.HelloService
Bundle-Version: 1.0.0
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Export-Package: com.javaworld.sample.service
Import-Package:org.osgi.framework;version="1.3.0"

您可以看到,HelloService Bundle中的MANIFEST.MF文件和HelloWorldBundle非常相似,唯一的區別就是多了一個Export-Package屬性頭,該屬性頭的值爲com.javaworld.sample.serviceExport-Package屬性頭通知OSGi容器,其它Bundle可以從HelloService Bundle外面訪問com.javaworld.sample.service包中的類。請注意,在示例代碼中,我們只暴露了接口類HelloService,而沒有暴露其實現類的HelloServiceImpl

4.2. 導入Java
下面,我們將從HelloServiceBundle中導出的com.javaworld.sample.service包並將其導入到HelloWorldBundle中,具體步驟如下:
1). 請在com.javaworld.sample.HelloWorld Bundle中找到MANIFEST.MF文件,並在Manifest編輯器中打開,點擊“Dependencies(依賴性)”標籤,然後點擊“ImportPackage(導入包)”按鈕,將com.javaworld.sample.service添加爲導入包,這時,您的HelloWorldBundle中的MANIFEST.MF文件內容應如源代碼清單6所示:
源代碼清單6. HelloWorld Bundle中的MANIFEST.MF文件
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorld Plug-in
Bundle-SymbolicName: com.javaworld.sample.HelloWorld
Bundle-Version: 1.0.0
Bundle-Activator: com.javaworld.sample.helloworld.Activator
Bundle-Vendor: JAVAWORLD
Bundle-Localization: plugin
Import-Package: com.javaworld.sample.service,
org.osgi.framework;version="1.3.0"

從上面的代碼可以看出,
Import-Package屬性頭的值是一個由逗號分隔的字符串,這是您想導入包的列表。在HelloWorldBundle示例代碼中,我們引入了兩個包,即com.javaworld.sample.serviceorg.osgi.framework
org.osgi.framework包中包含有OSGi框架類,比如,在HelloWorldBundle中的Activator.java中用到的BundleContextBundleActivator類都屬於這個包。
2) 下面,請在Eclipse Java編輯器中打開com.javaworld.sample.helloworld.Activator.java,您會注意到,您現在可以訪問HelloService接口,但不能訪問HelloServiceImpl實現類,這是因爲HelloServiceBunlde只導出了com.javaworld.sampel.service包,同時HelloWorldBundle也導入了這個包。HelloServiceImplHelloServiceBundle的一個內部類,任何其它的Bundle都不能訪問它。
4.3. 類級別上的訪問域
如果您運行示例的HelloService服務包,它會在Eclipse控制檯上打印出”HelloWorld”。但是,如果您想在HelloWorld BundleActivator中訪問HelloServiceImpl類,這時,編譯沒有問題,但在OSGi容器中運行這個Bundle時會拋出異常。
OSGi容器是如何能將jar文件中的一些類隱藏掉,而讓另外一些類可見呢?這是因爲OSGi容器使用Java類加載器來管理類的可見性,OSGi容器爲每個Bundle創建不同的類加載器,因此每個Bundle能訪問位於下列位置中的類:
a) 位於Java啓動類路徑下的、所有以Java.*開頭的包中的類;
b) 位於OSGi框架類路徑下的類,通常有一個獨立的類加載器負責加載框架的實現類及關鍵的接口類;
c) 位於Bundle空間中的類,這些類通常包含在與Bundle相關的jar文件中,以及加到這個Bundle中的其它jar包中的類。
d) 導入包中的類,例如,HelloWorld Bundle導入了com.javaworld.sample.service包,因此它能訪問該包中的類。Bundle級別的訪問域是OSGi一個非常強大的功能,例如,它可以讓您安全地更新HelloServiceImpl.java類,而不必擔心依賴於這個類的代碼受到破壞。
5. OSGi服務
前面我們提到,OSGi架構非常適合我們實現面向服務的應用(SOA)。它可以讓Bundles導出服務,而其它的Bundles可以在不必瞭解源Bundles任何信息的情況下消費這些導出的服務。由於OSGi具有隱藏真實的服務實現類的能力,所有它爲面向服務的應用提供了良好的類與接口的組合。
OSGi框架中,源BundleOSGi容器中註冊POJO對象,該對象不必實現任何接口,也不用繼承任何超類,但它可以註冊在一個或多個接口下,並對外提供服務。目標Bundle可以向OSGi容器請求註冊在某一接口下的服務,一旦它發現該服務,目標Bundle就會將該服務綁定到這個接口,並能調用該接口中的方法。下面我們舉個例子,以便我們能更好理解與OSGi相關的這些概念。
5.1. 導出服務
在本小節中,我們將更新HelloService Bundle,以便它能把HelloServiceImpl類的對象導出爲服務,具體步驟如下:
1) 修改com.javaworld.sample.HelloService Bundle中的MANIFEST.MF文件,讓它導入org.osgi.framework包(譯者注,這一步我們已經完成);
2) 新建Javacom.javaworld.sample.impl.HelloServiceActivator.java,其源代碼如清單7所示;
源代碼清單7. HelloServiceActivator.java
publicclass HelloServiceActivator implements BundleActivator {
ServiceRegistrationhelloServiceRegistration;
public void start(BundleContext context)throws Exception {
HelloService helloService = newHelloServiceImpl();
helloServiceRegistration=context.registerService(HelloService.class.getName(), helloService, null);
}
public void stop(BundleContext context)throws Exception {
helloServiceRegistration.unregister();
}
}
請注意,在源Bundle中,我們應使用BundleContext.registerService()方法導出服務,這個方法帶三個參數:
a) 該方法第一個參數爲您要註冊的服務的接口名稱。如果您想把您的服務註冊到多個接口下,您需要新建一個String數組存放這些接口名,然後把這個數組作爲第一個參數傳給registerService()方法。在示例代碼中,我們想把我們的服務導出到HelloServer接口名下;
b) 第二個參數是您要註冊的服務的實際Java對象。在示例代碼中,我們導出HelloServiceImpl類的對象,並將其作爲服務;
c) 第三個參數爲服務的屬性,它是一個Dictionary對象。如果多個Bundle導出服務的接口名相同,目標Bundle就可以使用這些屬性對源Bundle進行過濾,找到它感興趣的服務。
3) 最後,請修改HelloServiceBundle中的MANIFEST.MF文件,將Bundle-Activator屬性頭的值改爲com.javaworld.sample.service.impl.HelloServiceActivator
現在HelloService Bundle就可以導出HelloServiceImpl對象了。當OSGi容器啓動HelloServiceBundle時,它會將控制權交給HelloServiceActivator.java類,HelloServiceActivatorHelloServiceImpl對象註冊爲服務。下面,我們開始創建該服務的消費者。
5.2. 導入服務
在本小節中,我們將修改上面開發的HelloWorld Bundle,以便讓它成爲HelloService服務的消費者。您主要需要修改HelloWorldBundle中的Activator.java代碼,修改後的代碼如源代碼清單8所示:
源代碼清單8. HelloWorld Bundle中的Activator.java
packagecom.javaworld.sample.helloworld;

importorg.osgi.framework.BundleActivator;
importorg.osgi.framework.BundleContext;
importorg.osgi.framework.ServiceReference;
importcom.javaworld.sample.service.HelloService;

publicclass Activator implements BundleActivator {
ServiceReference helloServiceReference;
public void start(BundleContext context)throws Exception {
System.out.println("HelloWorld!!");
helloServiceReference=context.getServiceReference(HelloService.class.getName());
HelloService helloService=(HelloService)context.getService(helloServiceReference);
System.out.println(helloService.sayHello());

}
public void stop(BundleContext context)throws Exception {
System.out.println("Goodbye World!!");
context.ungetService(helloServiceReference);
}
}
在上面的代碼中,BundleContext.getServiceReference()方法將爲註冊在HelloService接口下的服務返回一個ServiceReference對象。如果存在多個HelloService服務,該方法會返回排行最高的服務(服務的排行是通過Constants.SERVICE_RANKING屬性指定的)。您一旦獲得ServiceReference對象,您就可以調用其BundleContext.getService()方法獲取真實的服務對象。
您可以參照運行Bundle的方法運行上面的示例應用,請點擊“RunàRun…”菜單,並確保HelloWorldHelloService這兩個Bundle被選中。當您啓動HelloServiceBundle時,您會在控制檯上看到“InsideHelloServiceImple.sayHello()”,這個消息是由HelloServiceImpl.sayHello()方法打印出來的。
5.3. 創建服務工廠
在上節中,我們學會了如何使用OSGi框架新建一個Java對象,並把它註冊爲一個服務,然後讓其它的Bundle去消費這個服務。如果您看一下HelloServiceActivator.start()方法,您會注意到我們在start()方法中新建了HelloServiceImpl類對象,然後將它註冊到HelloService接口名下。這樣註冊後,任何其它的Bundle在請求HelloService服務時,OSGi容器將返回同一對象。
在大多數情況下,這樣的實現方法沒有問題。但是,比如說我們要爲每一個Bundle消費者返回不同的HelloServiceImpl對象,再比如說,您的服務對象要提供的服務爲打開一個數據庫連接,但並不是馬上就打開它,而是在真正需要的時候纔打開這個數據庫連接。
對這兩種情況,我們的解決方法是,新建一個類實現ServiceFactory接口,並把該類的對象註冊爲服務,但並不是註冊實際的服務對象。一旦您完成這一步,其它Bundle在請求該服務時,您的ServiceFactory實現類將接管該請求,ServiceFactory會爲每個Bundle新建一個服務對象,並將真實服務的創建時間延遲到有人真正需要該服務的時候。
下面我們將使用ServiceFactory更新我們上面開發的com.javaworld.sample.HelloServiceBundle,具體步驟如下:
1) 新建工廠 HelloServiceFactory.java,源代碼如清單9所示。
源代碼清單9 . HelloServiceFactory.java
publicclass HelloServiceFactory implements ServiceFactory{
private int usageCounter = 0;
public Object getService(Bundle bundle,ServiceRegistration registration) {
System.out.println("Create objectof HelloService for " + bundle.getSymbolicName());
usageCounter++;
System.out.println("Number ofbundles using service " + usageCounter);
HelloService helloService = newHelloServiceImpl();
return helloService;
}
public void ungetService(Bundle bundle,ServiceRegistration registration, Object service) {
System.out.println("Release objectof HelloService for " + bundle.getSymbolicName());
usageCounter--;
System.out.println("Number ofbundles using service " + usageCounter);
}
}
從上面的代碼中,我們可以看到,ServiceFactory接口定義了兩個方法:
a) getService()方法:當某個Bundle第一次使用BundleContext.getService(ServiceReference)方法請求一個服務對象時,OSGi框架會調用該方法。在源代碼清單9中,我們用這個方法爲每個Bundle新建並返回不同的HelloServiceImpl對象,如果這個對象不是nullOSGi框架會緩存這個對象。如果同一個Bundle再次調用BundleContext.getService(ServiceReference)方法,OSGi將返回同一個服務對象。
b) ungetService()方法:當Bundle釋放服務時,OSGi容器可以調用該方法銷燬服務對象。在源代碼清單9中,我們使用usageCounter變量來跟蹤服務的使用數目,並打印出該服務的客戶端數量。
2) 修改HelloService Bundle中的HelloServiceActivator.javastart()方法,讓它註冊到ServiceFactory接口名下,而不是註冊到HelloService接口。詳細代碼如清單10所示:
源代碼清單10. 修改後的HelloServiceBundle中的HelloServiceActivator.java
packagecom.javaworld.sample.service.impl;
importorg.osgi.framework.BundleActivator;
importorg.osgi.framework.BundleContext;
importorg.osgi.framework.ServiceRegistration;

importcom.javaworld.sample.helloservice.HelloServiceFactory;
importcom.javaworld.sample.service.HelloService;

publicclass HelloServiceActivator implements BundleActivator {
ServiceRegistrationhelloServiceRegistration;
public void start(BundleContext context)throws Exception {
HelloServiceFactory helloServiceFactory= new HelloServiceFactory();
helloServiceRegistration=context.registerService(HelloService.class.getName(), helloServiceFactory,null);
}
public void stop(BundleContext context)throws Exception {
helloServiceRegistration.unregister();
}
}

現在,您可以試運行示例代碼。您會注意到,當HelloWorld Bundle啓動時,服務計數器變爲1;當HelloWorldBundle停止時,服務計數器的數目將變爲0
5.4. 跟蹤服務
在“OSGi服務”小節,您學會了如何使用服務的接口名搜索服務。但如果有多個Bundle使用同一接口名註冊服務,那會發生什麼呢?這時,OSGi容器將返回排行最高的服務,即,返回註冊時那個SERVICE_RANKING屬性值最大的服務。如果有多個服務的排行值相等,那麼OSGi容器將返回PID值最小的那個服務。
但是,如果您的服務消費者需要了解某一接口下的服務對象何時註冊、何時取消註冊,這時,您應使用ServiceTracker類。下面,我們看看如何使用服務跟蹤器來修改我們的示例代碼,具體步驟如下。
1) 修改HelloWorldBundleMANIFEST.MF文件,讓它導入org.osgi.util.tracker包;
2) 新建類HelloServiceTracker.java,其源代碼參見清單11
源代碼清單11.HelloServiceTracker.java

public class HelloServiceTracker extends ServiceTracker {

    public HelloServiceTracker(BundleContext context) {

        super(context, HelloService.class.getName(),null);

    }

    public Object addingService(ServiceReference reference) {

        System.out.println("Inside HelloServiceTracker.addingService " + reference.getBundle());

        return super.addingService(reference);

    }

    public void removedService(ServiceReference reference, Object service) {

        System.out.println("Inside HelloServiceTracker.removedService " + reference.getBundle());

        super.removedService(reference, service);

    }

}


在上面的HelloSerivceTracker類的構造函數中,您可以看到,我們把HelloService接口名傳入其父類中,這相當於說,HelloServiceTracker應跟蹤註冊到HelloService接口名下的所有服務,HelloServiceTracker繼承自ServiceTracker類,實現了下面兩個方法:
a) addingService()方法:當Bundle使用接口名註冊服務時,該方法將會被調用;
b)removedService()方法:當Bundle取消註冊某個接口名下的服務時,該方法將會被調用。
3) HelloServiceTracker類更新我們的Activator.java類,以便讓它來管理服務,而不是直接去查找它們,源代碼請參見清單12
源代碼清單12. 使用了HelloServiceTrackerActivator.java

public class Activator implements BundleActivator {

    HelloServiceTracker helloServiceTracker;

    public void start(BundleContext context) throws Exception {

        System.out.println("Hello World!!");

        helloServiceTracker= new HelloServiceTracker(context);

        helloServiceTracker.open();

        HelloService helloService = (HelloService)helloServiceTracker.getService();

        System.out.println(helloService.sayHello());

 

    }

    public void stop(BundleContext context) throws Exception {

        System.out.println("Goodbye World!!");

        helloServiceTracker.close();

    }

}


我們看到,在初始的start()方法中,我們首先新建一個HelloServiceTracker對象,然後要求這個對象跟蹤HelloService接口下的服務。這時,我們可以調用getService()方法獲得HelloService對象。
如果您試運行上面的示例代碼,您會注意到,在啓動或停止HelloSerivceBundle時,OSGi容器都會調用HelloServiceTracker對象的addingService()方法或removedService()方法。

6.
結論

這是“你好,
OSGi”系列三篇文章的第一篇,我向您介紹了使用OSGi進行模塊化應用開發的一些基本概念。您現在已瞭解到,當前有三種開源的OSGi容器,而且您還練習瞭如何使用Eclipse自帶的OSGi容器Equinox開發一個簡單的Bundle;同時,您也學會了Bundle之間是怎樣通過導出導入彼此的包和服務,從而達到彼此交互的目的。
在本文中,您也許注意到,開發OSGi Bundle的一個挑戰是,您的每個Bundle都需要了解OSGi API,在某些開發場景中,這可能意味着我們要許多基礎代碼。在本系列的下一篇文章中,我將向您介紹“SpringDynamic Modules for OSGi Service Platforms(OSG服務平臺的Spring動態模塊,亦稱爲SpringOSGi)”項目,該項目將會簡化OSGi Bundle 的開發。另外,您也可以參考本文的資源部分,學習更多關於OSGi的知識。

7.
作者介紹

Sunil Patil是一位Java企業/門戶開發者,現就職於加州三藩市的AscendentTechnology公司。他是Java Portlets 101一書的作者(該書由SourceBeat公司20074月出版),而且,他還寫了許多文章,通過O’ReillyMedia發表。Sunil曾經在IBMWebspherePortal Server開發團隊工作過3年。現在,他積極參與Pluto社區。另外,他擁有IBM WebspherePortal Server 5.05.1版本的開發者證書,Sun SCJP證書和Web組件開發者證書,同時,他還是商務組件的開發者。您可以訪問Sunil的博客:http://jroller.com/page/SunilPatil

8.
資源

非官方的
OSGi規範開始於1999年,最近,JSRJava模塊化支持分爲兩個規範,即,JSR 277(Java模塊系統,http://jcp.org/en/jsr/detail?id=277 )JSR 291(Java動態組件支持,http://jcp.org/en/jsr/detail?id=291 )。在這兩個JSR規範中,人們對哪個規範應該包含在Java EE6中,有過很多爭議,請參考http://www.infoq.com/news/2007/08/osgi-jsr277-debate
和許多Java開發者一樣,SebastienArbogast最近意識到OSGi的重要性(請參見http://osgi.dzone.com/news/why-osgi-zone ),他在Javalobby上開闢了OSGi專區(請參見http://osgi.dzone.com/ ),作爲OSGi相關信息和討論的平臺。
如果您想了解
OSGi服務規範第4版的信息,請參考http://www2.osgi.org/Release4/HomePage;關於OSGi服務規範第4版的Java API,請參考http://www2.osgi.org/javadoc/r4/;關於OSG服務平臺的Spring動態模塊項目,有時也稱作Spring OSGi項目,請參考http://www.springframework.org/osgi

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章