このチュートリアルでは、おなじみのHello Worldプログラムの分散システム版をJava Remote Method Invocation (Java RMI)を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。その回答は、Java RMI FAQで見つかることがあります。
ここで例として紹介する分散型の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
呼出しの結果として、ランタイムでは、リモート・オブジェクトを求める着信リモート呼出しを受け取るために、新規のサーバー・ソケット上で待機を開始するか、共有サーバー・ソケットを使うことができるようになります。返されたスタブには、リモート・オブジェクトのクラスと同じリモート・インタフェースのセットが実装され、リモート・オブジェクトに連絡できるホスト名とポートが含まれています。
注意: スタブ・クラスを生成する方法の詳細は、rmic
のツール・ドキュメント(UNIX用、Windows用)を参照してください。事前に生成したスタブ・クラス付きでアプリケーションを配置する方法の詳細については、コードベースのチュートリアルを参照してください。
呼出し側(クライアント、ピア、またはアプレット)がリモート・オブジェクトのメソッドを呼び出すために、呼出し側は最初にリモート・オブジェクトのスタブを取得する必要があります。ブートストラッピング用に、アプリケーションが名前をリモート・オブジェクトのスタブにバインドし、クライアントがそのスタブを取得するためにリモート・オブジェクトを名前で検索できるようにするために、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は、クラス・ファイルを配置するデスティネーション・ディレクトリです。
注意: 事前に生成したスタブ・クラス付きでアプリケーションを配置する方法の詳細は、コードベースのチュートリアルを参照してください。
この例を実行するには、次の操作を行う必要があります。
レジストリを起動するには、サーバーのホストでrmiregistry
コマンドを実行します。このコマンドからは成功時に何の出力もありません。通常、バックグラウンドで実行されます。詳細は、rmiregistry
のツール・ドキュメント(UNIX用、Windows用)を参照してください。
たとえば、Solarisオペレーティング・システムでは:
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は、クラス・ファイル・ツリーのルート・ディレクトリです。java.rmi.server.codebase
システム・プロパティを設定することで、レジストリがリモート・インタフェース定義をロードできるようになります(末尾のスラッシュが重要であることに注意してください)。このプロパティの使用方法の詳細は、コードベースのチュートリアルを参照してください。
サーバーからの出力は、次のようになります。
Server ready
ユーザーによって終了させられる(通常はプロセスの強制終了)まで、サーバーは実行を続けます。
サーバーの準備ができると、次のようにしてクライアントを実行できます。
java -classpath classDir example.hello.Client
ここで、classDirは、クラス・ファイル・ツリーのルート・ディレクトリです。
クライアントから出力されるのは、次のメッセージです。
response: Hello, world!