RMIという言葉は、Remote Method Invocation (遠隔メソッド呼び出し)の略です。RMIは、あるJava Virtual Machine上のインスタンスが、別のJava Virtual Machine上のメソッドを呼び出す仕組みです。このとき、2つのJava Virtual Machineは、それぞれ別のホスト上にあっても構いません。2つのJava Virtual Machineは、クライアント・サーバモデルによって通信を行います。
RMIを使わない通常のJavaプログラムについて考えてみましょう。例えばHelloクラスのsayHelloメソッドを実行しようとすると、Helloのインスタンスを作成して、このインスタンスのsayHelloメソッドを実行します。
RMIでは、Helloクラスの実装はサーバ側にあります。クライアントでHelloクラスのインスタンスを作成してsayHelloメソッドを呼び出すと、サーバ上のsayHelloメソッドが実行され、返値がクライアントに返されます。
Helloクラスの実装はサーバ側にあるので、クライアントはサーバ側を呼び出すだけでよいのです。このときクライアントは、メソッドを実行するだけでサーバを呼び出せます。
RMIの構成図を図6.1.2.1[RMIの構成図]に示します。
サーバでは、クライアントから呼び出されるメソッドを実装します。クライアントでは、サーバを見つけだして、メソッドを実行します。
RMIでは、サーバとクライアントには通信に必要な具体的な処理は記述されていません。そうした処理は「スタブ」と「スケルトン」というクラスに記述されています。スタブはクライアント側で、スケルトンはサーバ側で処理を行います。RMIでは、直接にはスタブとスケルトンが通信を行うことになります。スタブとスケルトンは、サーバのプログラムからツールで生成されます。
rmiregistryは、サーバの情報を登録しておくための「ネームサーバ」です。クライアントはrmiregistryにアクセスしてサーバのプログラムを探します。
RMIの原型となった技術がRPC (Remote Procedure Call)です。RPCは、別のマシン上にある手続きを実行するためのしくみを提供します。
もともとはSun Microsystemsによって開発され、現在ではUnixやWindowsなどのOSで常用されています。
1つのRMIプログラムは、基本的には、3つの部分から構成されます。サーバ側のプログラムと、クライアント側のプログラム。そして、両者に共通するインタフェースです。
Remoteインタフェースでは、クライアントから呼び出せるメソッドを定義します。このインタフェースは、サーバとクライアントで共有されます。サーバは、このインタフェースを実装することになります。
import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { public String sayHello() throws RemoteException; }
このインタフェースは、java.rmi.Remoteを継承しています。また、それぞれのメソッドは、RemoteExceptionを発行できるようにしなければいけません。
次に、サーバ側のプログラムについて見てみましょう。
import java.rmi.Remote; import java.rmi.Naming; import java.rmi.RMISecurityManager; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.net.MalformedURLException; public class HelloImpl extends UnicastRemoteObject implements Hello { public HelloImpl() throws RemoteException { super(); } public String sayHello() throws RemoteException { return "Hello world!"; } public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { HelloImpl obj = new HelloImpl(); StringBuffer url = new StringBuffer(); url.append("rmi://"); url.append(args[0]); url.append("/HelloServer"); Naming.rebind(new String(url), obj); System.out.println("HelloServer bound in registry"); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
まずは、クラスの定義部分から解説しましょう。Helloインタフェースを実装します。そして、UnicastRemoteObjectを継承します。
public class HelloImpl extends UnicastRemoteObject implements Hello { ...... }
次に、コンストラクタとメソッドです。
public HelloImpl() throws RemoteException { super(); } public String sayHello() throws RemoteException { return "Hello world!"; }
コンストラクタとメソッドは、RemoteExceptionを発行するようにします。そのため、必ずコンストラクタを用意しなければいけません。そして、インタフェースで用意されたメソッドを実装する必要があります。
では最後に、mainメソッドを見てみましょう。
この部分は、クラスが許可されていない操作を行わないために必要になります。
if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); }
次の部分で、"rmi://localhost/HelloServer"といったスタイルのURIを生成します。
StringBuffer url = new StringBuffer(); url.append("rmi://"); url.append(args[0]); url.append("/HelloServer");
そして最後に、HelloImplのインスタンスをrmi://localhost/HelloServerという名前で、rmiregistryに登録しています。
Naming.rebind(new String(url), obj);
クライアント側のプログラムです。
import java.rmi.Naming; import java.rmi.RMISecurityManager; import java.rmi.RemoteException; import java.rmi.NotBoundException; import java.net.MalformedURLException; public class HelloClient { public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { StringBuffer url = new StringBuffer(); url.append("rmi://"); url.append(args[0]); url.append("/HelloServer"); Hello obj = (Hello)Naming.lookup(new String(url)); System.out.println(obj.sayHello()); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
まずは、"rmi://localhost/HelloServer"といったようなURIを生成します。
StringBuffer url = new StringBuffer(); url.append("rmi://"); url.append(args[0]); url.append("/HelloServer");
次に、rmiregistryから"rmi://localhost/HelloServer"という名前のオブジェクトを探し、Helloにキャストします。
Hello obj = (Hello)Naming.lookup(new String(url));
最後に、HelloオブジェクトのsayHelloメソッドを実行しています。
System.out.println(obj.sayHello());
まずは、いま紹介した3つのプログラムをコンパイルします。
javac Hello.java javac HelloImpl.java javac HelloClient.java
次のコマンドによって、スタブとスケルトンを生成します。
rmic HelloImpl
HelloImplは、コンパイルされたHelloImpl.classのことです。このコマンドによって、次の2つのクラスが生成されます。
また、rmicコマンドにkeepオプションをつけることによって、スタブとスケルトンのソースファイルを残すことができます。
rmic -keep HelloImpl
サーバ側とクライアント側で用意するプログラムは、それぞれ次のようになります。
rmiregistryを起動します。このとき、起動するフォルダにクラスファイルを一切置かないようにしてください。
start rmiregistry
次のコマンドでサーバを起動します。
java -Djava.rmi.server.codebase=file:///C:\Home\rmi\ -Djava.security.policy=policy.txt HelloImpl localhost (1行で入力する)
起動時にプロパティが指定されているので、見てみましょう。
まずは、この部分です。
java.rmi.server.codebase=file:///C:\Home\rmi\
codebaseプロパティでスタブの位置を指定しています。この例では、C:\Home\rmi\フォルダにスタブのクラスファイルを置いておきます。このとき、URLの最後に"\"をつけるのを忘れないようにしましょう。
codebaseには、Web上のURLを指定することもできます。例えばhttp://www.wakhok.ac.jp/~tomoharu/にcodebaseを指定した場合、このURL上にスタブを置いておきます。
クライアントが起動されると、codebaseで指定されたURLからスタブがダウンロードされて、クライアントプログラムからスタブを利用できるようになります。
次に、このプロパティです。
java.security.policy=policy.txt
policy.txtというファイルでアクセス権を指定できます。policy.txtの内容は次のようになっています。
grant { // Allow everything for now permission java.security.AllPermission; };
ここでは、すべてのユーザにすべてのアクセス権を与えています。
次のようにしてクライアントを起動します。サーバと同じように、ポリシーファイルを指定するようにしましょう。
java -Djava.security.policy=policy.txt HelloClient localhost
うまくいけば、サーバからのメッセージが出力されるはずです。
Cygwinを使うとうまく動きません(原因は不明)。そしてプログラムを起動するときには、クラスパスを設定してはいけません。クラスパスを設定していると、もしクラスパス中にスタブがあった場合、クライアントプログラムはそのスタブをロードするからです。