14-2 RMI使用のサンプル


・ RMIを利用したサンプルを紹介しましょう。 RMIは広い範囲に応用が可能ですが、 最もオーソドックスなケースを考えてみましょう。 ネットワークを通じて外部から問い合わせを受け付け、 それに応じて検索処理を行って結果を返す分散オブジェクトを実現します。 最初に行うべきことは、分散オブジェクトが提供するサービスを決めることです。 そのために次のようなインターフェイス RemoteDictionary.java を定義します。
 インターフェイスの名前は RemoteDictionary としました。 後で明らかになるように、このオブジェクトはキーとなる英単語を受け取り 対応する日本語の解説を返す機能を実装するからです。 抽象メソッド answer() が RMIの機構によって呼び出されることになるメソッドです。 引数として Objectのオブジェクトを受け取り、検索結果に対応するオブジェクトを 返す仕様になっています。 引数や返値の型が Object になっているのは、単に機能を汎用なものにして 将来の拡張を容易にしたかったためです。
 このインターフェイスは分散オブジェクト自身のプログラムだけではなく、 サービスの提供を受けるクライアントにとっても重要です。 実際クライアント側のプログラムを記述するには、 このインターフェイスに定義された情報があれば十分なのです。 (具体的な例は後で紹介します。)
 サンプルを眺めていただければすぐわかるように、 RMIのためのインターフェイス定義では通常の場合と異なる部分が2カ所ほどあります。 まず第1にインターフェイスは java.rmi.Remote のサブクラスとなっています。 Remote は、それ自身は全く内容を持たないインターフェイスです。 以前紹介した java.io.Serializable などと同様で、 クラスを性質に応じてグループ分けするう目的のために存在します。 RMIによって取り扱われる分散オブジェクトは、 必ず Remoteのサブクラスでなければなりません。
 第2に RMIを通じて呼び出されるメソッドは、必ず RemoteException の 例外を発生できるように定義します。 RemoteExceptionはやはり java.rmiパッケージに定義され、 なんらかの理由で RMIによるメソッドの呼び出しに失敗した時に発生します。

・ 次に分散オブジェクトのクラスのサンプル RemoteDictionaryImpl.java を見てみましょう。 上記のインターフェイスに定義されたメソッドの内容を具体的に記述します。
 分散オブジェクトのクラス名は RemoteDictionaryImplとします。 辞書の役割をする java.util.Hashtableクラスのオブジェクトを1個生成し、 そこに英語と日本語の対応づけた情報を記憶します。 answer()メソッドの仕事は、 外部から与えられたオブジェクトをキーと解釈し、 それに対応する日本語の解説を検索して返すことです。 (登録されていないキーが指定された場合は null が返ります。) このサンプルでは理解しやすいようにわずか3個の単語しか登録されていませんが、 もちろん単語数を増やすことは容易です。 また Hashtableには任意のオブジェクトを記憶させることができますから、 返値として返す内容を単純な文字列から拡張してみるのも面白いでしょう。
 分散オブジェクトのクラスの定義が通常の場合と異なる部分は1カ所 だけ存在します。 それは UnicastRemoteObjectクラスを継承している点です。 このクラスは java.rmi.serverパッケージによって提供されます。 中身のないインターフェイスである Remoteとは異なり、 UnicastRemoteObjectは分散オブジェクトとして必要な機能を提供する重要な役割を 担っています。 具体的には SocketによるTCPのコネクションを確立し、 サーバーとクライアントの間でメソッドの引数や 返値のデータをやりとりできるようにする仕事です。 その内部まで立ち入って見ていくとけっこう面倒そうですが、 幸いなことに通常の RMIのプログラムの表面には UnicastRemoteObjectの 提供する機能は登場しません。 「縁の下の力持ち」とて隠されているのです。 そのためプログラマは UnicastRemoteObjectが どのようにしてネットワークの機能を実現しているのか知る必要がないわけです。 したがって分散オブジェクトが固有に提供するサービスの内容 (今の場合は Hashtabelの生成とメソッドの実装) にのみ集中することができます。

・ さて RMIによる分散オブジェクトの準備はできましたが、 これをどのようにして利用すればいいのでしょうか? 実は特別なルールがあるわけではなく、 アプリケーションの中で通常のオブジェクトと同じように自由に生成して かまわないのです。 しかし、あまりに複雑なケースだと混乱してしまう恐れがあるので、 ここでも最も基本的なパターンを取り扱うことにしましょう。 一方のアプリケーションがサーバーとして機能し、 サービスを提供する分散オブジェクトを生成します。 もう一方のアプリケーションがクライアントとしてそのオブジェクトに ネットワークを通じてアクセスし、メソッドを RMIによって呼び出すことにします。
 最初にサーバーの側のサンプルプログラム DictionaryServer.javaです。
 あまりにあっさりしたプログラムのため拍子抜けしたかもしれません。 本質的な仕事は2行分しかありません。 最初の重要な仕事は分散オブジェクト(RemoteDictionaryImpl)の生成です。 これは単に先のサンプルで定義されたコンストラクタを呼び出しているだけです。
 もう1つ必要な作業が「ネームサーバへの登録」です。 RMIの通信はオブジェクトに割り振られた「名前」を手がかりにして相手を特定します。 その管理を一元的に行うのがネームサーバの役割です。 ネームサーバがどのような形で実現されているかは、 それぞれのネットワークのサイトによって異なる可能性があります。 以下の説明では JDKに付属の rmiregistryサーバを用いる場合を解説しますが、 原理的にはそれと異なるネームサーバを用いていてもかまいません。 Namingクラスは、そうしたネームサーバの実装の違いに依存しない形式で ネームサーバとのやりとりを行う機能を提供してくれます。
 Namingの rebind()メソッドに与えられている最初の引数の文字列は URLを表しています。 URLを用いることで各オブジェクトに一意の名前が割り振られます。 ここではネームサーバとサンプルプログラムは同じマシン上で起動されると 仮定しているために、マシン名に当たる部分は省略されています。 ポート番号もデフォルト値(1099)を使用することを仮定しています。 もしきちんと書くとすると、たとえば "rmi://hostname:1099/RemoteDictionaryServer"となるわけです。
 プログラムの他の部分はメッセージの表示や例外処理などにすぎませんが、 先頭の行だけは特別な意味がありそうです。 この行はセキュリティのポリシーを定める SecurityManager を設定しています。 RMIはネットワークを用いて動作しますから、当然セキュリティには十分配慮が 必要です。独自のカスタマイズされた SecurityManagerを用意して用いること も可能ですし、そうでない場合は上のサンプルのように RMISecurityManagerの オブジェクトを生成し、デフォルトとして設定してください。 JDK1.2 ではこのセキュリティの取り扱いがかなり厳格に行われるようになりました。 もっとも厳格すぎて実行の際に多少手続きが面倒になっています。 それについては次の「実行の手順」の部分で詳しく述べることにします。

・ クライアントの側のサンプル SingleLineClient.java もサーバーと同じ程度に単純です。
 クライアントの側も行っている仕事は本質的には2つだけです。 まずネームサーバに問い合わせて通信相手の分散オブジェクトを見つけます。 ここでも Namingクラスの機能を利用します。 指定する URLは先ほどのサーバーのサンプルと同じものです。 ただし、クライアントのプログラムは一般に他のマシンの VM上で起動されます から、サーバーの名前を明示的に記述する必要があります。 ここではコマンドラインの引数で渡された最初の文字列をサーバー名として 解釈することにします。
 2番目の仕事は分散オブジェクトのメソッドを実行することです。 上のサンプルでは確かに answer()メソッドが呼び出され、 サーバーからの返答として値が返っているのがわかるはずです。 answer()メソッドのキーとして与える英単語も、 コマンドラインの引数として与えることができるようにします。
 クライアント側での分散オブジェクトの取り扱いが、 RMIのプログラミングのポイントになります。 サンプルを見てわかるように、クライアントの側にもあたかも分散オブジェクトが 存在しているかのような記述になっています。 (これは Javaの文法上メソッドを呼び出すためには必須の条件です。) この object という変数名のオブジェクトの正体は何でしょうか? 見かけは RemoteDictionaryとして宣言されています。 しかし RemoteDictionary はインターフェイスですからオブジェクトは生成できない はずです。では RemoteDictionaryImplでしょうか? それも違います。 RemoteDictionaryImplはサーバー側に生成されます。 クライアント側の VMの中には存在しないはずです。
 この疑問を解決したければ、 サンプルで実際に dictionaryのオブジェクトのクラス情報も 標準出力に表示させてみればよいでしょう。 先に答を書いてしまうと、 RemoteDictionaryImpl_Stub というクラスであることがわかるはずです。