前回は導入として、Hello Worldをやりましたが、今回からはOSGIの肝であるサービスの話をしていきます。
前述の通り、OSGIはバンドルと呼ばれるプログラムコンポーネントの集合体を動かすためのフレームワークです。
Serviceとはそのひとつのバンドルが公開する一連の処理を意味します。Javaオブジェクトであればなんでも
サービスになりえます。しかし、実際にはまず公開するIFを作成して、サービスとして登録するのはそのIFの
実装クラスになるでしょう。
・サービスバンドルの作成
まずはサービスバンドルを作ります。前回と同様、プラグインプロジェクトにて以下のプロジェクトを作成します。
プロジェクト名:ServiceBundle
Activator:test.service1.activator.Activator
Activator:test.service1.activator.Activator
続いてIFの作成
package test.service1.serviceif; /** * サービスinterface * @author rag-timer * */ public interface MyServiceIF { /** * なんらかのサービス */ public void print(); }
お次に実装の作成
package test.service1.serviceimpl; import test.service1.serviceif.MyServiceIF; public class MyServiceImpl implements MyServiceIF { @Override public void print() { System.out.println("This is my service!"); } }
なんの意味もないサービスですが、とりあえずこれで登録するネタはそろいました。では、Activatorで登録します。
サービスの登録にはBundleContext#registerService()を使用します。
/* * (non-Javadoc) * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ public void start(BundleContext bundleContext) throws Exception { Activator.context = bundleContext; System.out.println("ServiceBundle start"); //サービスの実体 MyServiceIF service = new MyServiceImpl(); //サービスの登録を行う context.registerService(MyServiceIF.class.getName(), service, null); System.out.println("Service registed"); }
registerService()の一つ目の引数はサービスのこのOSGIフレームワーク内で識別するための文字列です。
別に文字列であればなんでもかまいませんが、普通IFの名前を使用します。二つ目の引き数にはサービス
の実体を指定します。三つ目はこのサービスのプロパティを指定しますが今回は使いません。
続いて、このサービスを「公開する」ための作業を行います。バンドル間でのサービス呼び出しは一般の
Javaオブジェクトの呼び出しと大して変わりませんが、別バンドルが公開しているIFをそのままimportする
ことはできません。かならず「サービス公開者がexportし」「サービス消費者がimportする」という作業が
必要になります。この設定を行うのがMETA-INF/MANIFEST.MFです。なお公開はパッケージ単位で行います。
ではサービスの公開を行いましょう。MANIFEST.MFをダブルクリックし、[ランタイム」タブを選択して下さい。
左側の[エクスポートされるパッケージ]欄で追加を押下し、IFのパッケージを指定します。

これでサービス側のバンドルの準備は完了しました。
・サービス消費バンドルを作成
次にクライアントとなるサービス消費バンドルを作成します。
プロジェクト名:ServiceConsumerBundle
Activator:test.serviceconsumer1.activator.Activator
Activator:test.serviceconsumer1.activator.Activator
さて、まずはimportを行いましょう。これを行わないと、サービスIFが参照できません。
MANIFEST.MFをダブルクリックして、[依存関係]タブから[インポート済みパッケージ]を選んでサービスIFの
パッケージを選択します。なお、当然、exportをこの段階で行っていなければリストに出てきません。

続いて以下のようにActivatorを実装します。
package test.serviceconsumer1.activator; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import test.service1.serviceif.MyServiceIF; public class Activator implements BundleActivator { private static BundleContext context; private boolean flg = true; private final Thread myThread = new Thread(){ public void run(){ while(flg){ //ServeiceReferenceの取得 ServiceReference ref = Activator.context.getServiceReference(MyServiceIF.class.getName()); //サービスの生成 MyServiceIF service = (MyServiceIF)Activator.context.getService(ref); //サービスの呼び出し service.print(); try { Thread.sleep(5000); } catch (InterruptedException e) { break; } } } }; static BundleContext getContext() { return context; } public void start(BundleContext bundleContext) throws Exception { System.out.println("ServiceConsumerBundle start"); Activator.context = bundleContext; //スレッドのスタート myThread.start(); } public void stop(BundleContext bundleContext) throws Exception { System.out.println("ServiceConsumerBundle stop"); flg = false; Activator.context = null; } }
Activator#startでは、定期的にサービスを使用するスレッドを起動してます。そのスレッドのrun()の中で
BundleContext#getServiceReference()でServiceReferenceオブジェクトを作成します。引数で渡すのは
先ほどサービスの登録に使用したサービス識別用の文字列、すなわちサービスのクラス名です。
作成したServiceReferenceを引数に、BundleContext#getService()でサービスが取得できます。
とりあえずはさらっと流します。
これで実行構成からこの2つのバンドルを構成し、実行すると・・・
osgi> ServicConsumerBundle start
ServiceBundle start
Service registed
This is my service!
This is my service!
・・・
ServiceBundle start
Service registed
This is my service!
This is my service!
・・・
てな感じで表示されるはずです。ちゃんとサービス呼べてますね。
・でもサービスをupdateすると・・・
では前回のようにサービスを書き換えてみましょう。適当にprintlnの文字列を変更して保存し、ssで
IDを確認後
update サービスバンドルのid
を実行します。すると・・・
osgi> Exception in thread "Thread-2" java.lang.NullPointerException: A null service reference is not allowed.
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.getService(BundleContextImpl.java:660)
at test.serviceconsumer1.activator.Activator$1.run(Activator.java:22)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.getService(BundleContextImpl.java:660)
at test.serviceconsumer1.activator.Activator$1.run(Activator.java:22)
こんなんなっちゃいました。update後、スレッド内の処理でServiceReferenceが取れていないようです。
この解決策は次回にします。
では。