Java™ RMI 入門

このチュートリアルでは、おなじみの Hello World プログラムの分散システム版を Java™ Remote Method Invocation (Java RMI) を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。Java RMI FAQ および River プロジェクトの外部にあるメーリングリストおよびドキュメントの保存には、さまざまな疑問に対する回答があります。

ここで例として紹介する分散型の Hello World プログラムでは、単純なクライアントを使用して、リモートホスト上で稼働しているサーバーへのリモートメソッド呼び出しを行います。クライアントでは、サーバーから「Hello, world!」メッセージを受信します。

このチュートリアルでは、次の手順を実行します。

このチュートリアルの実行に必要なファイルは、次のとおりです。 注:以降のチュートリアルで、「リモートオブジェクトの実装」および「実装クラス」と言った場合はすべて、リモートインタフェースを実装するクラス 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 レジストリに登録する

呼び出し側 (クライアント、ピア、またはアプレット) がリモートオブジェクトのメソッドを呼び出すために、呼び出し側は最初にリモートオブジェクトのスタブを取得する必要があります。ブートストラッピング用に、アプリケーションが名前をリモートオブジェクトのスタブにバインドし、クライアントがそのスタブを取得するためにリモートオブジェクトを名前で検索できるようにするために、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.java
destDir は、クラスファイルを配置するデスティネーションディレクトリです。

注:5.0 VM 以前で実行されるクライアントをサーバーがサポートする必要がある場合は、rmic コンパイラを使用して事前にリモートオブジェクトの実装クラスに対応するスタブクラスを生成する必要があり、そのスタブクラスをクライアントがダウンロードできるようにする必要があります。詳細は「Java RMI の使用による動的なコードのダウンロード」に関するチュートリアルを参照してください。

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.Server

classDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」セクションの destDir を参照)。java.rmi.server.codebase システムプロパティーを設定することで、レジストリがリモートインタフェース定義をロードできるようになります (末尾のスラッシュが重要であることに注意してください)。このプロパティーを使用する詳細については、「Java RMI の使用による動的なコードのダウンロード」に関するチュートリアルを参照してください。

サーバーからの出力は、次のようになります。

Server ready

ユーザーによって終了させられる (通常はプロセスの強制終了) まで、サーバーは実行を続けます。

クライアントの実行

サーバーの準備ができると、次のようにしてクライアントを実行できます。

java  -classpath classDir example.hello.Client

classDir は、クラスファイルツリーのルートディレクトリです (「ソースファイルをコンパイルする」セクションの destDir を参照)。

クライアントから出力されるのは、次のメッセージです。

response: Hello, world!


Copyright © 1993, 2013, Oracle and/or its affiliates. All rights reserved.