Java RMI(Remote Method Invocation)とは?

RMIとは、2つの別のマシン上で動作するJavaプログラムの一方のオブジェクトのメソッドを、他方のプログラムから呼び出す機能を実現するための仕組みです。この記事ではRMIの使い方をサンプルを交えてご紹介します。

RMIとは

RMIとは、2つの別のマシン上で動作するJavaプログラムの一方のオブジェクトのメソッドを他方のプログラムから呼び出す機能を実現するための仕組みです。通信部分のコードを作成することなくクライアント・サーバ機能を実現することができることがメリットです。他のマシン上のオブジェクト(リモート・オブジェクト)に対して次のことが可能です。

リモートオブジェクトを別のリモートオブジェクトのメソッドに渡すようなことも可能です。

RMIを使った開発手順

  1. リモートオブジェクトのインタフェースを定義する。

    クライアントからアクセス可能にするオブジェクトのインタフェースを定義する。

    public interface MyObject extends Remote {
      public String getMessage() throws java.rmi.RemoteException;
    }

    Remoteオブジェクトのサブクラスであることと、メソッドはすべてRemoteExceptionを発生することを宣言するのがポイントです。

  2. リモートオブジェクトクラスを作る。

    ここで定義したインタフェースに従ってリモートオブジェクトのクラスを作成する。

    public class MyObjectImpl extends java.rmi.server.UnicastRemoteObject {
    implements MyObject {
    
      public String getMessage() throws java.rmi.RemoteException {
        return "Hello world!";
      }
    
      public MyObjectImpl throws java.rmi.RemoteException {}
    }
  3. リモートオブジェクトをレジストリサーバに登録する。

    サーバプログラム(Server.java)を作成し、その中(たとえばmain()メソッド中)から次のような処理を実行する。

    public class Server {
      public static void main(String args[]) {
        try {
          if (System.getSecurityManager() == null) {
            System.setSecurityManager(new java.rmi.RMISecurityManager());
          }
          MyObjectImpl myobj = new MyObjectImpl();
          java.rmi.Naming.rebind("//localhost/MyObject", myobj);
          System.error.println("Request Object ready.");
        }
        catch (SecurityException e) {
          System.error.println("Failed to regist request.");
        }
        catch (Exception e) {
          System.error.println("Failed to regist request.");
          System.error.println(e);
        }
      }
    }
  4. クライアントからリモートオブジェクトを参照する

    クライアントプログラムを作成し、その中からリモートオブジェクトの登録名を引数にしてNaming.lookup()メソッドを呼ぶ。

    public class Client {
      public static void main(String args[]) {
        MyObject myobj = null;
    
        try {
          System.setSecurityManager(new java.rmi.RMISecurityManager());
          String name = "rmi://localhost/MyObject";
          myobj = (MyObject)java.rmi.Naming.lookup(name);
        }
        catch (Exception e) {
          System.error.println(e);
        }
        try {
          System.out.println(myobj.getMessage());
        }
        catch (Exception e) {
          System.error.println(e);
        }
      }
    }

    lookup()メソッドによって、リモートオブジェクトが変数myobjに返されるので、後は通常どおりmyobjのメソッドを利用して様々な処理を行えばよい。

  5. ソースコードをコンパイルする

    通常通りソースコードをコンパイルし、classファイルを作成する。

    javac *.java
  6. リモートオブジェクトをコンパイルする

    リモートオブジェクトをコンパイルするには rmic コマンドを使います。 rmic コマンドの引数にはリモートオブジェクトのクラス名を指定します。

    rmic MyObjectImpl

    この結果、MyObjectImpl_Stub.class(スタブクラス)とMyObjectImpl_Skel.class(スケルトンクラス)という2つのクラスファイルが生成される。

    スタブクラスとインタフェースクラス(MyObject.class)をWWWサーバー経由でアクセスできる場所にコピーする。

    スタブクラスとスケルトンクラスはサーバ側にコピーします。

    スタブクラスはリモートオブジェクトとまったく同じメソッドからできているクラスで、その中身は単にメソッド呼び出しをリモートオブジェクトに中継するだけの機能を果たします。

    スケルトンクラスはスタブクラスから渡ってきた引数をストリームから取り出し、それをサーバ側のリモートオブジェクトに中継する役割を果たします。

  7. クライアントクラスをクライアントマシンに移動する

    クライアントクラス (Client.class) をクライアントマシンに移動します。

  8. セキュリティポリシーを記述する

    次のような3種類のパーミッションをポリシーに記述しなければなりません。

    1. ServerがrmiregistryにSocketで通信すること
    2. ClientがrmiregistryにSocketで通信すること
    3. MyClientImpl_StubがサーバマシンにSocketで接続することができること

    これらをすべて満たすポリシーを記述するのは難しいし、実際どのXXXPermissionを記述すればよいのかも明確にされていません。

    Java 2 のポリシーファイル (java.policy) に対し、何でも許してしまうという記述のサンプルを以下に示します。

    grant {
      permission  java.security.AllPermission;
    }
  9. レジストリサーバを起動する

    サーバマシン上でレジストリサーバを起動する。Serverクラスを実行するマシンと同じマシンでなければならないことに注意。そのためには、まずカレントディレクトリからスタブクラスが参照できないことを確認する。

    java MyObjectImpl_Stub
    Exception in thread "main" java.lang.NoClassDefFoundError: MyObjectImpl_Stub

    MyObjectImpl_Stubが参照できないことを確認したら、Java 2の場合はrmiregistryコマンドを起動する。

    C:\> start rmiregistry

    ※Windows版の場合

  10. サーバプログラムを起動する

    Webサーバ経由でスタブクラスにアクセスできる場合:

    java -D java.rmi.server.codebase=http://localhost//xxx/xxx/Server

    Webサーバ経由でスタブクラスにアクセスできない場合:

    java -Djava.rmi.server.codebase=file:/C:/xxx/xxx/Server

    これにより、「MyObject」という名前でMyObjectオブジェクトがレジストリサーバ (rmiregistry) に登録される。

    このとき「-D」オプションによってスタブクラスを置いたディレクトリのURLを指定しなければならない。URLとしては「http:」または「file:」が使えます。「-D」オプションの最後のスラッシュは必ずつけます。

  11. クライアントプログラムを起動する
    $ java Client
    Hello world!

RMI複数のオブジェクトへのアクセス

RMIでは、サーバプログラムによって登録されたオブジェクトを複数のクライアントで共有することになります。したがって、例えば2つのクライアントが順番にリモートオブジェクトの変数を書き換える場合、2つ目のクライアントが実行された時点でその変数は1つ目のクライアントによってすでに書き変わっているということです。しかし、クライアントがアクセスするたびにリモートオブジェクトの新しいインスタンスを入手したい場合があります。こういう場合は「クライアント別のリモートオブジェクトをインスタンス化して返すリモートオブジェクト」を作ればよいのです。すなわち、次のようなリモートオブジェクトを作ります。

public class MyObject1Impl extends UnicastRemoteObject
implements MyObject1 {
  public MyObject2 getMyObject2() throws RemoteException {
    return new MyObject2Impl();
  }
}
public class MyObject2Impl extends UnicastRemoteObject
implements MyObject2 {
}

これを次のようなクライアントコードでアクセスします。

System.setSecurityManager(new RMISecurityManager());
String name = "rmi://localhost/MyObject1";
MyObject1 obj1 = (MyObject1)Naming.lookup(name);
MyObject2 obj2 = obj1.getMyObject2();

RMIのリモートオブジェクトを自動的にブートさせる方法

Windows 2000の場合はrmiregistryとサーバプログラムをサービスとして登録しなければならない。その方法には主に2種類あります。

  1. Win32のAPIを使って自身をサービスとして登録する。
  2. レジストリを編集して xxx.exe をサービスとして登録する。

前者はサービス起動時に呼び出される関数を登録したり、Windowsのサービスコントロールマネージャにプロセスの状態を通知したりすることができる。