久しぶりに OSGi でもいじってみるかと思い立つ。
Wikipedia に載ってる Strategy パターンの サンプル・コードでも OSGi で動かしてみよっかなと。今回は、Equinox でやってみる。
OSGi 環境を作る
- 適当な場所に osgi-test フォルダを作る
- osgi-test フォルダ 下に plugins フォルダ を作る
- Eclipse の plugins下から以下のファイルを osgi-test/plugins フォルダ 下にコピーする。
- org.eclipse.osgi_*.jar
- org.eclipse.equinox.ds_*.jar
- org.eclipse.equinox.util_*.jar
- org.eclipse.osgi.services_*.jar
うちの環境だとこんな感じになる
$ cd /tmp/osgi-test
$ tree
.
└── plugins
├── org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
├── org.eclipse.equinox.util_1.0.300.v20110502.jar
├── org.eclipse.osgi.services_3.3.0.v20110513.jar
└── org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar
起動して、他のプラグインも追加しておく
$ java -jar plugins/org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.osgi.services_3.3.0.v20110513.jar
Bundle id is 1
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.util_1.0.300.v20110502.jar
Bundle id is 2
osgi> install file:///tmp/osgi-test/plugins/org.eclipse.equinox.ds_1.3.1.R37x_v20110701.jar
Bundle id is 3
osgi> start 1
osgi> start 2
osgi> start 3
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE org.eclipse.equinox.ds_1.3.1.R37x_v20110701
環境が出来たので、以降ではプラグインを作って動かしてみる。先に書いたとおり、Wikipedia にある Strategy のサンプルコードをネタにして、Strategy、ConcreteStrategyAdd、ConcreteStrategyMultiply、Context を連動させてみる。
Strategy の作成〜インストール
まずは、Strategy プラグインプロジェクトの作成
できたら以下のように Export しておく。
- プロジェクトのコンテキストメニューから、[Export]->[Deployable plug-ins and fragments] を選択
- [Destination] / [Directory]に、osgi-test フォルダを指定する
- [Options] で、"Package plug-ins as individual JAR archives"のみチェックされている事を確認して[Finish]
エクスポートしたら、OSGi に install しておく
osgi> install file:///tmp/osgi-test/plugins/ex1.osgi.strategy.strategy_1.0.0.201111202011.jar
Bundle id is 4
osgi> refresh
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 RESOLVED ex1.osgi.strategy.strategy_1.0.0.201111202011
ss や refresh といったコマンドの意味は、OSGi コンソールから help と打てば、説明が出てくる。
ConcreteStrategyAdd の作成〜インストール
これも Export して、OSGi にインストールしておく。
Context の作成〜インストール
次は ConcreteStrategyMultiply をやる前に、Context に行ってみる
- [New]->[Plugin-Project]
- Project Name には "ex1.osgi.strategy.context"を指定、Activetor 無し、tempate 不使用で[Finish]
- MANIFEST.MF の [Dependencies]タブを開いて、[Imported Packages]に"ex1.osgi.strategy.strategy" を追加する。
- 以下のコードを追加する。Wikipedia のサンプルコードでは、Context#executeStrategy() を実行するのは StrategyExample#main() だけど、面倒だから strategy が関連付けられたときに Context 自身が executeStrategy() のテストコードを実行するようにした。
package ex1.osgi.strategy.context;
import ex1.osgi.strategy.strategy.Strategy;
public class Context {
private Strategy strategy;
public synchronized void setStrategy(final Strategy strategy) {
this.strategy = strategy;
testExecuteStrategy();
}
public synchronized void unsetStrategy(final Strategy strategy) {
if (strategy == this.strategy) this.strategy = null;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
public void testExecuteStrategy() {
final long l = System.currentTimeMillis();
final int a = (int) (l % 10);
final int b = (int) (l / 10 % 10);
final int result = executeStrategy(a, b);
System.out.printf("execute(%d, %d) = %d", a, b, result);
}
}
- プロジェクト直下に OSGI-INF フォルダを追加する
- OSGI-INF フォルダのコンテキストメニューから、[New]->[Component Definition]を選択
- "Component Definition Information"/Class にex1.osgi.strategy.context.Context を指定して[Finish]
- component.xml の [Service]タブで、[Referenced Services] に Strategy を追加する
- 追加した Strategy を選択して[Edit]、"Bind"にsetStrategy、"Unbind"にunsetStrategy を指定する
できたら、これもエクスポートして、OSGiにインストールしておく。
動作確認
とりあえず、ここまでで動かしてみる。
osgi> start 4
osgi> start 5
osgi> start 6
Called ConcreteStrategyAdd's execute()
execute(3, 1) = 4
osgi>
期待通りに動作する。(実は、start 4 は無くても良いし、start 5 と start 6 の順番は逆でも良い。)
ConcreteStrategyMultiply の追加と動作確認
最後に、ConcreteStrategyMultiply を付け加えて、ConcreteStrategyMultiply と差し替えてみる。
これもエクスポートして、OSGiにインストールしておく。
エクスポートしたら、Context から実行される Strategy を、Add から Multiply に変えてみる。
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE ex1.osgi.strategy.strategy_1.0.0.201111202011
5 ACTIVE ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE ex1.osgi.strategy.context_1.0.0.201111202042
7 RESOLVED ex1.osgi.strategy.multiply_1.0.0.201111202123
osgi> stop 5
osgi> start 7
Called ConcreteStrategyMultiply's execute()
execute(3, 1) = 3
osgi> ss
Framework is launched.
id State Bundle
0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1 ACTIVE org.eclipse.osgi.services_3.3.0.v20110513
2 ACTIVE org.eclipse.equinox.util_1.0.300.v20110502
3 ACTIVE org.eclipse.equinox.ds_1.3.1.R37x_v20110701
4 ACTIVE ex1.osgi.strategy.strategy_1.0.0.201111202011
5 RESOLVED ex1.osgi.strategy.add_1.0.0.201111202028
6 ACTIVE ex1.osgi.strategy.context_1.0.0.201111202042
7 ACTIVE ex1.osgi.strategy.multiply_1.0.0.201111202123
osgi> stop 7
osgi> start 5
Called ConcreteStrategyAdd's execute()
execute(4, 3) = 7
osgi>
Context を起動したまま、Strategy を差し替えられるのが確認できる。
雑感
今まで、何回か、いわゆるパッケージ開発案件に関わってきたけど、納品先個別の要件の扱い方について、何パターンかの手法がある。
一番泥臭いのだと、トランクのソースコードに個別要件対応のコードを入れてしまって、条件文で切り分けるというベタベタのローテク手法がある。「バカじゃね?」って思う人も多いかもしれないけど、実際の現場では結構多い。
それよりは、少しはマシな手法だと、納品先別にブランチを切って個別要件はそこに入れていくという事になる。まあ、これも知的な方法とは言いにくいけど、割と普通に行われている。
もっと良い方法としては、パッケージ共通部分と個別にカスタマイズ可能な部分について最初に見通しを立てた上で、カスタマイズ可能な部分がプラガブルになるようにアーキテクチャを設計する手法がある。
この手法をとると、機能の差し替えや追加、また管理面においてもリスクが少なくて、柔軟なソフトウェアになるんだけど、この場合にもさらに、プラグインの方式を自分で作るか、既存の仕様や製品を使うかで道が分かれる。
必ずしも自作にこだわる必要がないとしたら、OSGi がかなりの有力候補なのではないかと思う。(個人的には、はっきり言って、かなり高い割合で自作の道を取るのは不正解だと思うけど。)