JavaTM RMI 入門 |
ドキュメントの目次 |
このチュートリアルでは、おなじみの Hello World プログラムの分散システム版を JavaTM Remote Method Invocation (Java RMI) を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。Java RMI FAQ および RMI-USERS メーリングリストのアーカイブには、さまざまな疑問に対する回答があります。 RMI-USERS メーリングリストを購読するには、ここをクリックしてください。
ここで例として紹介する分散型の Hello World プログラムでは、単純なクライアントを使用して、リモートホスト上で稼動しているサーバーへのリモートメソッド呼び出しを行います。クライアントでは、サーバーから「Hello, world!」メッセージを受信します。
このチュートリアルでは、次の手順を実行します。
このチュートリアルの実行に必要なファイルは、次のとおりです。Hello.java - リモートインタフェース
Server.java - リモートインタフェースを実装するリモートオブジェクトの実装
Client.java - リモートインタフェースのメソッドを呼び出す単純なクライアント
example.hello.Server を指します。
リモートオブジェクトは、「リモートインタフェース」を実装するクラスのインスタンスです。リモートインタフェースはjava.rmi.Remoteインタフェースを拡張し、一組の「リモートメソッド」を宣言します。各「リモートメソッド」は、アプリケーション固有の例外のほかに、throws節内でjava.rmi.RemoteException(またはRemoteExceptionのスーパークラス) を宣言する必要があります。以下は、この例で使用される
examples.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をスローすることによって通信上の障害を報告します。分散システム上での障害および復元の詳細については、「A Note on Distributed Computing」を参照してください。
このチュートリアルでの「サーバー」クラスには、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.exportObjectstatic メソッドは、指定されたリモートオブジェクトをエクスポートします。 これにより、着信するリモートメソッド呼び出しを匿名 TCP ポートで受信し、そのリモートオブジェクトのスタブをクライアントへ渡すために返すことができるようになります。exportObject呼び出しの結果として、ランタイムでは、リモートオブジェクトを求める着信リモート呼び出しを受け取るために、新規のサーバーソケット上で待機を開始するか、共有サーバーソケットを使うことができるようになります。返されたスタブには、リモートオブジェクトのクラスと同じリモートインタフェースのセットが実装され、リモートオブジェクトに連絡できるホスト名とポートが含まれています。注: J2SE 5.0 リリースでは、リモートオブジェクトが 5.0 VM より前を実行しているクライアントをサポートする必要がないかぎり、
rmicスタブコンパイラを使用してリモートオブジェクトのスタブクラスを事前に生成しておく必要がなくなりました。使用しているアプリケーションがこのようなクライアントをサポートする必要がある場合は、そのアプリケーション内で使用されるリモートオブジェクトのスタブクラスを生成し、クライアントがダウンロードするようにスタブクラスを配置する必要があります。スタブクラスを生成する方法の詳細については、rmicのツールドキュメント (Solaris 用、Windows 用) を参照してください。事前に生成したスタブクラス付きでアプリケーションを配置する方法の詳細については、Java RMI の使用による動的なコードのダウンロードに関するチュートリアルを参照してください。リモートオブジェクトを Java RMI レジストリに登録する
呼び出し側 (クライアント、ピア、またはアプレット) がリモートオブジェクトのメソッドを呼び出すために、呼び出し側は最初にリモートオブジェクトのスタブを取得する必要があります。ブートストラッピング用に、アプリケーションが名前をリモートオブジェクトのスタブにバインドし、クライアントがそのスタブを取得するためにリモートオブジェクトを名前で検索できるようにするために、Java RMI にはレジストリ API が用意されています。
Java RMI レジストリは単純なネームサービスであり、このサービスにより、クライアントはリモートオブジェクトへの参照 (スタブ) を取得することができます。通常、レジストリは、クライアントが使う必要がある最初のリモートオブジェクトの場所を特定するためにだけ使用されます。その後、通常はその最初のオブジェクトによって、その他のオブジェクトを検出するためのアプリケーション固有のサポートが提供されます。たとえば、参照は、別のリモートメソッド呼び出しに渡すパラメータ、またはリモートメソッド呼び出しからの戻り値として取得できます。詳細は、「Java RMI にファクトリパターンを適用する」を参照してください。
リモートオブジェクトがサーバーに登録されると、呼び出し側は、そのオブジェクトを名前で検索して、リモートオブジェクトへの参照を取得できます。 その後、そのオブジェクトのリモートメソッドを呼び出すことができます。
サーバー内の次のコードで、ローカルホストとデフォルトのレジストリポートのレジストリに対応するスタブを取得し、そのレジストリのスタブを使用して、「Hello」という名前をレジストリ内のリモートオブジェクトのスタブにバインドします。
引数をとらないRegistry registry = LocateRegistry.getRegistry(); registry.bind("Hello", stub);LocateRegistry.getRegistrystatic メソッドは、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.getRegistrystatic メソッドを呼び出すことで、レジストリに対応するスタブを最初に取得します。ホスト名が指定されていない場合は、ホスト名にnullが使用され、ローカルホストのアドレスを使用する必要があることを指示されます。次にクライアントは、サーバーのレジストリからリモートオブジェクトに対応するスタブを取得するために、レジストリスタブで
lookupリモートメソッドを呼び出します。最後にクライアントは、リモートオブジェクトのスタブで
sayHelloメソッドを呼び出し、この呼び出しによって次の操作が発生します。
- クライアント側のランタイムは、リモートオブジェクトのスタブ内のホストおよびポート情報を使用してサーバーへの接続を開き、呼び出しデータを直列化する
- サーバー側のランタイムは、着信する呼び出しを受け入れ、その呼び出しをリモートオブジェクトへディスパッチし、応答文字列の「Hello, world!」である結果をクライアントへ直列化する
- クライアント側のランタイムは、結果を受信し、直列化復元を行い、その結果を呼び出し側に返す
リモートオブジェクトのリモート呼び出しから返される応答メッセージは、その後
System.outに出力されます。
この例のソースファイルは、次のようにしてコンパイルできます。
destDir は、クラスファイルを配置するデスティネーションディレクトリです。javac -d destDir Hello.java Server.java Client.java注: 5.0 VM 以前で実行されるクライアントをサーバーがサポートする必要がある場合は、
rmicコンパイラを使用して事前にリモートオブジェクトの実装クラスに対応するスタブクラスを生成する必要があり、そのスタブクラスをクライアントがダウンロードできるようにする必要があります。詳細はJava RMI の使用による動的なコードのダウンロードに関するチュートリアルを参照してください。
この例を実行するには、次の操作を行う必要があります。
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.ServerclassDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」節の destDir を参照)。
java.rmi.server.codebaseシステムプロパティーを設定することで、レジストリがリモートインタフェース定義をロードできるようになります(末尾のスラッシュが重要であることに注意してください)。このプロパティーを使用する詳細については、Java RMI の使用による動的なコードのダウンロードに関するチュートリアルを参照してください。サーバーからの出力は、次のようになります。
Server readyユーザーによって終了させられる (通常はプロセスの強制終了) まで、サーバーは実行を続けます。
クライアントの実行
サーバーの準備ができると、次のようにしてクライアントを実行できます。
java -classpath classDir example.hello.ClientclassDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」節の destDir を参照)。
クライアントから出力されるのは、次のメッセージです。
response: Hello, world!
|
Copyright © 2006 Sun Microsystems, Inc. All Rights Reserved. コメントの送付先: rmi-comments@java.sun.com |
Java Software |