Declarative Serviceとは?

OSGiのサービス連携にはいくつかの方法がありますが、今日はDeclarative Service(通称DS)を見ていきます。

Declarative Serviceとは

Declarative Serviceとは、端的にいうと、そのBundleが提供するサービス/利用するサービスをXMLに記述するBundle間のサービス連携方法です。ServiceTrackerを使った方法は以前記事を書いた事がありますが、今回はDeclarative Serviceをやってみます。なぜならDeclarative ServiceはOSGiのサービス連携に関する仕様のうち、R4で追加された比較的新しい仕様だからです。ところでEquinoxの父であるJeff McAffer氏の書籍が近々リリースされます。

OSGi and Equinox: Creating Highly Modular Java Systems (Eclipse Series)

OSGi and Equinox: Creating Highly Modular Java Systems (Eclipse Series)

  • 作者: Jeff VanderLei, Paul Archer, Simon McAffer
  • 出版社/メーカー: Addison-Wesley Professional
  • 発売日: 2010/02/14
  • メディア: ペーパーバック
  • 購入: 1人 クリック: 44回
  • この商品を含むブログ (1件) を見る

この本では、OSGiのサービス連携の進化について、次のようにまとめられています。

R1 ServiceRegistrationとServiceListener APIの組み合わせ

BundleのActivatorを使って他のBundleからサービスが登録されたのをServiceListenerがイベントを受け取って連携するモデルです。ServiceListenerは他のBundleのサービスのregistration(登録)、サービスのunregistration(造語。なので妙訳がない。廃止?登録している他のBundleがstopされたときに発行)、modification(更新)を受けとります。

R2 ServiceTrackerの追加

ServiceTrackerを使って、他のBundleのサービス登録を追跡するモデルです。Serviceの追加、削除、更新を追跡できます。listenerモデルと比べると、重複や競合状態をとりあえず解決しています。

R4 Declarative Serviceの追加

Declarative Service(サービスの宣言)によって、動的なServiceの獲得、サービスの解放(release)を実現しています。これまでのモデルと異なり、直接参照させないので、サービスをスケールしやすくし、より有効に遅延ロードさせることを可能にしています。また、サービスの注入役のBundleが存在することにより、Bundleを起動させる順番によっては、サービスの準備ができていない状態から利用する状態を回避することができるようになりました。見た感じ、かなりDependency Injectionパターンを意識した作りになっています。

その他に仕様的にはSpring DMから作られたBlueprint Serviceがあり、Blueprint Serviceの方が新しいのですが、Bundle間でServiceを提供(provide)したり、利用(consume)したりする分にはDSで十分です。どちらも基本サービス連携はXMLで記述しますし。Blueprint Serviceとの機能差はおいおいまとめます。

あと、DSは別名Service Component Runtime(SCR)と呼ばれる事もありますが、DSは仕様、SCRはその仕様を実装したもの、という対応のようです。例:Apache Felix SCR

Declarative Serviceの設定を書いてみる。

Declarative Serviceの設定は簡単です。

  1. 設定XMLを作成し、
  2. MANIFEST.MFに設定xmlのパスを指定する。

やらなければならないことはこの2つだけ。

シンプルなDSの設定例を見てみる。

設定XMLの一番簡単な書き方は次のようになります。

org.kompiro.ds.hello/OSGI-INF/hello.xml

<?xml version="1.0"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
 name="org.kompiro.ds.HelloService">
  <implementation class="org.kompiro.ds.hello.impl.HelloServiceImpl"/>
  <service>
    <provide interface="org.kompiro.ds.hello.HelloService"/>
  </service>
</scr:component>

OSGIの設定はOSGI-INFに配置するのが慣習です。続いてMANIFEST.MFの書き方を見ていきましょう。

org.kompiro.ds.hello/META-INF/MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Hello DS
Bundle-SymbolicName: org.kompiro.ds.hello
Bundle-Version: 1.0.0
Bundle-RequiredExecutionEnvironment: J2SE-1.4
Service-Component: OSGI-INF/component.xml

Service-Componentで指定したOSGI-INF/hello.xmlに記述されているimplementationがBundleContextにServiceとして登録されます。この例では、"org.kompiro.ds.HelloService"というインタフェースで"org.kompiro.ds.hello.impl.HelloServiceImpl"が登録されます。Declarative Serviceが実装されているBundleが登録されているBundleの様子を伺って、よしなにしてくれるんですね。便利。サービス名は、慣習的にサービスのインタフェース名が指定されることが多いです。Service-Componentですが、ワイルドカードの指定もできれば、複数ファイルの指定もできます。

Service-Component: OSGI-INF/component.xml,
  OSGI-INF/service*.xml

こんな感じですね。

他のBundleとサービスを連携させてみる。

では、他のBundleのサービスを、このBundleのサービスに注入するにはどうすればよいのでしょう?例えば、HelloServiceインタフェースとHelloServiceImplクラスがこんな実装だとします。

org.kompiro.ds.hello.HelloService.java

package org.kompiro.ds.hello;

public interface HelloService{
  public void hello();
}

org.kompiro.ds.hello.impl.HelloServiceImpl.java

package org.kompiro.ds.hello.impl;
import org.kompiro.ds.hello.HelloService;
import org.kompiro.ds.greeting.GreetingService;

public class HelloServiceImpl implements HelloService{

  private GreetingService service;

  public void hello(){
    service.hello();
  }

  public void setService(GreetingService service){
    this.service = service;
  }
}

GreetingServiceをimportしてますが、このServiceはorg.kompiro.ds.greetingというBundleでexportされた名前のServiceだとします。*1このServiceを注入するには、OSGI-INF/component.xmlを次のように修正します。

<?xml version="1.0"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
 name="org.kompiro.ds.HelloService">
  <implementation class="org.kompiro.ds.hello.impl.HelloServiceImpl"/>
  <reference bind="setService"
   interface="org.kompiro.ds.greeting.GreetingService"
   name="greeting"/>
</scr:component>

referenceタグでそれっぽく書くだけでGreetingServiceの注入もできました。bindは、注入するためのメソッド名を指定しています。このGreetingServiceを提供するBundleがインストールされ、startされれば注入されますし、stopされれば、GreetingServiceがnullになるため、HelloServiceImplはぬるぽるのです。だめじゃん。それはおいておいて。

これらの単純な例では、外部からのイベントのハンドリング等はできないので、どう使うんだよ!と思われるかもしれません。しかし、この指定はベースです。仮にHTTPService等をBundleContextに登録しておけば、referenceに指定できるのです。Bundleをコンテナに載せるだけで、Bundle間の通信が指定したインタフェースによって、自動的に行われるわけです。

紹介したOSGi and Equinox本の中では、Server-Client連携をそれぞれ個別に実装するような複雑なアーキテクチャのシステムがチュートリアルで紹介されています。チュートリアルは、更新サイトからダウンロードでき、サンプルソースコードのインポートを補助するプラグインもあるので、結構簡単に試す事ができます。僕は試しに、equinox.dsをApache Felix SCRにBundleを差し替えたのですが、そのまま動いて驚きました。

次は、なぜ今OSGiに注目が集まっているのか、再考してみます。

1/20追記

component.xmlのインタフェースの指定を修正

*1:中の実装はどうでもいいので、まったく触れません。