S-JIS[2008-12-11/2009-12-18] 変更履歴

RMI(Remote Method Invocation)

RMIは、リモート(サーバー)上のインスタンスのメソッドを呼び出す手段。
RMIレジストリー又はJNDIにインスタンスを登録しておき、クライアントからそのメソッドを呼び出すことが出来る。
呼び出されたメソッドは、そのインスタンスを登録したJavaVM上で実行される。
コーディング上は、クライアントからは単なるメソッド呼び出しに見えるが、実際は通信が発生している。


RMIレジストリーを使うサンプル

Eclipse3.4・JDK1.6でサンプルを作ってみる。

RMIではサーバーとクライアントで別マシンになってよいので、つまりJavaVMやクラスパスが完全に独立となる。
したがって、Eclipse上では、登録用とクライアント用と共通用の3つのプロジェクトに分けてソースを書くことにする。

インターフェース(RmiSample)登録側クライアント側で使うので、rmi_serverとrmi_clientからrmi_interfaceを参照できるように設定する。

  1. メニューバーの「プロジェクト(P)」→「プロパティー(P)」で(rmi_server/rmi_clientの)プロパティーダイアログを開く。
  2. 左ツリーで「Javaのビルド・パス」を選択する。
  3. 右ペインで「プロジェクト(P)」タブを選択する。
  4. 「追加」ボタンを押して、rmi_interfaceプロジェクトを追加する。

RmiSample(リモートインターフェース)

まず、インターフェースを作る。
リモートインターフェースは、RMIレジストリーへの登録側クライアント側で使用する。

rmi_interface/RmiSample.java:

package jp.hishidama.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface RmiSample extends Remote {

	public String getMessage() throws RemoteException;
}

これは通常のインターフェースだが、java.rmi.Remoteを継承する必要がある。

また、呼び出す各メソッドでは、「throws RemoteException」を宣言する。
RemoteExceptionのスローを宣言していない場合、以下のような例外が発生する。[2009-11-14]

Exception in thread "main" java.rmi.server.ExportException:
		remote object implements illegal remote interface; nested exception is:
		java.lang.IllegalArgumentException: illegal remote method encountered:
		public abstract java.lang.String jp.hishidama.rmi.RmiSample.getMessage()
	at sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:181)
	at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:293)
	at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:235)
	at java.rmi.server.UnicastRemoteObject.<init>(UnicastRemoteObject.java:133)
	at java.rmi.server.UnicastRemoteObject.<init>(UnicastRemoteObject.java:119)
	at jp.hishidama.rmi.RmiSampleObject.<init>(RmiSampleObject.java:16)
	at jp.hishidama.rmi.server.RmiServer.object(RmiServer.java:46)
	at jp.hishidama.rmi.server.RmiServer.main(RmiServer.java:21)
Caused by: java.lang.IllegalArgumentException:
		illegal remote method encountered:
		public abstract java.lang.String jp.hishidama.rmi.RmiSample.getMessage()
	at sun.rmi.server.Util.checkMethod(Util.java:244)
	at sun.rmi.server.Util.getRemoteInterfaces(Util.java:223)
	at sun.rmi.server.Util.getRemoteInterfaces(Util.java:193)
	at sun.rmi.server.Util.createProxy(Util.java:126)
	at sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:179)
	... 7 more

RmiSampleImpl(インターフェースの実装)

サーバー(登録)側で、呼び出されるクラスを作成(実装)する。

rmi_server/RmiSampleImpl.java:

package jp.hishidama.rmi;

import java.io.Serializable;
import java.rmi.RemoteException;

public class RmiSampleImpl implements RmiSample, Serializable {

	private static final long serialVersionUID = 960582942208877131L;

	@Override
	public String getMessage() throws RemoteException {
		return "RmiSampleImpl#message";
	}
}

インターフェースであるRmiSampleをimplementsする。
また、Serializableもimplementsする必要がある。

UnicastRemoteObjectを継承する実装クラス

※実装したメソッドがRemoteExceptionを発生させないなら、throwsでRemoteExceptionを宣言する必要は無い。[2009-11-14]
(→オーバーライド時のthrowsの省略

	@Override
	public String getMessage() /* throws RemoteException ←不要 */ {
		return "RmiSampleImpl#message";
	}

RmiServer

RMIレジストリーへの登録(bind)を実施するクラスのサンプル。

rmi_server/RmiServer.java:

package jp.hishidama.rmi.server;

import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

import jp.hishidama.rmi.RmiSample;
import jp.hishidama.rmi.RmiSampleImpl;

public class RmiServer {

	public static void main(String[] args) throws Exception {
		Remote r = createObject();
		regist(r);

		//自分で無限ループする必要は無い
	}

	static Remote createObject() throws RemoteException {

		RmiSample rs = new RmiSampleImpl();
		Remote r = UnicastRemoteObject.exportObject(rs, 0);
		return r;
	}

	static void regist(Remote r) throws RemoteException {
		Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
		registry.rebind("RmiSampleName", r);
	}
}

UnicastRemoteObject#exportObject()には、オブジェクトだけを引数に渡すオーバーロード(匿名ポートが使われる)もある。
しかしJavadocを見ると、portに0を渡すと匿名ポートが使われる気がするので、第2引数が無いメソッドと同じ動きをしそうな気がするが、実際には動作が異なる。

LocateRegistry#createRegistry()で、RMIレジストリーを作成(起動)する。
Registry.REGISTRY_PORTは、RMIレジストリーのデフォルトのTCPポート番号(1099)。
createすることで、TCP受付が始まる。
(TCP受付をやめるには、作成したRMIレジストリーインスタンスをアンエクスポートする)[2009-12-13]

このプログラムを実行すると、RMIレジストリーへのオブジェクトの登録後、無限ループに入る。
(エクスポート(exportObject()呼び出し)したオブジェクトは(RMIレジストリーに登録されている為、ではなく)管理テーブルで保持され、監視用スレッドが動く為。[/2009-12-13]
終了させる為には、Eclipseのコンソールの「終了」ボタンを押す。
※サンプルの終了のさせ方としてはこれで充分だが、正しい終了の作法はunexportObject()を呼び出すこと。[2009-12-13]

createRegistry()でなく別プロセスでrmiregistryを起動して使う例
リモートオブジェクトの実装クラスがUnicastRemoteObjectを継承している場合のインスタンス化の方法


RmiClient

RMIレジストリーからリモートオブジェクト(へのプロキシー)を取得し、メソッド呼び出しするサンプル。

rmi_client/RmiClient.java:

package jp.hishidama.rmi.client;

import java.net.MalformedURLException;
import java.rmi.*;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

import jp.hishidama.rmi.RmiSample;

public class RmiClient {

	public static void main(String[] args) throws Exception {

		RmiSample rs = lookup();
		System.out.println(rs.getMessage());
	}

	static RmiSample lookup() throws MalformedURLException, RemoteException, NotBoundException {
		Remote r = Naming.lookup("RmiSampleName");
		return (RmiSample) r;
	}
}

java.rmi.Naming#lookup()で、RMIレジストリー(デフォルトでポート1099)からリモートオブジェクトのインターフェースを取得する。

もしくはRegistryを使って取得する。(というか、Namingの内部はRegistryを使っている模様)

	static RmiSample lookup() throws RemoteException, NotBoundException {
		Registry registry = LocateRegistry.getRegistry(Registry.REGISTRY_PORT);
		Remote r = registry.lookup("RmiSampleName");
		return (RmiSample) r;
	}

RmiServerの実行中にRmiClientを実行すると、リモートオブジェクトのメソッドを呼び出すことが出来る。


rmiregistry

JDKには、RMIレジストリー(リモートオブジェクトの登録・取得)の機能を実行するプログラム(rmiregistry)が用意されている。
rmiregistryを使う場合は、オブジェクト登録の際に(createRegistryでなく)java.rmi.Naming#bind()を呼ぶ。

rmi_server/RmiServer.java:

	static void regist(Remote r) throws RemoteException {
		Naming.rebind("RmiSampleName", r);
	}

(WindowsXPの場合)コマンドプロンプトでrmiregistry.exeを起動してから、RmiServerを実行する。

コマンドプロンプト:

> rmiregistry
> rmiregistry 1099		…ポート番号を指定する場合

(JDK1.6をインストールしていればC:\Program Files\Java\jdk1.6.〜\binにパスが通っているので、 他に何も指定しなくても実行できる)

しかし、この状態でRmiServerをそのまま実行すると、以下のような例外が発生する。

Exception in thread "main" java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
		java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
		java.lang.ClassNotFoundException: jp.hishidama.rmi.RmiSample
	at sun.rmi.server.UnicastServerRef.oldDispatch(UnicastServerRef.java:396)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:250)
	at sun.rmi.transport.Transport$1.run(Transport.java:159)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:885)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
	at java.lang.Thread.run(Thread.java:619)
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:255)
	at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:233)
	at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:359)
	at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
	at java.rmi.Naming.rebind(Naming.java:160)

RemoteException occurred in server thread( サーバースレッドでRemoteExceptionが発生した)」というメッセージだが、「サーバースレッド」というのはrmiregistryの事を指しているっぽい。

リモートオブジェクトを登録(bind)する為には、rmiregistryに対して「使用するリモートインターフェースの存在する場所(クラスパス /codebase)」を教えてやる必要がある。[/2009-11-14]


コードベース(codebase)の指定方法

rmiregistryにクラスパスを指定せず、)リモートオブジェクトを登録(bind)する側の実行時にVM引数「java.rmi.server.codebase」を指定すると、bind側でリモートオブジェクトを登録できる。[2009-11-14]
codebaseには、リモートインターフェースの存在しているクラスパスを指定する。

Eclipseでは、「実行の構成」でcodebaseを指定する。[2008-12-11]

  1. メニューバーの「実行(R)」→「実行構成(N)」で「実行構成」ダイアログを開く。
  2. 左ペインから“RmiServer”の実行構成を選択する(あるいは作成する)。
  3. 右ペインで「引数」タブを選択する。
  4. 「VM引数(G)」に「-Djava.rmi.server.codebase=file:${workspace_loc}/rmi_interface/classes/」を記述する。
    rmi_interfaceプロジェクトのコンパイル結果がclassesディレクトリーに入っている想定。
     ${workspace_loc}は、実行時にEclipseのワークスペースのパス(「C:\workspace」等)に自動的に変わってくれる)

codebaseに複数の場所を指定するには、値部分をダブルクォーテーションで囲み、スペース区切りで場所を指定する。[2009-11-07]
(異なるディレクトリーに置いてあるリモートインターフェースを両方とも使いたい場合は、複数の場所を指定する必要がある)

-Djava.rmi.server.codebase="file:${workspace_loc}/rmi_interface1/classes/ file:${workspace_loc}/rmi_interface2/classes/"

rmiregistryのクラスパスの指定方法

codebaseを使用せず、)rmiregistryに「使用するリモートインターフェースのクラスパス 」を指定すると、bind側でリモートオブジェクトを登録できる。[2009-11-14]

rmiregistryコマンド自身はjavaコマンドでsun.rmi.registry.RegistryImplクラスを実行しているだけのようなので、実体はJavaプログラム。
したがって、環境変数CLASSPATHにクラスパスを指定しておけば、それが使われる。

> set CLASSPATH=C:\workspace\rmi_interface\classes
> rmiregistry
> rmiregistry 1099		…ポート番号を指定する場合

ただ、環境変数CLASSPATHは他のライブラリーのパスが指定されている可能性もあるので、なるべく使いたくない。
「-J」でjavaコマンド向けの引数を渡すことが出来る(javacコマンドの-Jと同様)ので、それを使うのが良さそう。

> rmiregistry -J-cp -JC:\workspace\rmi_interface\classes
> rmiregistry -J-cp -JC:\workspace\rmi_interface\classes 1099	…ポート番号を指定する場合

rmiregistryのデバッグ方法

rmiregistryもJavaアプリケーションなので、Eclipseからデバッグ接続することが出来る。[2009-12-18]
以下のようにしてrmiregistryを起動する。

> rmiregistry -J-agentlib:jdwp=transport=dt_socket,address=10990,server=y,suspend=n 1099

ただ、rmiregistryはJREのjarファイルを使っているらしく、デバッグ情報が入っていない。したがって ろくにデバッグできない(苦笑)

rmiregistryはLocateRegistryを使ってRegistryを作っているだけ(のようなもの)なので、自分でそのプログラムを作ってデバッグ実行した方がやりやすそう。

	public void main(String[] args) throws RemoteException {
		if (System.getSecurityManager() == null) {
			System.setSecurityManager(new RMISecurityManager());
		}

		LocateRegistry.createRegistry(Registry.REGISTRY_PORT);

		for (;;) ;	//無限ループしておく必要がある
	}

ServerSocket#accept()の末尾辺り(return直前)にブレークポイントを置いておけば、クライアントからbind()やlookup()された時にそこで止まるかどうかで、ソケット接続が成功しているかどうか位は確認できる。


RmiSampleObject(インターフェースの実装)

RmiSampleImplRmiSampleを実装しているだけで、RMIレジストリーへの登録前にそのインスタンスをUnicastRemoteObjectを使ってエクスポートしている。
リモートインターフェースの実装クラスがUnicastRemoteObjectを継承していれば、エクスポートの必要は無い。

rmi_server/RmiSampleObject.java:

package jp.hishidama.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RmiSampleObject extends UnicastRemoteObject implements RmiSample {

	private static final long serialVersionUID = 5537153581398009494L;

	public RmiSampleObject() throws RemoteException {
		super(); // UnicastRemoteObjectのコンストラクター(super())は「throws RemoteException」が宣言されている。
	}

	@Override
	public String getMessage() throws RemoteException {
		return "RmiSampleObject#getMessage";
	}
}

rmi_server/RmiServer.java:

	static Remote createObject() throws RemoteException {

		RmiSample rs = new RmiSampleObject();
		return rs;
	}

UnicastRemoteObject.unexportObject()

エクスポートしたRMIオブジェクトは、unexportObject()を呼び出して明示的に破棄する必要がある。[2009-12-13]

エクスポートの方法は2種類あって、ひとつはUnicastRemoteObject.exportObject()を呼び出す方法。
もうひとつはUnicastRemoteObjectを継承したクラスをインスタンス化する方法。
後者は、コンストラクター内部でexportObject(this)を実行しているので、実質は同じ。

  明示的にexportObject()する方法 UnicastRemoteObjectを継承する方法
インスタンス化
RmiSample rs = new RmiSampleImpl();
Remote r = UnicastRemoteObject.exportObject(rs, 0);
RmiSample rs = new RmiSampleObject();
 
バインド
Naming.bind("RmiSampleName", r);
Naming.bind("RmiSampleName", rs);
アンバインド
Naming.unbind("RmiSampleName");
破棄
UnicastRemoteObject.unexportObject(r, true);
UnicastRemoteObject.unexportObject(rs, true);

RMIオブジェクトをエクスポートすると、ObjectTableというクラスでRMIオブジェクトのインスタンスが保持される。
なので、自分でインスタンス化した際の変数(参照)は保持しておかなくてもRMIとしては動作する。
RMIサーバーのサンプルで、main()は無限ループせず終わっているのにプログラムが終了しないのは、スレッド(RMI Reaper)が作られて動いている為)
しかし逆にアンエクスポートしないままだと、ずっと保持されたままになる。
なのでプログラムを終了させるためにはVMのプロセスをkillする(コンソールを閉じる)しかないが、これは良い作法とは言えない。
(現に、Tomcat上で動くウェブアプリでRMIのバインドを行ってunexportObject()しないでいると、Tomcatが正常にシャットダウンできない

実のところ、バインドとアンエクスポートは無関係なようだ。
つまり、バインドしていようがいまいが、エクスポートすると、アンエクスポートするまで正常に終了できない。
また、バインドしていてもアンエクスポートしてしまうと、クライアントから接続しようとした時にjava.rmi.NoSuchObjectExceptionとなる。
クライアントからのルックアップが終わっていれば、アンバインドしても(アンエクスポートしていなければ)メソッド呼び出し(通信)は可能。
(通信そのものはRMIオブジェクトが行っていて、RMIレジストリーは“どういうオブジェクトが公開されているか”の目次・橋渡し機能だけなのかな?)

ちなみに、RMIオブジェクトでfinalize()をオーバーライドして、その中でunexportObject(this)をしても、無意味。
結局unexportObject()を呼ぶまで解放されないから、それまでにファイナライザーが呼ばれることは無い。


LocateRegistry.createRegistry()によって返されるRegistryの実体は、RegistryImpl。[2009-12-13]
実はこのクラスもRemoteObjectRemoteServer)を継承したリモートクラスなので、ObjectTableクラスで保持されている。
ただしこのリモートオブジェクトは不変?永住?オブジェクト(permanent=true)として管理されているので、unexportObject()しなくても、アプリの終了には影響ない。

RegistryImplインスタンスが作られた時点で、TCPの受付待ち(listen)が始まる。
RegistryImplインスタンスの参照を自分で捨ててしまっても、RMI Reaperが保持しているので問題なく通信できる。
逆にアプリ全体を終了させずにRMIレジストリー機能だけを終了させたい場合は、RegistryImplインスタンスをアンエクスポートすればよい。

	Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
〜
	UnicastRemoteObject.unexportObject(registry, true);

ちょっとだけJDK1.6の「RMI Reaperスレッド」の調査結果をメモしておく。[2009-12-13]

RMI Reaperは、リモートオブジェクトの強参照が無くなったら管理テーブルから削除するスレッド。(弱参照のキューであるReferenceQueueが使われている)
スレッドダンプを見ると「RMI Reaper」という名前になっているので、RMI Reaperスレッドと呼んでいる。
RMI Reaperは非デーモンスレッドなので、このスレッドが終わらないとJavaアプリは終了しない。(デーモンでないスレッドが全て終わると、Javaアプリ全体の実行が終了するらしい)

リモートオブジェクトの管理(インスタンスの保持)はsun.rmi.transport.ObjectTableクラスで行われている。
Eclipseのデバッグ機能の監視式を用いて「sun.rmi.transport.ObjectTable.objTable」あるいは「sun.rmi.transport.ObjectTable.implTable」を見れば、保持されているリモートオブジェクトが分かる。

RMI Reaperが終了しないことの調査であれば、permanent=falseとなっているものがあるかどうか探す。
objTableやimplTableはMapで、値はTargetとかいうクラスとなっている。(permanentというのはTargetのフィールド)
Target#weakImplが弱参照オブジェクトで、その中のreferentが保持しているリモートオブジェクトと思われる。

RMI Reaperスレッドは、permanent=falseのリモートオブジェクトが初めてエクスポートされた時に開始され、
unexportObject()されてpermanent=falseのリモートオブジェクトが0個になったら終了する。


Ant:rmic

他の人のRMIの説明を見ると、「rmicによって特別なコンパイルをする必要がある」とある。[2008-12-01]
しかしJDK1.5以降では、rmicによるコンパイルは不要なようだ。

ちなみにAntrmicタスクによるコンパイルは以下の様にする。→最新rmic
(コアタスクorg.apache.tools.ant.taskdefs.Rmic extends MatchingTask)

build.xml:

	<target name="rmic">
		<rmic base="classes" classpath="${java.class.path}">
			<include name="**/RmiSampleImpl.class" />
		</rmic>
	</target>

rmicによるコンパイルは、javacによってコンパイルされたclassファイルに対して行う。
コンパイル対象はリモートオブジェクトの実装クラスのみでよい(というか、実装クラスだけでないとダメ)。

(なお、コンパイル対象クラスが標準のCLASSPATH以外のクラスも必要とする場合は、classpath属性にそのライブラリーも含める必要がある)

これにより、RmiSampleImpl_Skel.classRmiSampleImpl_Stub.classという、スケルトンスタブが作られる。
スケルトンがサーバー側(で実際のインスタンスをラップするクラス)で、スタブがクライアント側(でリモートへアクセスする為のラッパークラス)らしい。

debug属性をtrueにすると、デバッグ用の情報が付加される。
sourcebase="C:\temp"とすると、スタブとスケルトンのJavaソースがC:\temp(のパッケージディレクトリーの下)に作られる。


バインド名

RMIオブジェクトをbindおよびlookupする際の名前の指定方法について。[2009-11-14]

同じサーバー上であれば、名前だけ指定すればよい。
別サーバーからルックアップする時は、IPアドレス(ホスト名)を指定する必要がある。

  備考
名前 RmiSampleName  
bind Naming
Naming.rebind("RmiSampleName", r);
名前のみ指定
Naming.rebind("rmi://localhost/RmiSampleName", r);
RMI URL形式
Naming.rebind("rmi://localhost:1099/RmiSampleName", r);
RMI URL形式でポート番号も指定
Registry
Registry registry = LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
registry.rebind("RmiSampleName", r);
createRegistry()を使う場合、rmiregistryは不要。
Registry.REGISTRY_PORTは1099
(RMIレジストリーのデフォルトポート番号)。
RMIレジストリー機能の終了方法
JNDI
Context ctx = new InitialContext();
ctx.rebind("rmi:RmiSampleName", r);
JNDIのオブジェクト登録先として、rmiregistryが必要。

デフォルトのJNDIコンテキストを使う場合、
RMI URL形式でバインドする必要がある。
Context ctx = new InitialContext();
ctx.rebind("rmi:/RmiSampleName", r);
Context ctx = new InitialContext();
ctx.rebind("rmi://localhost/RmiSampleName", r);
Context ctx = new InitialContext();
ctx.rebind("rmi://localhost:1099/RmiSampleName", r);
Hashtable<Object, Object> env = new Hashtable<Object, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
 "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
Context ctx = new InitialContext(env);
ctx.rebind("RmiSampleName", r);
RMIレジストリーサービスプロバイダーを使う場合、
バインド時は名前だけでいいらしい。
lookup Naming
r = (RmiSample)Naming.lookup("RmiSampleName");
ローカルホストからルックアップ
r = (RmiSample)Naming.lookup("rmi://localhost/RmiSampleName");
RMI URL形式
r = (RmiSample)Naming.lookup("rmi://localhost:1099/RmiSampleName");
RMI URL形式でポート番号も指定
Registry
Registry registry = LocateRegistry.getRegistry();
r = (RmiSample)registry.lookup("RmiSampleName");
ローカルホストのデフォルトポートからルックアップ
(ちなみにRegistryの実体はRegistryImpl_Stub)
Registry registry = LocateRegistry.getRegistry(Registry.REGISTRY_PORT);
r = (RmiSample)registry.lookup("RmiSampleName");
ローカルホストの指定ポートからルックアップ
Registry registry = LocateRegistry.getRegistry("localhost");
r = (RmiSample)registry.lookup("RmiSampleName");
ホスト名(IPアドレス)指定
Registry registry = LocateRegistry.getRegistry("localhost", 1099);
r = (RmiSample)registry.lookup("RmiSampleName");
ホスト名(IPアドレス)とポート番号を指定
JNDI
Context ctx = new InitialContext();
r = (RmiSample)ctx.lookup("rmi:RmiSampleName");
 
Context ctx = new InitialContext();
r = (RmiSample)ctx.lookup("rmi:/RmiSampleName");
Context ctx = new InitialContext();
r = (RmiSample)ctx.lookup("rmi://localhost/RmiSampleName");
Context ctx = new InitialContext();
r = (RmiSample)ctx.lookup("rmi://localhost:1099/RmiSampleName");
Hashtable<Object, Object> env = new Hashtable<Object, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
 "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
Context ctx = new InitialContext(env);
r = (RmiSample)ctx.lookup("RmiSampleName");
RMIレジストリーサービスプロバイダーを使う場合、
ルックアップ時も名前だけでいいらしい。

bindは自分のマシンに対してしか行えないらしい(RMIの仕様)。
したがって、createRegistry()ではポート番号しか指定できない(ホスト名やIPアドレスは指定できない)。
RMI URL形式で他サーバーのIPアドレスを指定しても、そのサーバーに対してbindできない。

一方、lookupはRMIサーバーに対して行うものなので、接続先のIPアドレスやポート番号を指定できる。


リモートオブジェクトのスコープ

RMIレジストリーに登録しているオブジェクトに値をセットすると、別サーバーからもその値を取得できる。[2009-11-14]

  bind lookup1 lookup2
 
public class RmiDataImpl
  extends UnicastRemoteObject
  implements RmiData {

  protected String msg;

  /** コンストラクター */
  public RmiDataImpl() throws RemoteException {
  }

  @Override
  public String getMessage() {
    return msg;
  }

  @Override
  public void setMessage(String s) {
    msg = s;
  }
}
public interface RmiData extends Remote {










  public String getMessage() throws RemoteException;




  public void setMessage(String s) throws RemoteException;


}
1 RmiDataImpl data = new RmiDataImpl();
data.setMessage("msg0");
bind(data);
msg0        
2     RmiData data = lookup();
String s = data.getMessage();
msg0 RmiData data = lookup();
String s = data.getMessage();
msg0
3     data.setMessage("msg1"); msg1    
4 String s = data.getMessage(); msg1     String s = data.getMessage(); msg1


しかし、さらに別オブジェクトを内包している場合、そのデータまでは伝播しない。

  bind lookup1 lookup2
 
public class Sub implements Serializable {
  protected String msg;

  public String getMessage() {
    return msg;
  }

  public void setMessage(String s) {
    msg = s;
  }
}
public class RmiDataImpl
  extends UnicastRemoteObject
  implements RmiData, Serializable {

  protected Sub sub;

  /** コンストラクター */
  public RmiDataImpl() throws RemoteException {
  }

  @Override
  public Sub getSub() {
    return sub;
  }

  public void setSub(Sub s) {
    sub = s;
  }
}
public interface RmiData extends Remote {










  public Sub getSub() throws RemoteException;






}
1 Sub sub = new Sub();
sub.setMessage("msg0");

RmiDataImpl data = new RmiDataImpl();
data.setSub(sub);

bind(data);
msg0        
2     RmiData data = lookup();
Sub sub = data.getSub();
String s = sub.getMessage();
msg0 RmiData data = lookup();
Sub sub = data.getSub();
String s = sub.getMessage();
msg0
3     sub.setMessage("msg1"); msg1    
4 String s = sub.getMessage(); msg0     String s = sub.getMessage(); msg0

内包するデータもリモートオブジェクトにすれば、変更が伝播する。
(参考:JavaHouseのRMI: bind one Object to one Object

  bind lookup1 lookup2
 
public class SubImpl
  extends UnicastRemoteObject
  implements Sub {

  protected String msg;

  /** コンストラクター */
  public SubImpl() throws RemoteException {
  }

  @Override
  public String getMessage() {
    return msg;
  }

  @Override
  public void setMessage(String s) {
    msg = s;
  }
}
public class Sub extends Remote {










  public String getMessage() throws RemoteException;




  public void setMessage(String s) throws RemoteException;


}
public class RmiDataImpl
  extends UnicastRemoteObject
  implements RmiData {

  protected Sub sub;

  /** コンストラクター */
  public RmiDataImpl() throws RemoteException {
  }

  @Override
  public Sub getSub() {
    return sub;
  }

  public void setSub(Sub s) {
    sub = s;
  }
}
public interface RmiData extends Remote {










  public Sub getSub() throws RemoteException;






}
1 SubImpl sub = new SubImpl();
sub.setMessage("msg0");

RmiDataImpl data = new RmiDataImpl();
data.setSub(sub);

bind(data);
msg0        
2     RmiData data = lookup();
Sub sub = data.getSub();
String s = sub.getMessage();
msg0 RmiData data = lookup();
Sub sub = data.getSub();
String s = sub.getMessage();
msg0
3     sub.setMessage("msg1"); msg1    
4 String s = sub.getMessage(); msg1     String s = sub.getMessage(); msg1

例外の扱い

RMIの実装クラスのメソッド内で例外が発生した場合、呼び出した側にスローされる。[2009-11-16]
(bindしたVMでキャッチするわけではない)

  bind lookup1 lookup2
 
public class RmiSampleObject
  extends UnicastRemoteObject
  implements RmiSample {

  protected String msg;

  /** コンストラクター */
  public RmiDataImpl() throws RemoteException {
  }

  @Override
  public String getMessage() {
    return msg;
  }

  @Override
  public void setMessage(String s) {
    throw new RuntimeException("実験");
  }
}
public interface RmiSample extends Remote {










  public String getMessage() throws RemoteException;




  public void setMessage(String s) throws RemoteException;


}
1 RmiSampleOBject data = new RmiSampleOBject();
bind(data);
         
2 try {
  data.setMessage("msg0");
}catch(RuntimeException e) {
  e.printStackTrace();
}
キャッチ        
3     RmiSample data = lookup();   RmiSample data = lookup();  
4     try {
  data.setMessage("msg1");
}catch(RuntimeException e) {
  e.printStackTrace();
}
キャッチ    
 
java.lang.RuntimeException: 実験
	at jp.hishidama.rmi.RmiSampleObject.setMessage(RmiSampleObject.java:33)
	at jp.hishidama.rmi.server.RmiServer.main(RmiServer.java:42)
java.lang.RuntimeException: 実験
	at jp.hishidama.rmi.RmiSampleObject.setMessage(RmiSampleObject.java:33)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
	at sun.rmi.transport.Transport$1.run(Transport.java:159)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:255)
	at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:233)
	at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:142)
	at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:178)
	at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:132)
	at $Proxy0.setMessage(Unknown Source)
	at jp.hishidama.rmi.client.RmiClient.main(RmiClient.java:49)
   

JavaEEへ戻る / 新機能へ戻る / Java目次へ戻る
メールの送信先:ひしだま