初期のJavaにおけるRMIを利用した分散アプリケーションでは、通信処理を行うためのスタブ・スケルトンをrmicコマンドを用いて事前に生成しておく必要がありました。スタブはクライアント側に配置し、スケルトンはサーバ側に配置していました。RMIのバージョンが上がって(JRMP1.2)、スケルトンは生成する必要がなくなりましたが、スタブは必要でした。J2SE 1.5からは、スタブの生成も不要になり、実質的には通常のJavaプログラムを作る手順だけでRMIプログラムを作ることができるようになりました。
遠隔から呼び出すインタフェース(通称リモート・インタフェース)を定義します。java.rmi.Remoteを継承することと、メソッドは必ずjava.rmi.RemoteExceptionをスローするように定義します。また、メソッドの引数・戻り値に使用する型は、シリアライズ可能でなくてはなりません。
import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { String sayHello() throws RemoteException; }
遠隔から呼び出されたメソッドの実装を定義します。実装方法には幾種類かがありますが、最もよく使われるものは、java.rmi.server.UnicastRemoteObjectクラスを継承する方法です。
import java.rmi.server.UnicastRemoteObject; import java.rmi.Naming; import java.rmi.RMISecurityManager; import java.rmi.RemoteException; public class HelloImpl extends UnicastRemoteObject implements Hello { /** * 注記:スーパークラスのUnicastRemoteObjectのコンストラクタにて * RemoteExceptionがスローされるので、本コンストラクタにおいて * これをcatchするかthrowするかが必要である。本実装においては、 * throwを選択している。 */ public HelloImpl() throws RemoteException { } public String sayHello() { return "Hello, RMI World!"; } public static void main(String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { Hello hello = new HelloImpl(); Naming.rebind("HelloObject", hello); } catch (Exception e) { e.printStackTrace(); } } }
import java.rmi.RMISecurityManager; import java.rmi.Naming; public class HelloClient { public static void main(String[] args) { Hello hello; try { System.setSecurityManager(new RMISecurityManager()); hello = (Hello)Naming.lookup("rmi://localhost/HelloObject"); System.out.println(hello.sayHello()); } catch (Exception e) { e.printStackTrace(); } } }
実験用なので、セキュリティを全て許可にしています。
grant { permission java.security.AllPermission; };
RMIプログラムを作成し、コンパイル・実行する際の注意点として、サーバ側とクライアント側を同じディレクトリやクラスパスで参照できる場所に置かないことがあります。これは、ローカルマシンでサーバとクライアントを実行するときは、実はクラスファイルをネットワークからではなくローカルディスクからクラスパスで参照してしまう問題を避けるためです。また、rmiregistryもクラスパス上にクラスファイルを見つけると、指定されたコードベースを無視してローカルのクラスパスを使用してしまいます。
そこで、クライアントとサーバ双方が参照するリモートインタフェースを共通のディレクトリに置き、それ以外のクライアント固有、サーバ固有のクラスは個別に置きます。
client | HelloClient.java |
ifc | Hello.java |
java.policy | |
server | HelloImpl.java |
dev$ cd ifc ifc$ javac Hello.java ifc$ ls Hello.class Hello.java java.policy ifc$
dev$ cd server server$ javac -classpath .:../ifc HelloImpl.java server$ ls HelloImpl.class HelloImpl.java server$
dev$ cd client client$ javac -classpath .:../ifc HelloClient.java client$ ls HelloClient.class HelloClient.java client$
dev$ unset CLASSPATH dev$ rmiregistry
server$ java -cp .:../ifc \ -Djava.security.policy=../ifc/java.policy \ -Djava.rmi.server.codebase=file:///home/torutk/dev/ifc/ \ HelloImpl
client$ java -cp .:../ifc \ -Djava.security.policy=../ifc/java.policy HelloClient Hello, RMI World! client$
dev> set CLASSPATH= dev> rmiregistry
server> java -cp .;..\ifc \ -Djava.security.policy=..\ifc\java.policy \ -Djava.rmi.server.codebase=file:///C:/home/torutk/dev/ifc/ \ HelloImpl
client> java -cp .;..\ifc \ -Djava.security.policy=..\ifc\java.policy HelloClient Hello, RMI World! client>