実際にEclipseでSpring Dynamic Modulesを利用したOSGiサービスとコンシューマを作ってみる
全体の設定とEclipseヘのインポート
まずテンプレ作成のスクリプトを実行。更に短く、かつ最新のバンドルを利用するように修正しました。
#!/bin/sh # Create Project "hello" pax-create-project -g org.cytoscape -a hello cd hello # Add SpringSource Enterprise Bundle Repositories pax-add-repository -i com.springsource.repository.bundles.release -u http://repository.springsource.com/maven/bundles/release pax-add-repository -i com.springsource.repository.bundles.external -u http://repository.springsource.com/maven/bundles/external # Import required bundles pax-import-bundle -g org.springframework.osgi -a org.springframework.osgi.extender -v 1.1.0 -- -DimportTransitive -DwidenScope # Create bundles pax-create-bundle -p org.cytoscape.helloservice -- -Dspring=2.5.5 -Djunit pax-create-bundle -p org.cytoscape.helloserviceuser -- -Dspring=2.5.5 -Djunit # Build and run the project mvn clean install pax:provision
実行されているバンドルをpsコマンドで確認する
-> ps START LEVEL 6 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.0.4) [ 1] [Active ] [ 5] Spring DM Extender (1.1.0) [ 2] [Active ] [ 5] Apache Commons Logging (1.1.1) [ 3] [Active ] [ 5] Spring Beans (2.5.5.A) [ 4] [Active ] [ 5] Spring Context (2.5.5.A) [ 5] [Active ] [ 5] Spring Core (2.5.5.A) [ 6] [Active ] [ 5] Spring DM Core (1.1.0) [ 7] [Active ] [ 5] AOP Appliance API (1.0.0) [ 8] [Active ] [ 5] Spring AOP (2.5.5.A) [ 9] [Active ] [ 5] Spring DM IO (1.1.0) [ 10] [Active ] [ 5] org.cytoscape.helloservice (1.0.0.SNAPSHOT) [ 11] [Active ] [ 5] org.cytoscape.helloserviceuser (1.0.0.SNAPSHOT) [ 12] [Active ] [ 1] osgi.compendium (4.0.1) [ 13] [Active ] [ 1] Apache Felix Shell Service (1.0.1) [ 14] [Active ] [ 1] Apache Felix Shell TUI (1.0.1) ->
Spring DM Extenderが、"Spring-Powered"なバンドルを認識しているか確認する
System Bundle (0) provides: --------------------------- org.osgi.service.startlevel.StartLevel org.osgi.service.packageadmin.PackageAdmin Spring DM Extender (1) provides: -------------------------------- org.springframework.beans.factory.xml.NamespaceHandlerResolver org.xml.sax.EntityResolver org.cytoscape.helloservice (10) provides: ----------------------------------------- org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext, org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext, org.springframework.context.ConfigurableApplicationContext, org.springframework.context.ApplicationContext, org.springframework.context.Lifecycle, org.springframework.beans.factory.ListableBeanFactory, org.springframework.beans.factory.HierarchicalBeanFactory, org.springframework.context.MessageSource, org.springframework.context.ApplicationEventPublisher, org.springframework.core.io.support.ResourcePatternResolver, org.springframework.beans.factory.BeanFactory, org.springframework.core.io.ResourceLoader, org.springframework.beans.factory.DisposableBean org.cytoscape.helloserviceuser (11) provides: --------------------------------------------- org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext, org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext, org.springframework.context.ConfigurableApplicationContext, org.springframework.context.ApplicationContext, org.springframework.context.Lifecycle, org.springframework.beans.factory.ListableBeanFactory, org.springframework.beans.factory.HierarchicalBeanFactory, org.springframework.context.MessageSource, org.springframework.context.ApplicationEventPublisher, org.springframework.core.io.support.ResourcePatternResolver, org.springframework.beans.factory.BeanFactory, org.springframework.core.io.ResourceLoader, org.springframework.beans.factory.DisposableBean Apache Felix Shell Service (13) provides: ----------------------------------------- org.apache.felix.shell.ShellService, org.ungoverned.osgi.service.shell.ShellService ->
大丈夫そうなので、shutdownにて一旦終了します。helloディレクトリに移動。ディレクトリ内はこんな感じです:
drwxr-xr-x 7 kono users 4.0K 7月 8 17:32 . drwxr-xr-x 112 kono users 4.0K 7月 8 17:32 .. drwxr-xr-x 4 kono users 4.0K 7月 8 17:32 org.cytoscape.helloservice drwxr-xr-x 4 kono users 4.0K 7月 8 17:32 org.cytoscape.helloserviceuser -rw-r--r-- 1 kono users 2.6K 7月 8 17:32 pom.xml drwxr-xr-x 4 kono users 4.0K 7月 8 17:32 poms drwxr-xr-x 2 kono users 4.0K 7月 8 17:32 provision drwxr-xr-x 4 kono users 4.0K 7月 8 17:32 runner
ではEclipseのプロジェクトを作成しましょう。
mvn pax:eclipse
そしてそれをEclipseへインポートします。予め、Speing IDEとm2eclipseプラグインをインストールしておいてください。Mavenプロジェクトとしてインポートします。helloserviceとhelloserviceuserだけインポートすれば大丈夫です:
インポート後のプロジェクトはこんな感じになります。プロジェクト名を右クリックして、Springプロジェクトとしての属性を設定してください。
OSGiサービスの作成
以下は前提条件として、Springの基礎の基礎を知っていると仮定して話を進めます。
ではまずサービスから作りましょう。とは言っても、もうすでにサンプルのbeanが自動生成されていますので、それを元に作ることにします。ExampleBeanというインターフェースの方に、メソッドを一つ加えましょう:
public interface ExampleBean { public boolean isABean(); public void sayHello(); }
そしてそれを実装:
public class ExampleBeanImpl implements ExampleBean { public boolean isABean() { return true; } public void sayHello() { System.out.println("Hello, OSGi World!"); } }
次は、このBeanをOSGiサービスとしてエクスポートしてみましょう。もうすでにBeanとしての定義は自動生成されています:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- regular spring configuration file defining the beans for this bundle. We've kept the osgi definitions in a separate configuration file so that this file can easily be used for integration testing outside of an OSGi environment --> <bean name="myExampleBean" class="org.cytoscape.helloservice.internal.ExampleBeanImpl" /> </beans>
このBeanをOSGiサービスとしてエクスポートするには、bundle-context-osgi.xmlの方をいじります:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd"> <!-- definitions using elements of the osgi namespace can be included in this file. There is no requirement to keep these definitions in a separate file if you do not want to. The rationale for keeping these definitions separate is to facilitate integration testing of the bundle outside of an OSGi container --> <osgi:service id="helloServiceOSGi" ref="myExampleBean" interface="org.cytoscape.helloservice.ExampleBean" /> </beans>
たったこれだけです。では実行してみましょう。プロジェクトのトップディレクトリで、コマンドラインから
mvn clean install pax:provision
とタイプします。psコマンドでhelloserviceのIDを確認(今回は10)。そしてサービスの状態を確認します:
-> services 10 org.cytoscape.helloservice (10) provides: ----------------------------------------- Bundle-SymbolicName = org.cytoscape.helloservice Bundle-Version = 1.0.0.SNAPSHOT objectClass = org.cytoscape.helloservice.ExampleBean org.springframework.osgi.bean.name = myExampleBean service.id = 23 ---- Bundle-SymbolicName = org.cytoscape.helloservice Bundle-Version = <unknown value type> objectClass = org.springframework.osgi.context.DelegatedExecutionOsgiBundleApplicationContext, org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext, org.springframework.context.ConfigurableApplicationContext, org.springframework.context.ApplicationContext, org.springframework.context.Lifecycle, org.springframework.beans.factory.ListableBeanFactory, org.springframework.beans.factory.HierarchicalBeanFactory, org.springframework.context.MessageSource, org.springframework.context.ApplicationEventPublisher, org.springframework.core.io.support.ResourcePatternResolver, org.springframework.beans.factory.BeanFactory, org.springframework.core.io.ResourceLoader, org.springframework.beans.factory.DisposableBean org.springframework.context.service.name = org.cytoscape.helloservice service.id = 24
たった今作ったバンドルが、org.cytoscape.helloservice.ExampleBeanと言うインターフェイスを窓口として、OSGiサービスとして公開されています。実装はこのサービスの利用者からは見えなくなっていて、実装とAPIがきれいに分離されています。これで成功です。
インターフェイス、実装ともに、OSGiはおろか、Springにも非依存のコードになっている事が確認していただけたと思います。
OSGiサービスを利用するバンドルを作製する
次はサービスを利用するバンドルを書きます。話を簡略化するため、インテグレーションテストについては考えないことにして、helloserviceuserバンドルのテストディレクトリの中にあるExampleBeanIntegrationTestと言うファイルを削除します。そして、このバンドルはhelloserviceの中のクラスを利用しますので、pom.xmlファイルに依存を明示しましょう:
<dependency> <groupId>org.cytoscape.hello</groupId> <artifactId>org.cytoscape.helloservice</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
これをpomの
ではまず、OSGiサービスをインポートしましょう。bundle-context-osgi.xmlを書きます:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:osgi="http://www.springframework.org/schema/osgi" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi-1.0.xsd"> <!-- definitions using elements of the osgi namespace can be included in this file. There is no requirement to keep these definitions in a separate file if you do not want to. The rationale for keeping these definitions separate is to facilitate integration testing of the bundle outside of an OSGi container --> <osgi:reference id="helloServiceRef" interface="org.cytoscape.helloservice.ExampleBean" /> </beans>
これでSpring DMが、OSGiサービスレジストリの中から、ExampleBeanというインターフェイスを公開しているサービスを探して来て、helloServiceRefと言う名前のBeanとしてインポートします。
次に実際にサービスを利用するクラスを書きます:
package org.cytoscape.helloserviceuser.internal; import org.cytoscape.helloservice.ExampleBean; public class HelloConsumer { private ExampleBean helloService; public void setHelloService(ExampleBean helloService) { this.helloService = helloService; this.helloService.sayHello(); } }
この状態では、とりあえずhelloServiceが初期化された際に、sayHello()メソッドが実行されます。そして、こいつに先ほどのサービスをインジェクトします:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- regular spring configuration file defining the beans for this bundle. We've kept the osgi definitions in a separate configuration file so that this file can easily be used for integration testing outside of an OSGi environment --> <bean name="myExampleBean" class="org.cytoscape.helloserviceuser.internal.ExampleBeanImpl" /> <bean name="helloServiceUser" class="org.cytoscape.helloserviceuser.internal.HelloConsumer"> <property name="helloService" ref="helloServiceRef" /> </bean> </beans>
方法は、普通のBeanと全く同じです。Setter Injectionを使っています。
では実行します。
mvn clean install pax:provision
(略) 情報: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1884174: defining beans [helloServiceRef,myExampleBean,helloServiceUser]; root of factory hierarchy Hello, OSGi World! 2008/07/08 19:27:20 org.springframework.osgi.context.support.AbstractOsgiBundleApplicationContext publishContextAsOsgiServiceIfNecessary 情報: Publishing application context as OSGi service with properties {org.springframework.context.service.name=org.cytoscape.helloserviceuser, Bundle-SymbolicName=org.cytoscape.helloserviceuser, Bundle-Version=1.0.0.SNAPSHOT}
無事にサービスが利用されています。サービスは自動的に同期され、サービスがユーザーの方に注入されています。Spring DMを使わないと、ここはServiceTrackerと言うOSGi依存のクラスを利用することになるのですが、そのあたりのことをSpringがやってくれます。
さて、ここからがOSGiのダイナミズムに関係する部分です。ここでユーザーのバンドルを停止しましょう:
START LEVEL 6 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.0.4) [ 1] [Active ] [ 5] Spring DM Extender (1.1.0) [ 2] [Active ] [ 5] Apache Commons Logging (1.1.1) [ 3] [Active ] [ 5] Spring Beans (2.5.5.A) [ 4] [Active ] [ 5] Spring Context (2.5.5.A) [ 5] [Active ] [ 5] Spring Core (2.5.5.A) [ 6] [Active ] [ 5] Spring DM Core (1.1.0) [ 7] [Active ] [ 5] AOP Appliance API (1.0.0) [ 8] [Active ] [ 5] Spring AOP (2.5.5.A) [ 9] [Active ] [ 5] Spring DM IO (1.1.0) [ 10] [Active ] [ 5] org.cytoscape.helloservice (1.0.0.SNAPSHOT) [ 11] [Active ] [ 5] org.cytoscape.helloserviceuser (1.0.0.SNAPSHOT) [ 12] [Active ] [ 1] osgi.compendium (4.0.1) [ 13] [Active ] [ 1] Apache Felix Shell Service (1.0.1) [ 14] [Active ] [ 1] Apache Felix Shell TUI (1.0.1) -> stop 11 2008/07/08 19:30:17 org.springframework.osgi.context.support.AbstractOsgiBundleApplicationContext doClose 情報: Application Context service already unpublished 2008/07/08 19:30:17 org.springframework.context.support.AbstractApplicationContext doClose 情報: Closing org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@17725c4: display name [OsgiBundleXmlApplicationContext(bundle=org.cytoscape.helloserviceuser, config=osgibundle:/META-INF/spring/*.xml)]; startup date [Tue Jul 08 19:27:19 PDT 2008]; root of context hierarchy 2008/07/08 19:30:17 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons 情報: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1884174: defining beans [helloServiceRef,myExampleBean,helloServiceUser]; root of factory hierarchy -> ps START LEVEL 6 ID State Level Name [ 0] [Active ] [ 0] System Bundle (1.0.4) [ 1] [Active ] [ 5] Spring DM Extender (1.1.0) [ 2] [Active ] [ 5] Apache Commons Logging (1.1.1) [ 3] [Active ] [ 5] Spring Beans (2.5.5.A) [ 4] [Active ] [ 5] Spring Context (2.5.5.A) [ 5] [Active ] [ 5] Spring Core (2.5.5.A) [ 6] [Active ] [ 5] Spring DM Core (1.1.0) [ 7] [Active ] [ 5] AOP Appliance API (1.0.0) [ 8] [Active ] [ 5] Spring AOP (2.5.5.A) [ 9] [Active ] [ 5] Spring DM IO (1.1.0) [ 10] [Active ] [ 5] org.cytoscape.helloservice (1.0.0.SNAPSHOT) [ 11] [Resolved ] [ 5] org.cytoscape.helloserviceuser (1.0.0.SNAPSHOT) [ 12] [Active ] [ 1] osgi.compendium (4.0.1) [ 13] [Active ] [ 1] Apache Felix Shell Service (1.0.1) [ 14] [Active ] [ 1] Apache Felix Shell TUI (1.0.1)
停止したのが確認できます。そしてふたたび起動してみます:
-> start 11 2008/07/08 19:30:24 org.springframework.osgi.extender.support.DefaultOsgiApplicationContextCreator createApplicationContext 情報: Discovered configurations {osgibundle:/META-INF/spring/*.xml} in bundle [org.cytoscape.helloserviceuser (org.cytoscape.helloserviceuser)] -> 2008/07/08 19:30:24 org.springframework.context.support.AbstractApplicationContext prepareRefresh 情報: Refreshing org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@1e13e07: display name [OsgiBundleXmlApplicationContext(bundle=org.cytoscape.helloserviceuser, config=osgibundle:/META-INF/spring/*.xml)]; startup date [Tue Jul 08 19:30:24 PDT 2008]; root of context hierarchy 2008/07/08 19:30:24 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 情報: Loading XML bean definitions from URL [bundle://11.0:0/META-INF/spring/bundle-context-osgi.xml] 2008/07/08 19:30:24 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions 情報: Loading XML bean definitions from URL [bundle://11.0:0/META-INF/spring/bundle-context.xml] 2008/07/08 19:30:24 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory 情報: Bean factory for application context [org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@1e13e07]: org.springframework.beans.factory.support.DefaultListableBeanFactory@15718f2 2008/07/08 19:30:24 org.springframework.osgi.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor stageOne 情報: No outstanding OSGi service dependencies, completing initialization for OsgiBundleXmlApplicationContext(bundle=org.cytoscape.helloserviceuser, config=osgibundle:/META-INF/spring/*.xml)2008/07/08 19:30:24 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons 情報: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@15718f2: defining beans [helloServiceRef,myExampleBean,helloServiceUser]; root of factory hierarchy Hello, OSGi World! 2008/07/08 19:30:24 org.springframework.osgi.context.support.AbstractOsgiBundleApplicationContext publishContextAsOsgiServiceIfNecessary 情報: Publishing application context as OSGi service with properties {org.springframework.context.service.name=org.cytoscape.helloserviceuser, Bundle-SymbolicName=org.cytoscape.helloserviceuser, Bundle-Version=1.0.0.SNAPSHOT}
helloserviceuserバンドルが再起動されたのと同時に、Springが再びサービスを探しだし、自動的にユーザーへ注入しました。サービスがきちんと使われているか確認してみます:
-> services -u 11 org.cytoscape.helloserviceuser (11) uses: ----------------------------------------- objectClass = org.xml.sax.EntityResolver service.id = 22 ---- objectClass = org.springframework.beans.factory.xml.NamespaceHandlerResolver service.id = 21 ---- Bundle-SymbolicName = org.cytoscape.helloservice Bundle-Version = 1.0.0.SNAPSHOT objectClass = org.cytoscape.helloservice.ExampleBean org.springframework.osgi.bean.name = myExampleBean service.id = 23
大丈夫ですね。では、サービスそのものを停止してしまったらどうなるんでしょう?
-> stop 10 2008/07/08 19:36:50 org.springframework.osgi.context.support.AbstractOsgiBundleApplicationContext doClose 情報: Application Context service already unpublished 2008/07/08 19:36:50 org.springframework.context.support.AbstractApplicationContext doClose 情報: Closing org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext@a4e743: display name [OsgiBundleXmlApplicationContext(bundle=org.cytoscape.helloservice, config=osgibundle:/META-INF/spring/*.xml)]; startup date [Tue Jul 08 19:27:19 PDT 2008]; root of context hierarchy 2008/07/08 19:36:50 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons 情報: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@bac9b9: defining beans [helloServiceOSGi,myExampleBean]; root of factory hierarchy 2008/07/08 19:36:50 org.springframework.osgi.service.exporter.support.OsgiServiceFactoryBean unregisterService 情報: Unregistered service [ServiceWrapper for org.apache.felix.framework.ServiceRegistrationImpl@1142196] -> services -u 11 org.cytoscape.helloserviceuser (11) uses: ----------------------------------------- objectClass = org.xml.sax.EntityResolver service.id = 22 ---- objectClass = org.springframework.beans.factory.xml.NamespaceHandlerResolver service.id = 21 ->
サービスの提供元が停止すると、そのサービスの利用も停止しました。このような同期を(かなりの部分)自動的に面倒を見てくれるのがSpring DMです。
何が便利なのか、これだけでは実感がわかないかもしれませんが、OSGiのAPIに依存しているコードも書いてみて、比較してみると、サービスやオブジェクトのワイアリングを外に出したこのバージョンの方が、コードがスッキリしていると感じられると思います。
ここで書かれたクラス群は、全てPOJOです。従って、他の環境で利用することもそれほど大変ではありません。また、OSGiバンドルと言っても、要するにただのJARファイルにメタデータを加えただけですから、普通のライブラリとして再利用することも勿論可能です。こういった特長が、このフレームワークで作製されたコンポーネントの再利用性を高めるポイントになっています。