このチュートリアルでは、おなじみの「Hello World」プログラムの分散システム版を、Java の 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!」が表示されます。
このチュートリアルの構成は、次のとおりです。
チュートリアルの各手順はこの記号によって示します。
このセクションで行うタスクは 3 つあります。
HelloInterface.java
- リモートインタフェースHelloImpl.java
- HelloInterface
を実装するリモートオブジェクトの実装HelloServer.java
- リモートオブジェクト実装のインスタンスを作成し、そのインスタンスをネームサービスの名前にバインドする、RMI サーバーHelloClient.java
- リモートメソッド sayHello()
を呼び出すクライアントアプリケーションソースファイルを作成する、または HelloRMIIIOP.zip をダウンロードして解凍するには、次の手順に従います。
Remote
インタフェースを実装するクラスのインスタンスです。作成するリモートインタフェースでは、ほかのマシンから呼び出したい各メソッドを宣言します。リモートインタフェースには、次の特性があります。
public
として宣言する必要があります。そうしないと、クライアントがリモートインタフェースと同じパッケージ内にある場合を除いて、リモートインタフェースを実装しているリモートオブジェクトをクライアントがロードしようとした時点でエラーが発生します。java.rmi.Remote
インタフェースを拡張します。throws
節内で java.rmi.RemoteException
(または RemoteException
のスーパークラス) を宣言する必要があります。HelloImpl
) ではなく、リモートインタフェースの型 (たとえば、HelloInterface
) として宣言する必要があります。この例では、すべてのソースファイルを同じディレクトリ (たとえば、$HOME/mysrc/helloWorld
) 内に作成します。
HelloInterface.java ファイルを作成します。次のコードは、リモートインタフェース HelloInterface
のインタフェース定義です。この定義には、ただ 1 つのメソッド 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
をスローすることによって報告します。 分散システム上での障害および回復の詳細については、分散コンピューティングでの注意事項を参照してください。
リモートオブジェクトの実装クラス 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
をスローする必要があります。スーパークラスの引数なしのコンストラクタ 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
内のコアクラスの大部分は、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 -iiop
コマンドは、引数に 1 つ以上のクラス名をとり、_HelloImpl_Tie.class
および _HelloInterface_Stub.class
という形式のクラスファイルを生成します。この例では、リモート実装ファイル HelloImpl.class
のクラス名を渡します。
rmic
のオプションの詳細は、Solaris 用 rmic
のマニュアルページまたは Windows 用 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
のマニュアルページまたは Windows 用 javac
のマニュアルページを参照してください。
orbd
) を使用します。これには、一時ネームサービスと持続ネームサービスの両方が組み込まれており、J2SE 1.4 以降をダウンロードすれば入手できます。
呼び出し側 (クライアント、ピア、またはクライアントアプリケーション) がリモートオブジェクトのメソッドを呼び出すには、呼び出し側はまずリモートオブジェクトへの参照を取得する必要があります。
リモートオブジェクトがサーバーに登録されると、呼び出し側は、そのオブジェクトを名前によって検索して、リモートオブジェクトへの参照を取得できます。そうすれば、そのオブジェクトのメソッドをリモートから呼び出せます。
ネームサービスを起動するには、コマンド行から orbd
を実行します。
この例の場合、Solaris オペレーティングシステムでは次のコマンドを実行します。
orbd -ORBInitialPort 1050&
Windows オペレーティングシステムでは、次のコマンドを実行します。
start orbd -ORBInitialPort 1050
orbd
を実行するポートを指定する必要があります。Solaris オペレーティング環境では、1024 より番号の小さいポート上でプロセスを開始するにはスーパーユーザーになる必要があるため、この例では、ポート 1050
が選択されています。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
のマニュアルページまたは Windows 用 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 チュートリアルを終わります。さらに複雑なアプリケーションの作成に進む場合は、次に挙げる情報が役に立ちます。