Java RMI入門

このチュートリアルでは、おなじみのHello Worldプログラムの分散システム版をJava Remote Method Invocation (Java RMI)を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。その回答は、Java RMI FAQで見つかることがあります。

ここで例として紹介する分散型の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呼出しの結果として、ランタイムでは、リモート・オブジェクトを求める着信リモート呼出しを受け取るために、新規のサーバー・ソケット上で待機を開始するか、共有サーバー・ソケットを使うことができるようになります。返されたスタブには、リモート・オブジェクトのクラスと同じリモート・インタフェースのセットが実装され、リモート・オブジェクトに連絡できるホスト名とポートが含まれています。

注意: スタブ・クラスを生成する方法の詳細は、rmicのツール・ドキュメント(UNIX用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.java
destDirは、クラス・ファイルを配置するデスティネーション・ディレクトリです。

注意: 事前に生成したスタブ・クラス付きでアプリケーションを配置する方法の詳細は、コードベースのチュートリアルを参照してください。

Java RMIレジストリ、サーバー、およびクライアントの起動

この例を実行するには、次の操作を行う必要があります。

Java RMIレジストリを起動する

レジストリを起動するには、サーバーのホストで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!


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