Java

RMI-IIOP プログラマーズガイド


Copyright © 1999 Sun Microsystems, Inc. 
Copyright © 1999 International Business Machines Corporation. All Rights Reserved. 

ドキュメントの目次

目次

はじめに

このドキュメントでは、Internet Inter-ORB Protocol (IIOP) を使用してリモートオブジェクトにアクセスすることができる、Java Remote Method Invocation (RMI) プログラムの作成方法について説明します。いくつかの制約に準拠すれば、RMI プログラムから CORBA オブジェクトにアクセスすることができます。RMI-IIOP によって、RMI を簡単に使用できるようになり、CORBA/IIOP 言語の相互運用性が実現されます。このドキュメントは、RMI プログラマが RMI-IIOP プログラムを作成したり、既存の RMI プログラムを RMI-IIOP に変換するときに使用することを前提としています。

背景情報

関連する情報については以下のサイトを参照してください。

RMI-IIOP について

RMI

RMI を使用して、Java プログラミング言語で分散プログラムを作成できます。RMI は簡単に使用することができ、Interface Definition Language (IDL) の知識は必要ありません。 また、Java の「Write Once, Run Anywhere」という利点を継承できます。クライアント、リモートインタフェース、およびサーバは、すべて Java で記述できます。RMI では、Java オブジェクトのリモート通信を行うときに、Java Remote Method Protocol (JRMP) が使用されます。RMI プログラム作成についての概要は、「RMI チュートリアル」の Web ページを参照してください。このドキュメントでは、簡単な「Hello World」RMI プログラムの作成について説明されています。

RMI には、ほかの言語との相互運用性はありません。 また、非標準の通信プロトコルを使用しているため、CORBA オブジェクトと通信することができません。

IIOP、CORBA、および Java IDL

IIOP は、CORBA の通信プロトコルです。CORBA のクライアントとサーバ間の通信回線を介してビットを送信する方式を定義します。CORBA は、Object Management Group (OMG) によって開発された、標準の分散オブジェクトアーキテクチャです。リモートオブジェクトとのインタフェースは、プラットフォームに依存しない Interface Definition Language (IDL) で記述されています。IDL から特定のプログラミング言語へのマッピングが実装されており、その言語は CORBA/IIOP に結合されています。

JDK の CORBA/IIOP 実装は、Java IDL と呼ばれています。idltojava コンパイラおよび Java IDL を使用して、Java プログラミング言語から CORBA オブジェクトの定義、実装、およびアクセスを行うことができます。

「Java IDL」 Web ページでは、CORBA/IIOP プログラミングについて Java を中心にわかりやすく説明されています。Java IDL プログラムの作成についての概要は、「入門: Hello World」Web ページを参照してください。

RMI-IIOP

以前は、分散プログラミングソリューションを作成する場合、Java プログラマは、RMI または CORBA/IIOP (Java IDL) から選択しなければなりませんでした。現在は、いくつかの制約に従うことによって、IIOP プロトコルを介して RMI オブジェクトおよび CORBA オブジェクト間で通信することができます。このソリューションは、RMI-IIOP と呼ばれます。RMI-IIOP は、RMI 方式の使いやすさと、CORBA の言語間の相互運用性がともに実現されています。

新しい rmic コンパイラ

RMI-IIOP ソフトウェアの新しい rmic コンパイラを使用して、IIOP のスタブと Tie を生成し、IDL を発行することができます。

新しい rmic のフラグを次に示します。

-iiop -- IIOP スタブ/Tie を生成する
-idl -- IDL を生成する
-noValueMethods -- IDL の valuetype 内にメソッドとコンストラクタが生成されないようにする
-always -- 既存のスタブ、Tie、または idl が入力クラスより新しいときでも再生成する。-iiop または -idl フラグ (あるいはその両方) が指定されている場合のみ有効
-factory -- ファクトリキーワードを使用する
-idlModule <fromJavaPackage<.class>> <toIDLModule> -- IDLEntity パッケージマッピングを指定する。例: -idlModule foo.bar my::real::idlmod
-idlFile <fromJavaPackage<.class>> <toIDLFile> -- IDLEntity ファイルマッピングを指定する。例: -idlFile test.pkg.X TEST16.idl
新しい rmic の動作では、出力ディレクトリ (-d オプション) を指定しない場合、以前のバージョンと動作が異なります。JDK では、-d オプションを指定しない場合、パッケージにかかわりなく、スタブおよび Tie ファイルは常に現在の作業用ディレクトリに書き込まれます。新しい rmic では、パッケージに対応する、カレントディレクトリのサブディレクトリにファイルが作成されます。

-iiop フラグ

rmic-iiop オプションを指定した場合は、スタブおよび Tie クラスが生成されます。スタブクラスは、リモートオブジェクトに対するローカルプロキシです。スタブクラスは、サーバに呼び出しを送信するときに、クライアントによって使用されます。各リモートインタフェースにはスタブクラスが必要です。スタブクラスによってリモートインタフェースが実装されます。クライアントのリモートオブジェクトへの参照は、実際にはスタブへの参照になります。Tie クラスは、サーバ側で入接続呼を処理し、その呼び出しを適切な実装クラスにディスパッチするときに使われます。各実装クラスには、Tie クラスが必要です。

スタブクラスは、abstract インタフェースにも生成されます。abstract インタフェースは、java.rmi.Remote は継承していませんが、そのメソッドから java.rmi.RemoteException または java.rmi.RemoteException のスーパークラスがスローされるインタフェースです。また、java.rmi.Remote を継承せず、メソッドが存在しないインタフェースも、abstract インタフェースです。

-idl フラグ

rmic-idl オプションを指定すると、指定されているクラスおよび参照されているすべてのクラスに対して OMG IDL が生成されます。
IDL を使用すると、オブジェクトに対して API を指定するときに、プログラミング言語に依存せずに、単に宣言することができます。
IDL は、メソッドおよびデータの仕様として使用します。CORBA バインディングを提供する任意の言語で、メソッドおよびデータの作成および呼び出しを行うことができます。これらの言語には、Java および C++ が含まれます。詳細は、「Java Language to IDL Mapping」(OMG) を参照してください。

注: 生成された IDL は、IDL に対する CORBA 2.3 拡張機能がサポートされている IDL コンパイラを使用しないとコンパイルできません。

-noValueMethods フラグ

-idl とともに -noValueMethods オプションを指定した場合は、IDL 生成の間に発行される valuetype に、メソッドおよび初期化子が取り込まれません。このフラグは、valuetype のオプションです。 valuetype が発行されない場合は、省略されます。

rmic についての詳細は、RMIC ツールのページ (Solaris 版/Windows 版) を参照してください。

新しい idlj コンパイラ

RMI-IIOP ソフトウェアには、新しい IDL-to-Java コンパイラが組み込まれています。このコンパイラでは、RMI-IIOP との相互運用に必要な新しい CORBA Objects By Value 機能がサポートされます。このコンパイラは、Java で記述されているため、あらゆるプラットフォームで実行できます。このコンパイラの使用法についての詳細は、IDL-to-Java コンパイラのユーザーズガイドを参照してください。

RMI プログラムから IIOP を使用する

RMI アプリケーションを RMI-IIOP に変換するときの、一般的な手順について説明します。
  1. ネームサービスに RMI レジストリを使用している場合は、CosNaming プラグインを使用して JNDI に切り替える必要があります。次の操作を行う必要があります。
    1. クライアントおよびサーバのコードに、次のコードを使用して JNDI 用の InitialContext を作成する必要があります。
    2.          import javax.naming.*;
               ...
               Context initialNamingContext = new InitialContext();
    3. RMI レジストリで lookup() および bind() が使用されている部分を、すべて JNDI の lookup() および bind() に変更します。たとえば、RMI サーバで次のコードを使用するのではなく、
    4.          import java.rmi.*;
               ...
               Naming.rebind("MyObject", myObj);
      次のコードを使用します。
               import javax.naming.*;
               ...
               initialNamingContext.rebind("MyObject", myObj);
    5. クライアントがアプレットの場合は、this を JNDI の CosNaming プラグインに渡す必要があります。上記のコードを次のコードに置き換えます。
    6.         import java.util.*;
              import javax.naming.*;
              ...
              Hashtable env = new Hashtable();
              env.put("java.naming.applet", this);
              Context ic = new InitialContext(env);
  2. ネームサービスに RMI レジストリを使用していない場合は、 初期リモートオブジェクト参照のブートストラップの方法を変更することができます。たとえば、サーバのコードでは、Java 直列化を使用して RMI オブジェクト参照を ObjectOutputStream に書き込みます。 そのオブジェクト参照をクライアントのコードに渡して、RMI スタブに直列化復元を行うことができます。
  3. サーバ側では、PortableRemoteObject.toStub() 呼び出しを使用してスタブを取得し、writeObject() を使用してそのスタブを ObjectOutputStream に直列化します。次のコードによって、これらの処理が行われます。

    org.omg.CORBA.ORB myORB = org.omg.CORBA.ORB.init(new String[0], null);
    Wombat myWombat = new WombatImpl();
    javax.rmi.CORBA.Stub myStub = (javax.rmi.CORBA.Stub)PortableRemoteObject.toStub(myWombat);
    myStub.connect(myORB);
    // myWombat is now connected to myORB.  To connect other objects to the
    // same ORB, use PortableRemoteObject.connect(nextWombat, myWombat);
    FileOutputStream myFile = new FileOutputStream("t.tmp");
    ObjectOutputStream myStream = new ObjectOutputStream(myFile);
    myStream.writeObject(myStub);
    クライアント側では、readObject() を使用して ObjectInputStream から、リモート参照の直列化復元をオブジェクトに行います。 次のコードが使用されます。
    FileInputStream myFile = new FileInputStream("t.tmp");
    ObjectInputStream myStream = new ObjectInputStream(myFile);
    Wombat myWombat = (Wombat)myStream.readObject();
    org.omg.CORBA.ORB myORB = org.omg.CORBA.ORB.init(new String[0], null);
    ((javax.rmi.CORBA.Stub)myWombat).connect(myORB);
    // myWombat is now connected to myORB.  To connect other objects to the
    // same ORB, use PortableRemoteObject.connect(nextWombat, myWombat);
  4. リモート実装クラスを javax.rmi.PortableRemoteObject から継承するように変更するか、PortableRemoteObject.exportObject() を呼び出して実装クラスを作成してから実装オブジェクトを明示的にエクスポートします。

  5.  
  6. コード内で、リモートインタフェースの Java キャストが使用されている個所を、すべて javax.rmi.PortableRemoteObject.narrow() を使用するように変更します。

  7.  
  8. 使用済みのオブジェクトをアンエクスポートするときは、分散ガベージコレクションに依存したり、RMI DGC 機能を使用したりせずに、PortableRemoteObject.unexportObject() を使用してください。このとき、1.1.6 で JRMP にエクスポートされたオブジェクトには影響しません。

  9.  
  10. -iiop オプションを指定して rmic コマンドを実行し、RMI スタブおよび Tie を再生成します。スタブおよび Tie ファイルが次の名前で作成されます。
  11.      _<implementionName>_Tie.class
         _<interfaceName>_Stub.class
  12. サーバを起動する前に、次のコマンドを使用して CosNaming サーバを自身のプロセス内で起動します。
  13.      tnameserv
    デフォルトのポート番号 900 が使用されます。別のポート番号 (ポート 1050 など) を使用する場合は、ORBInitialPort 修飾子を使用します。
         tnameserv -ORBInitialPort 1050
  14. クライアントおよびサーバのアプリケーションを起動するときは、次のシステムプロパティを指定します。
  15.      java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
              -Djava.naming.provider.url=iiop://<hostname>:900
               <appl_class>
    この例では、デフォルトのネームサービスのポート番号 900 が使用されています。ステップ 7 で別のポート番号を指定した場合は、プロバイダの URL に、同じポート番号を使用する必要があります。プロバイダの URL の <hostname> は、ステップ 7 で CosNaming サーバを起動したときに使用したホスト名です。

  16. クライアントがアプレットの場合は、アプレットタグに次のプロパティを指定します。
  17.      java.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
         java.naming.provider.url=iiop://<hostname>:900
    この例では、デフォルトのネームサービスのポート番号 900 が使用されています。ステップ 7 で別のポート番号を指定した場合は、プロバイダの URL に、同じポート番号を使用する必要があります。プロバイダの URL の <hostname> は、ステップ 7 で CosNaming サーバを起動したときに使用したホスト名です。

IIOP を介して RMI プログラムを実行するときの制約

IIOP を介して既存の RMI プログラムを実行するときは、次の制約に従う必要があります。
  1. リモートインタフェース内のすべての定数の定義は、基本データ型または文字列でなければなりません。 また、コンパイル時に評価する必要があります。
  2. Java-to-IDL マッピング規則によって生成された、IDL の復号化名と競合する Java の名前を使用しないでください。Java-to-IDL の名前のマッピング規則については、「Java Language to IDL Mapping」の 28.3.2 節を参照してください。
  3. 複数の基底リモートインタフェースから、同じメソッド名をリモートインタフェースに 2 回以上継承しないでください。
  4. 名前を使用するときは、大文字小文字の区別に注意してください。型の名前およびその型の変数を使用するときは、大文字小文字は区別されません。ただし、ほとんどの名前では、大文字小文字は区別されます。
  5. IIOP を介してオブジェクト参照を転送するときに、オブジェクト参照の実行時共有に依存すると、正しく保持されないことがあります。ほかのオブジェクトの実行時共有は、正しく保持されます。
  6. 次の RMI 機能は使用しないでください。

RMI Hello World プログラムを RMI-IIOP に変換する

次の例では、RMI での Hello World の例を RMI-IIOP に変換します。また、RMI の Hello World アプレットクライアントを、アプリケーションに変換します。

RMI の Hello World は、次の要素で構成されています。

RMI の Hello World の例では、$HOME/jdk1.1/mysrc/example/hello の開発ディレクトリおよび $HOME/public_html/codebase の配置ディレクトリが使用されます。 $HOME は、ユーザのホームディレクトリです。ディレクトリは変更してもかまいませんが、次の例ではこれらのディレクトリを使用することを前提としています。

RMI の Hello World の例を使用したことがない場合は、使用してみることをお勧めします。例を使用したあとで、次のステップを実行します。

実装クラス (サーバ) を RMI-IIOP に適合させる:

  1. javax.rmi.server.UnicastRemoteObject ではなく javax.rmi.server.PortableRemoteObject をインポートします。
  2.      //Goodbye
         //import java.rmi.server.UnicastRemoteObject;
    
         //Hello
         import javax.rmi.PortableRemoteObject;
  3. JNDI ネーミングパッケージをインポートします。
  4.      import javax.naming.*;
  5. HelloImpl の継承元を、UnicastRemoteObject から PortableRemoteObject に変更します。
  6.      public class HelloImpl
                      extends PortableRemoteObject
                      ...
  7. 次のコードを追加して、RMI レジストリから JNDI レジストリに変更します。
  8.      Context initialNamingContext = new InitialContext();
    初期 JNDI ネーミングコンテキストが提供されます。 このステップは、クライアント側でも実行する必要があります。
     
  9. rebind() を RMI 版から JNDI 版に変更します。
  10. 変更前のコード:

         HelloImpl obj = new HelloImpl("HelloServer");
         Naming.rebind("HelloServer", obj);
    変更後のコード:
         HelloImpl obj = new HelloImpl("HelloServer"); //unchanged
         initialNamingContext.rebind("HelloServer",obj);
HelloApplet.java に対して必要な変更を次に示します。
  1. PortableRemoteObject パッケージをインポートします。
  2.      import javax.rmi.PortableRemoteObject;
  3. JNDI 初期ネーミングコンテキストを作成し、CosNaming プラグインに渡します。
  4.      import java.util.*;
         import javax.naming.*;
         ...
         Hashtable env = new Hashtable();
         env.put("java.naming.corba.applet", this);
         //The next two values will be specifiable
         //By applet tag params in future releases
         env.put("java.naming.factory.initial",
                 "com.sun.jndi.cosnaming.CNCtxFactory");
         env.put("java.naming.provider.url", "iiop://<hostname>:900");
         Context ic = new InitialContext(env);
  5. lookup() を RMI 版から JNDI 版に変更し、Java リモートインタフェースのキャストを javax.rmi.PortableRemoteObject.narrow() の呼び出しに置き換えます。
  6. 変更前のコード:

         import java.rmi.*;
         ...
         Hello obj = (Hello)Naming.lookup("//" +
                         getCodeBase().getHost() + "/HelloServer");
    変更後のコード:
         import javax.naming.*;
         ...
         Hello obj =
          (Hello)PortableRemoteObject.narrow(
                             initialNamingContext.lookup("HelloServer"),
                             Hello.class);
アプレットタグのネーミングプロパティの指定

次のプロパティを Hello.html アプレットタグに追加します。

<param name="java.naming.factory.initial" value="com.sun.jndi.cosnaming.CNCtxFactory">
<param name="java.naming.provider.url" value="iiop://<hostname>:900">

Java ソースファイルのコンパイル

javac -d $HOME/public_html/codebase Hello.java HelloImpl.java HelloApplet.java
スタブおよび Tie クラスの生成

検索パスの $RMI_IIOP_HOME/bin ディレクトリに rmic コマンドが指定されていることを確認します。

rmic -iiop -d $HOME/public_html/codebase examples.hello.HelloImpl
Hello_Stub.class (クライアント側のプロキシ) および HelloImpl_Tie.class (サーバ側のプロキシ) ファイルが、$HOME/public_html/codebase/examples/hello ディレクトリに生成されます。

JNDI ネームサーバの起動

tnameserv
JNDI ネームサーバが、デフォルトのポート番号 900 を使用して起動されます。別のポート番号 (Solaris のユーザは、1024 よりも大きいポートを使用すること) を使用する場合は、次のようなコマンド行を使用します。
tnameserv -ORBInitialPort 1050

Hello サーバの起動

java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
     -Djava.naming.provider.url=iiop://<hostname>:900
     examples.hello.HelloImpl

Hello クライアントの起動

appletviewer を使用して Hello.html をロードします。

appletviewer Hello.html
Hello.html の内容は、次のとおりです。
<html>
<title>Hello World</title>
<center> <h1>Hello World</h1> </center>

HelloServer からのメッセージ:
<p>
<applet
        code="examples.hello.HelloApplet"
        width=500 height=120>

</applet>
</HTML>
これらの処理が正常終了した場合は、appletviewer によって HelloServer のメッセージが表示されます。

クライアントアプレットをアプリケーションに変換する

アプレットクライアントをアプリケーションクライアントに変更する方法を次に示します。

クライアントアプリケーションを RMI-IIOP に適合させる:

  1. HelloApplet をアプリケーションに変換します。
  2. RMI レジストリを JNDI レジストリに変更します。
  3.      import javax.naming.*;
         ...
         Context initialNamingContext = new InitialContext();
  4. lookup() を RMI 版から JNDI 版に変更し、Java リモートインタフェースのキャストを javax.rmi.PortableRemoteObject.narrow() の呼び出しに置き換えます。
  5. 変更前のコード:

         import java.rmi.*;
         ...
         Hello obj = (Hello)Naming.lookup("//" +
                         getCodeBase().getHost() + "/HelloServer");
    変更後のコード:
         import javax.naming.*;
         ...
         Hello obj = (Hello)PortableRemoteObject.narrow(
                             initialNamingContext.lookup("HelloServer"),
                             Hello.class);
    サーバの起動時に、ホストおよびポートが指定されます。
HelloApp ソースのコンパイル
javac -d $HOME/public_html/codebase HelloApp.java
スタブおよび Tie を再生成する必要はありません。

ネームサーバおよび Hello サーバの起動

アプレットの例と同じ方法で起動します。

Hello アプリケーションのクライアントの起動

次のコードで起動します。

java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
     -Djava.naming.provider.url=iiop://<hostname>:900
      examples.hello.HelloApp
サーバのメッセージが、クライアントのコンソールに表示されます。

その他の注意事項

サーバはスレッドに対して安全でなければならない

同一のリモートオブジェクトに対するリモートメソッド呼び出しは、並列に実行されることがあるため、リモートオブジェクトの実装は、その実装がスレッドに対して安全であることを確認する必要があります。

同一のベクトルキーを持つハッシュテーブル

同一のベクトルキーを持つハッシュテーブルが、IIOP または JRMP を介して、JDK 1.1 RMI のアプリケーションから Java 2 プラットフォームベースの RMI アプリケーションに渡されると、Java 2 プラットフォームの直列化規則によって、同一のキーは 1 つのキーに「統合」されます。例を示します。
  1. JDK 1.1 RMI アプリケーションによって、ハッシュテーブルが作成されます。
  2. アプリケーションによって、ベクトルキー A を使用して値がハッシュテーブルに格納されます。
  3. アプリケーションによって、ベクトルキー B を使用して別の値がハッシュテーブルに格納されます。ベクトル B とベクトル A の構造は同じですが、これらは異なるオブジェクトです。
  4. ハッシュテーブルに対して、キー A およびキー B の 2 つのエントリが追加されました。
  5. JDK 1.1 RMI アプリケーションによって、ハッシュテーブルの各値が 1.2 RMI アプリケーションに送信されます。
  6. 1.2 の側のハッシュテーブルには、キー B のエントリが 1 つ作成されます。これは、ハッシュテーブルの直列化復元コードが 1.2 の規則を使用してハッシュテーブルを生成するときに、規則によってオブジェクト識別情報ではなく値とベクトルキーが比較されるためです。
  7. RMI を使用してこのハッシュテーブルを 1.2 から 1.1 に戻した場合も、ハッシュテーブルにはキー B のエントリだけが格納されています。

ほかの ORB との相互運用

RMI-IIOP は、CORBA 2.3 仕様がサポートされるほかの ORBS と相互運用することができます。 以前の ORB では、Objects By Value に対する IIOP エンコーディングがサポートされていないので、相互運用できませんでした。このサポートは、IIOP を介して RMI 値のクラス (文字列を含む) を送信するときに必要です。現在、CORBA 2.3 ORB の製品版はまだリリースされていませんが、間もなくリリースされる予定です。

既知の問題点


RMI-IIOP FCS リリース - バージョン 1.00 -
コメントの送付先: rmi-iiop@sun.com