RMIは、リモート(サーバー)上のインスタンスのメソッドを呼び出す手段。
RMIレジストリー又はJNDIにインスタンスを登録しておき、クライアントからそのメソッドを呼び出すことが出来る。
呼び出されたメソッドは、そのインスタンスを登録したJavaVM上で実行される。
コーディング上は、クライアントからは単なるメソッド呼び出しに見えるが、実際は通信が発生している。
|
|
Eclipse3.4・JDK1.6でサンプルを作ってみる。
RMIではサーバーとクライアントで別マシンになってよいので、つまりJavaVMやクラスパスが完全に独立となる。
したがって、Eclipse上では、登録用とクライアント用と共通用の3つのプロジェクトに分けてソースを書くことにする。
インターフェース(RmiSample)は登録側とクライアント側で使うので、rmi_serverとrmi_clientからrmi_interfaceを参照できるように設定する。
まず、インターフェースを作る。
リモートインターフェースは、RMIレジストリーへの登録側とクライアント側で使用する。
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
サーバー(登録)側で、呼び出されるクラスを作成(実装)する。
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";
}
RMIレジストリーへの登録(bind)を実施するクラスのサンプル。
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を継承している場合のインスタンス化の方法
RMIレジストリーからリモートオブジェクト(へのプロキシー)を取得し、メソッド呼び出しするサンプル。
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を実行すると、リモートオブジェクトのメソッドを呼び出すことが出来る。
JDKには、RMIレジストリー(リモートオブジェクトの登録・取得)の機能を実行するプログラム(rmiregistry)が用意されている。
rmiregistryを使う場合は、オブジェクト登録の際に(createRegistryでなく)java.rmi.Naming#bind()を呼ぶ。
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]
(rmiregistryにクラスパスを指定せず、)リモートオブジェクトを登録(bind)する側の実行時にVM引数「java.rmi.server.codebase」を指定すると、bind側でリモートオブジェクトを登録できる。[2009-11-14]
codebaseには、リモートインターフェースの存在しているクラスパスを指定する。
Eclipseでは、「実行の構成」でcodebaseを指定する。[2008-12-11]
-Djava.rmi.server.codebase=file:${workspace_loc}/rmi_interface/classes/
」を記述する。codebaseに複数の場所を指定するには、値部分をダブルクォーテーションで囲み、スペース区切りで場所を指定する。[2009-11-07]
(異なるディレクトリーに置いてあるリモートインターフェースを両方とも使いたい場合は、複数の場所を指定する必要がある)
-Djava.rmi.server.codebase="file:${workspace_loc}/rmi_interface1/classes/ file:${workspace_loc}/rmi_interface2/classes/"
(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も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()された時にそこで止まるかどうかで、ソケット接続が成功しているかどうか位は確認できる。
RmiSampleImplはRmiSampleを実装しているだけで、RMIレジストリーへの登録前にそのインスタンスをUnicastRemoteObjectを使ってエクスポートしている。
リモートインターフェースの実装クラスがUnicastRemoteObjectを継承していれば、エクスポートの必要は無い。
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"; } }
static Remote createObject() throws RemoteException { RmiSample rs = new RmiSampleObject(); return rs; }
エクスポートした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]
実はこのクラスもRemoteObject(RemoteServer)を継承したリモートクラスなので、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個になったら終了する。
他の人のRMIの説明を見ると、「rmicによって特別なコンパイルをする必要がある」とある。[2008-12-01]
しかしJDK1.5以降では、rmicによるコンパイルは不要なようだ。
ちなみにAntのrmicタスクによるコンパイルは以下の様にする。→最新rmic
(コアタスクorg.apache.tools.ant.taskdefs.Rmic extends MatchingTask)
<target name="rmic"> <rmic base="classes" classpath="${java.class.path}"> <include name="**/RmiSampleImpl.class" /> </rmic> </target>
rmicによるコンパイルは、javacによってコンパイルされたclassファイルに対して行う。
コンパイル対象はリモートオブジェクトの実装クラスのみでよい(というか、実装クラスだけでないとダメ)。
(なお、コンパイル対象クラスが標準のCLASSPATH以外のクラスも必要とする場合は、classpath属性にそのライブラリーも含める必要がある)
これにより、RmiSampleImpl_Skel.class
とRmiSampleImpl_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(); |
msg0 |
||||
2 | RmiData data = lookup(); |
msg0 |
RmiData data = lookup(); |
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(); |
msg0 |
||||
2 | RmiData data = lookup(); |
msg0 |
RmiData data = lookup(); |
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(); |
msg0 |
||||
2 | RmiData data = lookup(); |
msg0 |
RmiData data = lookup(); |
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(); |
|||||
2 | try { |
キャッチ | ||||
3 | RmiSample data = lookup(); |
RmiSample data = lookup(); |
||||
4 | try { |
キャッチ | ||||
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) |