このチュートリアルでは、おなじみの Hello World プログラムの分散システム版を Java™ Remote Method Invocation (Java RMI) を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。Java RMI FAQ および River プロジェクトの外部にあるメーリングリストおよびドキュメントの保存には、さまざまな疑問に対する回答があります。
ここで例として紹介する分散型の Hello World プログラムでは、単純なクライアントを使用して、リモートホスト上で稼働しているサーバーへのリモートメソッド呼び出しを行います。クライアントでは、サーバーから「Hello, world!」メッセージを受信します。
このチュートリアルでは、次の手順を実行します。
このチュートリアルの実行に必要なファイルは、次のとおりです。Hello.java
- リモートインタフェースServer.java
- リモートインタフェースを実装するリモートオブジェクトの実装Client.java
- リモートインタフェースのメソッドを呼び出す単純なクライアントexample.hello.Server
を指します。
java.rmi.Remote
インタフェースを拡張し、一組のリモートメソッドを宣言します。各リモートメソッドは、アプリケーション固有の例外のほかに、throws
節内で java.rmi.RemoteException
(または RemoteException
のスーパークラス) を宣言する必要があります。
次は、この例で使用される example.hello.Hello
リモートインタフェースの、インタフェース定義です。この例では、呼び出し側に文字列を返す sayHello
メソッドのみが宣言されています。
package example.hello; import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { String sayHello() throws RemoteException; }リモートメソッド呼び出しでは、ローカルメソッド呼び出しと比べて多くの過程でエラーが発生することがあります (ネットワーク通信上の問題や、サーバーの問題など)。このためリモートメソッドは、
java.rmi.RemoteException
をスローすることによって通信上の障害を報告します。分散システム上での障害および復元の詳細については、分散コンピューティングでの注意事項を参照してください。
main
メソッドがあります。このメソッドは、リモートオブジェクトの実装インスタンスを生成し、リモートオブジェクトをエクスポートして、そのインスタンスを Java RMI レジストリ内の名前にバインドします。この main
メソッドを含むクラスは、実装クラスそのものである場合も、まったく別のクラスである場合もあります。
この例のサーバー用の main
メソッドは、Hello
リモートインタフェースの実装も行う Server
クラス内に定義されています。サーバーの main
メソッドで、次の操作が実行されます。
Server
クラスのソースコードです。このサーバークラスを作成する際の説明は、ソースコードのあとにあります。
package example.hello; import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class Server implements Hello { public Server() {} public String sayHello() { return "Hello, world!"; } public static void main(String args[]) { try { Server obj = new Server(); Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0); // Bind the remote object's stub in the registry Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello", stub); System.err.println("Server ready"); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } } }
Server
実装クラスは、sayHello
リモートメソッドを実装して、Hello
リモートインタフェースを実装します。sayHello
メソッドでは、メソッドが例外をスローすることを宣言する必要はありません。メソッドの実装自体が RemoteException
やその他のどのようなチェック例外もスローしないためです。
注:クラスは、リモートインタフェースで指定されていないメソッドを定義できますが、これらのメソッドは、サービスを実行する仮想マシン内でしか呼び出すことはできず、リモートから呼び出すことはできません。
main
メソッドでは、サービスを提供するリモートオブジェクトを作成する必要があります。さらに、着信するリモート呼び出しを受信できるように、リモートオブジェクトが Java RMI ランタイムへエクスポートされる必要があります。これは、次のようにして実行できます。
Server obj = new Server(); Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0);
UnicastRemoteObject.exportObject
static メソッドは、指定されたリモートオブジェクトをエクスポートします。これにより、着信するリモートメソッド呼び出しを匿名 TCP ポートで受信し、そのリモートオブジェクトのスタブをクライアントへ渡すために返すことができるようになります。exportObject
呼び出しの結果として、ランタイムでは、リモートオブジェクトを求める着信リモート呼び出しを受け取るために、新規のサーバーソケット上で待機を開始するか、共有サーバーソケットを使うことができるようになります。返されたスタブには、リモートオブジェクトのクラスと同じリモートインタフェースのセットが実装され、リモートオブジェクトに連絡できるホスト名とポートが含まれています。
注:J2SE 5.0 リリースでは、リモートオブジェクトが 5.0 VM より前を実行しているクライアントをサポートする必要がないかぎり、rmic
スタブコンパイラを使用してリモートオブジェクトのスタブクラスを事前に生成しておく必要がなくなりました。使用しているアプリケーションがこのようなクライアントをサポートする必要がある場合は、そのアプリケーション内で使用されるリモートオブジェクトのスタブクラスを生成し、クライアントがダウンロードするようにスタブクラスを配置する必要があります。スタブクラスを生成する方法の詳細については、rmic
のツールドキュメント (Solaris 用、Windows 用) を参照してください。事前に生成したスタブクラス付きでアプリケーションを配置する方法の詳細については、「Java RMI の使用による動的なコードのダウンロード」に関するチュートリアルを参照してください。
呼び出し側 (クライアント、ピア、またはアプレット) がリモートオブジェクトのメソッドを呼び出すために、呼び出し側は最初にリモートオブジェクトのスタブを取得する必要があります。ブートストラッピング用に、アプリケーションが名前をリモートオブジェクトのスタブにバインドし、クライアントがそのスタブを取得するためにリモートオブジェクトを名前で検索できるようにするために、Java RMI にはレジストリ API が用意されています。
Java RMI レジストリは単純なネームサービスであり、このサービスにより、クライアントはリモートオブジェクトへの参照 (スタブ) を取得することができます。通常、レジストリは、クライアントが使う必要がある最初のリモートオブジェクトの場所を特定するためにだけ使用されます。そのあと、通常はその最初のオブジェクトによって、その他のオブジェクトを検出するためのアプリケーション固有のサポートが提供されます。たとえば、参照は、別のリモートメソッド呼び出しに渡すパラメータ、またはリモートメソッド呼び出しからの戻り値として取得できます。詳細は、「Java RMI にファクトリパターンを適用する」を参照してください。
リモートオブジェクトがサーバーに登録されると、呼び出し側は、そのオブジェクトを名前で検索して、リモートオブジェクトへの参照を取得できます。そのあと、そのオブジェクトのリモートメソッドを呼び出すことができます。
サーバー内の次のコードで、ローカルホストとデフォルトのレジストリポートのレジストリに対応するスタブを取得し、そのレジストリのスタブを使用して、「Hello」という名前をレジストリ内のリモートオブジェクトのスタブにバインドします。
Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello", stub);引数をとらない
LocateRegistry.getRegistry
static メソッドは、java.rmi.registry.Registry
リモートインタフェースを実装するスタブを返し、デフォルトの 1099
レジストリポート上にあるサーバーのローカルホストのレジストリへ、呼び出しを送信します。次に、リモートオブジェクトのスタブをレジストリ内の "Hello"
とバインドするために、bind
メソッドが registry
スタブで呼び出されます。
注:LocateRegistry.getRegistry
の呼び出しでは、単純に、レジストリに対応する適切なスタブを返します。この呼び出しでは、レジストリが実際に実行されているかどうかはチェックされません。bind
メソッドが呼び出されるときにローカルホストの TCP ポート 1099 でレジストリが実行されていない場合は、サーバーが RemoteException
となり、失敗します。
クライアントプログラムでは、サーバーのホスト上のレジストリに対応するスタブを取得し、レジストリ内の名前でリモートオブジェクトのスタブを検索し、そのあとそのスタブを使用して、リモートオブジェクトの sayHello
メソッドを呼び出します。
クライアントのソースコードは、次のとおりです。
package example.hello; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { private Client() {} public static void main(String[] args) { String host = (args.length < 1) ? null : args[0]; try { Registry registry = LocateRegistry.getRegistry(host); Hello stub = (Hello) registry.lookup("Hello"); String response = stub.sayHello(); System.out.println("response: " + response); } catch (Exception e) { System.err.println("Client exception: " + e.toString()); e.printStackTrace(); } } }
このクライアントは、コマンド行に指定されたホスト名を使用して LocateRegistry.getRegistry
static メソッドを呼び出すことで、レジストリに対応するスタブを最初に取得します。ホスト名が指定されていない場合は、ホスト名に null
が使用され、ローカルホストのアドレスを使用する必要があることを指示されます。
次にクライアントは、サーバーのレジストリからリモートオブジェクトに対応するスタブを取得するために、レジストリスタブで lookup
リモートメソッドを呼び出します。
最後にクライアントは、リモートオブジェクトのスタブで sayHello
メソッドを呼び出し、この呼び出しによって次の操作が発生します。
リモートオブジェクトのリモート呼び出しから返される応答メッセージは、そのあと System.out
に出力されます。
この例のソースファイルは、次のようにしてコンパイルできます。
javac -d destDir Hello.java Server.java Client.javadestDir は、クラスファイルを配置するデスティネーションディレクトリです。
注:5.0 VM 以前で実行されるクライアントをサーバーがサポートする必要がある場合は、rmic
コンパイラを使用して事前にリモートオブジェクトの実装クラスに対応するスタブクラスを生成する必要があり、そのスタブクラスをクライアントがダウンロードできるようにする必要があります。詳細は「Java RMI の使用による動的なコードのダウンロード」に関するチュートリアルを参照してください。
この例を実行するには、次の操作を行う必要があります。
レジストリを起動するには、サーバーのホストで rmiregistry
コマンドを実行します。このコマンドからは成功時に何の出力もありません。通常、バックグラウンドで実行されます。詳細は、rmiregistry
のツールドキュメント (Solaris 用、Windows 用) を参照してください。
この例の場合、Solaris (TM) オペレーティングシステムでは次のコマンドを実行します。
rmiregistry &
Windows プラットフォームでは、次のコマンドを実行します。
start rmiregistry
デフォルトでは、レジストリは TCP ポート番号 1099 で実行されます。別のポート上でレジストリを実行するには、コマンド行でポート番号を指定します。たとえば、Windows プラットフォーム上のポート 2001 でレジストリを起動するには、次のようにします。
start rmiregistry 2001
レジストリが 1099 以外のポートで実行される場合は、Server
および Client
クラス内の LocateRegistry.getRegistry
の呼び出しに、ポート番号を指定する必要があります。たとえば、この例でレジストリをポート番号 2001 で実行する場合、サーバー内の getRegistry
の呼び出しは次のようになります。
Registry registry = LocateRegistry.getRegistry(2001);
サーバーを起動するには、次の java
コマンドを使用して Server
クラスを実行します。
Solaris オペレーティングシステムの場合:
java -classpath classDir -Djava.rmi.server.codebase=file:classDir/ example.hello.Server &
Windows プラットフォームの場合:
start java -classpath classDir -Djava.rmi.server.codebase=file:classDir/ example.hello.Server
classDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」セクションの destDir を参照)。java.rmi.server.codebase
システムプロパティーを設定することで、レジストリがリモートインタフェース定義をロードできるようになります (末尾のスラッシュが重要であることに注意してください)。このプロパティーを使用する詳細については、「Java RMI の使用による動的なコードのダウンロード」に関するチュートリアルを参照してください。
サーバーからの出力は、次のようになります。
Server ready
ユーザーによって終了させられる (通常はプロセスの強制終了) まで、サーバーは実行を続けます。
サーバーの準備ができると、次のようにしてクライアントを実行できます。
java -classpath classDir example.hello.Client
classDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」セクションの destDir を参照)。
クライアントから出力されるのは、次のメッセージです。
response: Hello, world!