RMIでいいじゃないか

ここで何度も採り上げてきた話題として、複数のクライアントアプリケーション間でオブジェクトを共有するという要件があった。C#では.NET Remotingで書いていた機能をJavaでどう実現するかというのが肝だった。

以前には同様にRMIで実装するつもりでいたが、いろいろと問題があると思いこみ、

  • クライアントデータ(オブジェクト)の共有

Javaには元々RMIという立派な仕組みがあるんだが、rmiregistryというリポジトリの常駐が必要なこと、(対して.NET Remotingは特別なインフラやリポジトリは必要とせずに配置されたオブジェクトは生き続けることができる)いちいちrmicによりスタブ・スケルトン(スケルトンは1.2の時点で不要になった)のコンパイルが必要なことは、10年前であれば我慢できたが今となっては使いたくないのが本音だ。※
〜中略〜
※J2SE5以降はスタブの生成も不要になった。JavaTM RMI リリースノート - JavaTM SE Development Kit (JDK) 6 の拡張機能
あとは、rmiregistryも不要になればいいんだけどなぁ。
代替困難

と愚痴をこぼしていたのだが、これはちゃんと勉強してから書くべきだった。というのも現在の仕様のRMIであれば、ほとんど.NET Remotingと変わらない運用で使えることが判ったからだ。(さすがに.NET RemotingのようにIPCプロトコルは選べないが)

  • rmicによるスタブ・スケルトンの生成は不要

元々のRMIはリモートオブジェクトを公開するサーバに対して"スケルトン"、アクセスするクライアントに対して"スタブ"を事前にコンパイルする必要があってこれが面倒だったのだが、J2SE5以降はスタブもスケルトンも必要が無くなり、オブジェクトを公開するのに必要な要件は、公開するインタフェースはjava.rmi.Remoteから継承してRemoteExceptionをスローするようにすることだけとなった。

public interface IFoo extends Remote {
    void foo() throws RemoteException;
}
public cladd FooImpl implement IFoo {
    //UnicastRemoteObjectから継承することもできるが、export前提であればそれすら不要
}
  • rmiregistryとリモートオブジェクトのバインドはデマンドで実行可能

RMIはリモートオブジェクトをバインドしておくレジストリが必要であり、これは上記のスタブ・スケルトンが不要になっても変わらない。しかし、よくよく調べてみるとローカルPC上で使用するためのレジストリであればデマンドで生成可能であり、データをリモートオブジェクトとしてサービングするアプリケーションが自ら起動してオブジェクトを公開できるのだ。

Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
IFoo foo = new FooImpl();
Remote remote= UnicastRemoteObject.exportObject(foo, 0);
registry.rebind("foo", remote);     

これだけだ。RMIレジストリといえばORBのエージェントのように別プロセスで起動、常駐させておく必要があると思い込んでいたのだが、それは私の思い込み?だったようだ。
これならば.NET Remotingと比べてもシンプルさでは負けていないし、セキュリティポリシの必要がないのであれば、設定ファイルも必要無い。必要なのはRMIプロトコルで使用するTCPポート(1099)を開けておくだけ。

クライアントからアクセスするコードも非常にシンプルだ。

IFoo foo = (IFoo)Naming.lookup("foo");

複数のアプリケーションでデータを共有する方法として、「どのようにデータを公開するか」が解決したならば、次に要となるのは「誰がオブジェクトを公開するのか」だ。ファサードや任意のメニューを設定しているシステムであれば別だが、一般的に複数インストールされているクライアントアプリケーションのどれを起動するかはユーザの都合によって決定される。
最初に起動されたアプリケーションは共有するオブジェクトを公開する責務があり、上記のようにRMIレジストリを生成してデータを収集した後にオブジェクトとしてレジストリにバインドしなくてはならない。以降、起動されたアプリケーションはRMIレジストリをルックアップすることでデータを共有する。

ここで重要なのは「最初に起動された奴がオブジェクトを生成して公開する」ということである。それには「すでにそのオブジェクトが公開されていないか」を判断する必要があるのだが、上記の方法であれば、レジストリの生成時にすでに他の誰かが起動済みであればポートがバインドできずに例外がスローされるので、それが判断材料になるだろう。

java.rmi.server.ExportException: Port already in use: 1099; nested exception is: 
	java.net.BindException: Address already in use: JVM_Bind
try {
    Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
} catch (ExportException e) {
   //既に誰かがレジストリを起動済み
}

なお、スタンドアロンRMIを使用可能にする方法に関しては以下のエントリを参考にさせて頂いた。

スタブ不要なRMI - Javaとか
RMIでNoSuchObjectExceptionが出る件 - きしだのはてな