Master -
Yucchi
Since - 2012/05/05
次に main() メソッドでどのような処理が行われているか調べてみます。RMI アプリケーションの研究
完成した RMI アプリケーションを研究してみましょう。
リモートインタフェース Hello.java
/* * Hello.java * * Created on 2004/10/25, 16:23 */ package HelloWorld; import java.rmi.Remote; import java.rmi.RemoteException; /** Remote interface. * * @author Yucchi * @version 1.0 */ public interface Hello extends Remote { /** sayHello method usually returns "Hello". */ public String sayHello() throws RemoteException; }リモートインタフェースは、public として宣言しなければならない。リモートインタフェースは、java.rmi.Remote インタフェースを継承する。
各メソッドは、アプリケーション固有の例外のほかに、throws 節内で java.rmi.RemoteException または (RemoteException のスーパークラス)を宣言する必要がある。
引数か戻り値として (直接またはローカルオブジェクトに埋め込まれて) 渡されるリモートオブジェクトのデータ型は、実装クラスではなく、リモートインタフェースの型として宣言する必要がある。
以上のような特性があります。
よって、Hello インタフェースのコードは上記のようになります。
次に実装クラスを見てみましょう。
リモートオブジェクトの実装クラスは少なくとも次の条件を備えていなければなりません
- 少なくとも1つのリモートインタフェースの実装を宣言している。
- リモートオブジェクトのコンストラクタを定義する。
- リモートから呼び出せるメソッドの実装を提供している。
- セキュリティマネージャを作成およびインストール
- リモートオブジェクトの1つ以上のインスタンスを生成
- ブートストラッピングを行うために、1つ以上のリモートオブジェクトを RMI リモートオブジェクトのレジストリに登録
実装クラス HelloImpl.java
/* * HelloImpl.java * * Created on 2004/10/25, 16:45 */ package HelloWorld; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RMISecurityManager; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; /** Unicast remote object implementing remote interface. * * @author Yucchi * @version 1.0 */ public class HelloImpl extends UnicastRemoteObject implements HelloWorld.Hello { /** Constructs HelloImpl object and exports it on default port. */ public HelloImpl() throws RemoteException { super(); } /** Constructs HelloImpl object and exports it on specified port. * @param port The port for exporting */ public HelloImpl(int port) throws RemoteException { super(port); } /** Register HelloImpl object with the RMI registry. * @param name - name identifying the service in the RMI registry * @param create - create local registry if necessary * @throw RemoteException if cannot be exported or bound to RMI registry * @throw MalformedURLException if name cannot be used to construct a valid URL * @throw IllegalArgumentException if null passed as name */ public static void registerToRegistry(String name, Remote obj, boolean create) throws RemoteException, MalformedURLException{ if (name == null) throw new IllegalArgumentException("registration name can not be null"); try { Naming.rebind(name, obj); } catch (RemoteException ex){ if (create) { Registry r = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); r.rebind(name, obj); } else throw ex; } } /** Main method. */ public static void main(String[] args) { System.setSecurityManager(new RMISecurityManager()); try { HelloImpl obj = new HelloImpl(); registerToRegistry("HelloServer", obj, true); } catch (RemoteException ex) { ex.printStackTrace(); } catch (MalformedURLException ex) { ex.printStackTrace(); } } /** sayHello method usually returns "Hello". * */ public String sayHello() throws RemoteException { return "はろー! From: HelloServer"; } }
リモートインタフェースの実装の宣言
public class HelloImpl extends UnicastRemoteObject implements HelloWorld.Hello {となります。
実装クラスは、リモートクラスを継承できます。
この場合、リモートクラスは、java.rmi.server.UnicastRemoteObject です。
UnicastRemoteObject を継承することにより、HelloImpl クラスから以下の機能を持つリモートオブジェクトを作成できます。
- 通信に RMI のデフォルトソケットをベースとするトランスポートを使用する。
- 常時稼動している
リモートオブジェクトのコンストラクタを定義する
public HelloImpl() throws RemoteException {
super();
}
super() メソッド呼び出しは、リモートオブジェクトをエクスポートしています。
デフォルトで super() メソッドの呼び出しは発生しますが、Java Studio では明示的に呼び出しをコーディングするようです。
あと、通信リソースが利用できない場合は、RMI がリモートオブジェクトを構築中にエクスポートしようとすると失敗する可能性があるためコンストラクタは java.rmi.RemoteException をスローする必要があります。
リモートから呼び出せるメソッドの実装を提供
public String sayHello() throws RemoteException { return "はろー! From: HelloServer"; }
リモートメソッドに渡す引数、またはリモートメソッドからの戻り値は、Java プラットフォーム用のどのデータ型であってもよい。デフォルトでは、ローカルオブジェクトはコピーによってわたされる。
つまり、static または transient とマークされたもの意外は、オブジェクトのすべてのデータメンバ(またはフィールド)がコピーされます。
セキュリティマネージャを作成およびインストール
サーバの main() メソッドは、まず、RMISecurityManager または独自に定義したセキュリティマネージャーの、作成およびインストールを行う必要があります。System.setSecurityManager(new RMISecurityManager());
リモートオブジェクトの1つ以上のインスタンスを生成
サーバの main() メソッドでは、サービスを提供するリモートオブジェクトの実装のインスタンスを、1つ以上生成する必要があります。HelloImpl obj = new HelloImpl();
ブートストラッピングを行うために、1つ以上のリモートオブジェクトを RMI リモートオブジェクトのレジストリに登録
クライアントがリモートオブジェクトのメソッドを呼び出すには、クライアントはまずリモートオブジェクトの参照を取得する必要があります。ブートストラップ用に、RMI システムはリモートオブジェクトのレジストリを提供します。
これにより、URL形式の名前をリモートオブジェクトにバインドできます。
RMI レジストリは、単純なサーバ側のネームサービスで、これによりリモートクライアントは、リモートオブジェクトへの参照を取得します。
通常、RMI レジストリは、RMI クライアントが最初に通信するリモートオブジェクトの場所を特定するためだけに使われます。
次に、RMI クライアントが最初に通信したオブジェクトが、ほかのオブジェクトを見つける上で必要なアプリケーション固有のサポートをおこないます。
リモートオブジェクトがサーバに登録されると、クライアントはその名前によって検索してリモートオブジェクトへの参照を取得します。
そうすることによって、クライアントはそのオブジェクトのメソッドをリモートから呼び出すことが可能になります。
registerToRegistry("HelloServer", obj, true);
上記のコードで呼ばれる下記のコード内にある、 Naming.rebind(name, obj);によって、HelloServer という名前をそのリモートオブジェクトへの参照にバインドします。
public static void registerToRegistry(String name, Remote obj, boolean create) throws RemoteException, MalformedURLException{ if (name == null) throw new IllegalArgumentException("registration name can not be null"); try { Naming.rebind(name, obj); } catch (RemoteException ex){ if (create) { Registry r = LocateRegistry.createRegistry(Registry.REGISTRY_PORT); r.rebind(name, obj); } else throw ex; } }
セキュリティ上の理由により、アプリケーションがバインド、アンバインドできるのは、同一ホスト上で動作中のレジストリに対してだけです。
この制限により、クライアントがサーバのリモートレジストリを削除したり上書きしたりすることはありません。
ただし、ルックアップはどのホストからでも可能です。
クライアント
このクライアントアプリケーションは、sayHello() メソッドを呼び出すだけの簡単なものです。クライアントアプリケーションは最初にサーバホストの rmiregistry からリモートオブジェクトの実装(HelloServer として公示されている)への参照を取得する。
Naming.lookup() メソッドは、Naming.rebind() メソッド同様に、URL形式の java.lang.String をとる。
Naming.lookup() メソッドは、引数として提供されたホスト名、ポート番号を使って、レジストリスタブのインスタンスを構築し、サーバのレジストリに接続する。
レジストリスタブから、URLの名前要素(HelloServer)を使ってレジストリ上のリモート lookup() メソッドを呼びだす。
Naming.lookup() メソッドは、呼び出し側(HelloClient)にスタブを返す。
クライアントはサーバのリモートオブジェクトのリモートメソッド sayHello() を呼び出す。
RMI は、応答文字列 はろー! From: HelloServer を直列化して返す。
RMI は、直列化された文字列を復元して標準出力に出力する。
/* * HelloClient.java * * Created on 2004/10/25, 18:12 */ package HelloWorld; import java.rmi.Naming; import java.rmi.RMISecurityManager; /** * * @author Yucchi * @version 1.0 */ public class HelloClient extends Object { /** Creates new HelloClient */ public HelloClient() { } /** * @param args the command line arguments */ public static void main(String args[]) { Hello obj = null; try { System.setSecurityManager(new RMISecurityManager()); obj = (Hello)Naming.lookup("//localhost/HelloServer"); System.out.println(obj.sayHello()); } catch (Exception e) { e.printStackTrace(); } } }
セキュリティポリシーファイル
今回は全ての権限を与えるセキュリティポリシーファイルを作成しました。grant { permission java.security.AllPermission; };
コンパイル
サーバ側のコンパイルは rmic という専用のコンパイラを使用します。rmic コンパイラによって .class ファイルおよび、スタブ、スケルトンが作成されます。
コマンドプロンプトを起動し
rmic HelloImpl
生成されたクラスファイルを必要に応じて配備します。
スケルトンはサーバ側に、スタブはクライアント側に配備します。
スケルトンは Java 1.2 以降のバージョンでは省略可能です。
クライアント側のコンパイルはデフォルトのコンパイラでコンパイルします。
javac HelloClient
それぞれ生成されたクラスファイルとセキュリティポリシーファイルを配備します。
rmi レジストリの起動
コマンドプロンプトを起動し
rmiregistryこれで rmi レジストリが起動します。
サーバの起動
コマンドプロンプトを起動しjava -Djava.security.policy=HelloServer.policy -Djava.rmi.server.codebase=file///F:\Sun\yucchi_RMI\RMI001\HelloWorld\Helloworld.HelloImpl上記のように実行時オプションを設定し、実行します。
* 実際には改行しないで1行で実行します。
上段はセキュリティポリシー読み込むためpolicy ファイルの位置を指定してます。
下段はサーバの起動時に、スタブクラスがレジストリ、次いでクライアントに動的にダウンロードされるように指定されています。
クライアントの実行
コマンドプロンプトを起動しjava -Djava.security.policy=HelloServer.policy HelloClient
上記のようにセキュリティポリシーを設定し、実行します。
これでコマンドプロンプト画面に はろー! From: HelloServer と表示されます。
これでRMI アプリケーションの基礎を勉強しました。あくまで入門用なので部分的障害、拡張性、マルチスレッド対応などのことは考慮されていません。