このチュートリアルでは、おなじみの「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