このチュートリアルでは、おなじみの「Hello World」プログラムの分散システム版を、Java TM の RMI (Remote Method Invocation、リモートメソッド呼び出し) を Internet Inter-ORB Protocol (IIOP) 経由で使用して作成する手順を説明します。 RMI-IIOP は、Java RMI に CORBA (Common Object Request Broker Architecture) 機能を追加することにより、標準規格に基づいた相互運用性と接続性を、ほかの多くのプログラミング言語およびプラットフォームに提供します。 RMI-IIOP を利用すると、Web ベースの分散 Java アプリケーションから、Object Management Group によって定義された業界標準である IIOP を使用しているリモートネットワークサービス上の操作を、透過的に呼び出すことができます。 ランタイムコンポーネントには、IIOP 通信を使った分散コンピューティング用の Java ORB が含まれています。
RMI-IIOP は、IIOP を背後のトランスポートとして使用して、RMI インタフェースに対するプログラムを作成したい Java プログラマ向けです。 RMI-IIOP はさまざまな言語で実装される CORBA オブジェクトとの相互運用性を提供しますが、リモートインタフェースをあらかじめ Java RMI インタフェースとして定義しておく必要があります。 EJB のリモートオブジェクトモデルは RMI ベースなので、Enterprise JavaBeans (EJB) を使うプログラマには特に有用です。
分散アプリケーションを作成するためのその他のオプションは次のとおりです。
Java IDL は、CORBA インタフェース定義言語 (IDL) で定義されたインタフェースに基づいて Java プログラミング言語でプログラムを記述したい CORBA プログラマ向けです。 これは「通常どおりの」CORBA プログラミングで、C++ や COBOL のような他の言語とまったく同じ方法で Java をサポートしています。
Java RMI システムを使って、ある Java 仮想マシン (VM) で実行中のオブジェクトから別の Java VM で実行中のオブジェクトのメソッドを呼び出すことができます。 RMI は、Java リモートメソッドプロトコル (JRMP) を介して、Java プログラミング言語で書かれたプログラム間のリモート通信を実現します。
ここで例として紹介する分散型の「Hello World」プログラムでは、クライアントアプリケーションから IIOP 経由でサーバ (そのクライアントアプリケーションのダウンロード元のホスト上で稼動している) に対してリモートメソッド呼び出しを行います。 このクライアントアプリケーションを実行すると、「Hello from MARS!」が表示されます。
このチュートリアルの構成は、次のとおりです。
チュートリアルに含まれる各手順は、このイラストで表されます。
ここで行う作業は 4 つあります。
このチュートリアルで作成するソースファイルは、次のとおりです。HelloInterface.java - リモートインタフェース
HelloImpl.java - HelloInterface を実装するリモートオブジェクトの実装
HelloServer.java - リモートオブジェクト実装のインスタンスを作成し、そのインスタンスをネームサービスの名前にバインドする、RMI サーバ
HelloClient.java - リモートメソッド sayHello() を呼び出すクライアントアプリケーション
Remote インタフェースを実装するクラスのインスタンスです。 作成するリモートインタフェースでは、ほかのマシンから呼び出したい各メソッドを宣言します。 リモートインタフェースには、次の特性があります。
public として宣言する必要がある。 そうしないと、クライアントがリモートインタフェースと同じパッケージ内にある場合を除いて、リモートインタフェースを実装しているリモートオブジェクトをクライアントがロードしようとした時点でエラーが発生する
java.rmi.Remote インタフェースを継承する
throws 節内で java.rmi.RemoteException (または RemoteException のスーパークラス) を宣言する必要がある
HelloImpl) ではなく、リモートインタフェースの型 (たとえば、HelloInterface) として宣言する必要がある
この例では、すべてのソースファイルを同じディレクトリ (たとえば、$HOME/mysrc/helloWorld) 内に作成します。
HelloInterface.java ファイルを作成します。 次のコードは、リモートインタフェース HelloInterface のインタフェース定義です。この定義には、ただひとつのメソッド sayHello が含まれています。
//HelloInterface.java
import java.rmi.Remote;
public interface HelloInterface extends java.rmi.Remote {
public void sayHello( String from ) throws java.rmi.RemoteException;
}
リモートメソッド呼び出しは、ローカルメソッド呼び出しとは異なる方法でエラーが発生します。 これは、ネットワーク通信上の問題とサーバ上の問題によるものです。 このため、リモートメソッドは、java.rmi.RemoteException をスローすることにより通信エラーを報告します。分散システム上のエラーおよび復元の詳細については、「A Note on Distributed Computing」を参照してください。
リモートオブジェクトの実装クラス HelloImpl.java は、少なくとも次の条件を満たしていなければなりません。
HelloImpl.java ファイルを作成します。 このファイルのコードは以下のとおりです。 ソースコードを紹介したあと、上記の各ステップについて説明します。
//HelloImpl.java
import javax.rmi.PortableRemoteObject;
public class HelloImpl extends PortableRemoteObject implements HelloInterface{
public HelloImpl() throws java.rmi.RemoteException {
super(); // invoke rmi linking and remote object initialization
}
public void sayHello( String from ) throws java.rmi.RemoteException {
System.out.println( "Hello from " + from + "!!" );
System.out.flush();
}
}
Java プログラミング言語では、あるインタフェースを実装することをクラスが宣言すると、そのクラスとコンパイラの間で契約が結ばれます。 この契約によって、そのクラスは、そのインタフェース内で宣言された各メソッドシグニチャーに対して、メソッドの本体 (つまり定義) を提供することを約束します。 インタフェースのメソッドは、暗黙のうちに public および abstract として宣言されているため、実装クラスでその契約が果たされない場合、そのクラスは定義に基づき abstract になります。そのクラスが abstract として宣言されていない場合は、コンパイラによってその事実が指摘されます。
この例では、実装クラスは HelloImpl です。 このクラスは、どのリモートインタフェースを実装するのかを宣言します。 HelloImpl クラスの宣言は、次のとおりです。
public class HelloImpl extends PortableRemoteObject
implements HelloInterface{
便宜上、実装クラスはリモートクラスを継承できます。この例では、リモートクラスは javax.rmi.PortableRemoteObject です。 PortableRemoteObject を継承していることにより、HelloImpl クラスを、通信に IIOP ベースのトランスポートを使うリモートオブジェクトを作成するために利用できます。
さらに、リモートオブジェクトのインスタンスは「エクスポート」される必要があります。 リモートオブジェクトをエクスポートすると、そのオブジェクトは、匿名ポート上でリモートオブジェクトへの着呼を監視することによって、着信したリモートメソッド要求を受け入れることができるようになります。 javax.rmi.PortableRemoteObject を継承すると、そのクラスは作成時に自動的にエクスポートされます。
オブジェクトのエクスポートは、java.rmi.RemoteException をスローする可能性があるため、コンストラクタがほかに何も行わない場合でも、RemoteException をスローするコンストラクタを定義する必要があります。 コンストラクタを定義しなかった場合は、javac は、次のエラーメッセージを生成します。
HelloImpl.java:3: unreported exception java.rmi.RemoteException; must be
caught or declared to be thrown.
public class HelloImpl extends PortableRemoteObject implements HelloInterface{
^
1 error
復習: リモートオブジェクトの実装クラスが行う必要のある事柄は、次のとおりです。
java.rmi.RemoteException をスローするようにコンストラクタを定義する
HelloImpl クラスのコンストラクタは、次のとおりです。
public HelloImpl() throws java.rmi.RemoteException {
super();
}
次の点に注意してください。
super メソッド呼び出しは、リモートオブジェクトをエクスポートする javax.rmi.PortableRemoteObject の引数なしのコンストラクタを呼び出す
java.rmi.RemoteException をスローする必要がある
java.rmi.RemoteException が実行時例外ではなくチェックされる例外である理由については、rmi-users 電子メールリストの下記のアーカイブを参照してください。
http://java.sun.com/products/jdk/rmi/archives/3490.html
スーパークラスの引数なしのコンストラクタ super() への呼び出しは、もし省略したとしてもデフォルトで発生しますが、この例では、クラスの前にスーパークラスが構築されることを明確にするために、この呼び出しを省略せずに記述しました。
sayHello メソッドの実装例を示します。 この例では、呼び出し側に「Hello from MARS!!」という文字列が返されます。
public void sayHello( String from ) throws java.rmi.RemoteException {
System.out.println( "Hello from " + from + "!!");
System.out.flush();
}
リモートメソッドに渡す引数、またはリモートメソッドからの戻り値は、Java プラットフォーム用のどのデータ型であっても構いません。さらに、インタフェース java.io.Serializable を実装したオブジェクトであれば、オブジェクト型であっても構いません。 java.lang および java.util 内のコア Java クラスの大部分は、Serializable インタフェースを実装しています。 RMI では、次のようになります。
static または transient とマークされたもの以外は、オブジェクトのすべてのデータメンバ (またはフィールド) がコピーされる。 直列化のデフォルト動作を変更する方法については、「Java オブジェクト直列化仕様」を参照
rmic を使ってスタブおよびスケルトンを生成する」で説明する。
サーバクラスは、リモートオブジェクト実装のインスタンスを生成し、そのインスタンスをネームサービスの名前にバインドする main メソッドを持ちます。 この main メソッドを含むクラスは、実装クラスそのものである場合も、まったく別のクラスである場合もあります。
この例では、main は HelloServer.java の一部として含まれており、次の処理を実行します。
HelloServer.java ファイルを作成します。 このファイルのソースコードは以下のとおりです。 ソースコードを紹介したあと、上記の各ステップについて説明します。
//HelloServer.java
import javax.naming.InitialContext;
import javax.naming.Context;
public class HelloServer {
public static void main(String[] args) {
try {
// STEP 1: Instantiate the Hello servant
HelloImpl helloRef = new HelloImpl();
// STEP 2: Publish the reference in the Naming Service
// using JNDI API
Context initialNamingContext = new InitialContext();
initialNamingContext.rebind("HelloService", helloRef );
System.out.println("Hello Server: Ready...");
} catch (Exception e) {
System.out.println("Trouble: " + e);
e.printStackTrace();
}
}
}
main メソッドでは、リモートオブジェクト実装のインスタンス (つまり「サーバント」) を作成する必要があります。 例を示します。
HelloImpl helloRef = new HelloImpl();
コンストラクタはリモートオブジェクトをエクスポートします。これは、リモートオブジェクトが作成された時点で、そのリモートオブジェクトは着呼を受け入れる準備ができていることを意味します。
リモートオブジェクトがサーバに登録されたあとは、呼び出し側は、オブジェクトを名前で検索し (ネームサービスを利用する)、リモートオブジェクトへの参照を取得してはじめて、そのオブジェクトのメソッドをリモートから呼び出せるようになります。 この例では、Object Request Broker Daemon (orbd) の一部であるネームサービスを使用します。
たとえば、次のコードは、「HelloService」という名前をリモートオブジェクトへの参照にバインドします。
// STEP 2: Publish the reference in the Naming Service
// using JNDI API
Context initialNamingContext = new InitialContext();
initialNamingContext.rebind("HelloService", helloRef );
rebind メソッド呼び出しの引数については、次の点に注意してください。
"HelloService" は、バインドするリモートオブジェクトの名前を表す java.lang.String である
helloRef はバインドするリモートオブジェクトのオブジェクト ID である
この例のクライアントアプリケーションは、リモートから sayHello メソッドを呼び出して、クライアントアプリケーションが実行されたときに「Hello from MARS!!」という文字列を表示します。
HelloClient.java ファイルを作成します。 クライアントアプリケーションのソースコードは、次のとおりです。
//HelloClient.java
import java.rmi.RemoteException;
import java.net.MalformedURLException;
import java.rmi.NotBoundException;
import javax.rmi.*;
import java.util.Vector;
import javax.naming.NamingException;
import javax.naming.InitialContext;
import javax.naming.Context;
public class HelloClient {
public static void main( String args[] ) {
Context ic;
Object objref;
HelloInterface hi;
try {
ic = new InitialContext();
// STEP 1: Get the Object reference from the Name Service
// using JNDI call.
objref = ic.lookup("HelloService");
System.out.println("Client: Obtained a ref. to Hello server.");
// STEP 2: Narrow the object reference to the concrete type and
// invoke the method.
hi = (HelloInterface) PortableRemoteObject.narrow(
objref, HelloInterface.class);
hi.sayHello( " MARS " );
} catch( Exception e ) {
System.err.println( "Exception " + e + "Caught" );
e.printStackTrace();
return;
}
}
}
まず、クライアントアプリケーションは、リモートオブジェクト実装 (「HelloService」として公開されている) への参照を、Java Naming and Directory Interface [TM] (JNDI) 呼び出しを使用してネームサービスから取得します。 Naming.rebind メソッドと同じく、Naming.lookup メソッドも、検索するオブジェクトの名前を表す java.lang.String 値を引数として取ります。 検索したいオブジェクトの名前を Naming.lookup() に提供すると、その名前にバインドされたオブジェクトが返されます。 Naming.lookup() は、呼び出し側 (HelloClient) に Hello インタフェースのリモート実装のスタブを返します。
sayHello() メソッドをリモートから呼び出して、コマンド行に「Hello from MARS!!」という文字列を表示させる
HelloInterface.java - リモートインタフェースのソースコードが入っている
HelloImpl.java - リモートオブジェクト実装のソースコードが入っている
HelloServer.java - サーバのソースコードが入っている
HelloClient.java - クライアントアプリケーションのソースコードが入っている
HelloImpl.java をコンパイルして、rmic を実行するのに必要な .class ファイルを作成します。 次に、rmic コンパイラを実行して、スタブとスケルトンを作成します。 スタブとは、リモートオブジェクトのクライアント側のプロキシのことで、RMI-IIOP 呼び出しをサーバ側のディスパッチャに転送します。続いて、ディスパッチャは、その呼び出しを実際のリモートオブジェクト実装に転送します。 最後に、残りの .java ソースファイルをコンパイルして、.class ファイルを作成します。
このセクションで実行する作業は次のとおりです。
スタブファイルとスケルトンファイルを作成するには、リモートオブジェクト実装の入ったコンパイル済みクラスファイルの完全指定のパッケージ名について、rmic を実行する必要があります。 この例では、リモートオブジェクト実装の入ったファイルは HelloImpl.java です。 スタブとスケルトンを生成するには、
次のようにして HelloImpl.java をコンパイルします。
javac -d . -classpath . HelloImpl.java
「-d .」オプションは、生成されたファイルを、コンパイラを実行しているのと同じディレクトリに置くことを示します。 「-classpath .」オプションは、HelloImpl.java が依存しているファイルが、このディレクトリ内にあることを示します。
rmic を使ってスタブおよびスケルトンを生成するrmic を、-iiop オプションを指定して実行します。 rmic コマンドは、引数に 1 つ以上のクラス名をとり、_HelloImpl_Tie.class および _HelloInterface_Stub.class の形式のクラスファイルを生成します。 この例では、リモート実装ファイル HelloImpl.class のクラス名を渡します。
rmic のオプションの詳細は、Solaris 用 rmic のマニュアルページまたは Win32 用 rmic のマニュアルページを参照してください。

HelloImpl リモートオブジェクト実装のスタブおよびスケルトンを作成するには、次のように rmic を実行します。
rmic -iiop HelloImpl
上記のコマンドによって、次のファイルが作成されます。
_HelloInterface_Stub.class - クライアントスタブ
_HelloImpl_Tie.class - サーバスケルトン
次の方法で、すべてのソースファイルをコンパイルします。
javac -d . -classpath . HelloInterface.java HelloServer.java HelloClient.java
このコマンドにより、HelloInterface.class、HelloServer.class、および HelloClient.class の各クラスファイルが作成されます。 これらのファイルはそれぞれ、リモートインタフェース、サーバ、そしてクライアントアプリケーションです。 javac のオプションの詳細は、Solaris 用 javac のマニュアルページまたは Win32 用 javac のマニュアルページを参照してください。
orbd) を使用します。これには、一時ネームサービスと持続ネームサービスの両方が組み込まれており、J2SE 1.4 以降をダウンロードすれば入手できます。
呼び出し側 (クライアント、ピア、またはクライアントアプリケーション) がリモートオブジェクトのメソッドを呼び出すには、呼び出し側はまずリモートオブジェクトへの参照を取得する必要があります。
リモートオブジェクトがサーバに登録されると、呼び出し側は、そのオブジェクトを名前によって検索して、リモートオブジェクトへの参照を取得できます。そうすれば、そのオブジェクトのメソッドをリモートから呼び出せます。
ネームサービスを起動するには、コマンド行から orbd を実行します。
この例の場合、Solaris オペレーティングシステムでは次のコマンドを実行します。
orbd -ORBInitialPort 1050&
Windows オペレーティングシステムでは、次のコマンドを実行します。
start orbd -ORBInitialPort 1050
このコマンドでは、orbd を実行するポートを指定しなければなりません。 1024 より番号の小さいポート上でプロセスを開始するにはスーパーユーザになる必要があるため、この例では、ポート 1050 が選択されています。Solaris オペレーティング環境では、orbd ツールの詳細については、orbd のマニュアルページを参照してください。
リモートインタフェースを変更したり、変更または追加されたリモートインタフェースをリモートオブジェクトの実装で使用する場合は、必ずサーバをいったん停止してから再起動する必要があります。 そうしないと、ネームサービスでバインドされるオブジェクト参照の型が、修正されたクラスと一致しなくなります。
端末ウィンドウをもう 1 つ開き、この例のソースファイルが入っているディレクトリに移ります。 クライアントを実行するための下記のコマンドは、読みやすくするために複数行に分けてありますが、実際にコマンドを入力するときには改行を入れないでください。 このコマンドは、HelloServer サーバを起動する方法を示しています。 orbd ツールを起動するときに 1050 以外のポートや localhost 以外のホストを使用した場合には、下記のコマンドの該当する値を、orbd を起動するときに使用した実際の値で置き換えてください。
次のようにして、Hello サーバを起動します。
java
-classpath .
-Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://localhost:1050
HelloServer
java のオプションの詳細は、Solaris 用 java のマニュアルページまたは Win32 用 java マニュアルページを参照してください。
出力は、次のようになります。
Hello Server: Ready ...
orbd ツールを起動するときに 1050 以外のポートや localhost 以外のホストを使用した場合には、下記のコマンドの該当する値を、orbd を起動するときに使用した実際の値で置き換えてください。
次のようにして、クライアントアプリケーションを起動します。
java
-classpath .
-Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
-Djava.naming.provider.url=iiop://localhost:1050
HelloClient
クライアントアプリケーションを実行すると、次のような出力が画面に表示されます。
Client: Obtained a ref. to Hello server. Hello from MARS
ORBD および Hello サーバは、明示的に停止しないかぎり継続して実行されます。 これらのプロセスを停止したい場合、Solaris では、端末ウィンドウから pkill orbd および pkill HelloServer コマンドを実行します。 Windows の場合は、プロンプトウィンドウ内で Ctrl+C と入力します。
これで基礎的な RMI-IIOP チュートリアルを終わります。 さらに複雑なアプリケーションの作成に進む場合は、次に挙げる情報が役に立ちます。
コメントの送付先: rmi-iiop@sun.com