このチュートリアルでは、おなじみの「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 (Solaris、LinuxまたはMac OS X用またはWindows用の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 (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、LinuxまたはMac OS X用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、LinuxまたはMac OS X用javacのマニュアル・ページまたはWindows用javacのマニュアル・ページを参照してください。
orbd)を使用します。これには、一時ネーム・サービスと持続ネーム・サービスの両方が組み込まれており、JDKをダウンロードすれば入手できます。
呼出し側(クライアント、ピア、またはクライアント・アプリケーション)がリモート・オブジェクトのメソッドを呼び出すには、呼出し側はまずリモート・オブジェクトへの参照を取得する必要があります。
リモート・オブジェクトがサーバーに登録されると、呼出し側は、そのオブジェクトを名前によって検索して、リモート・オブジェクトへの参照を取得できます。そうすれば、そのオブジェクトのメソッドをリモートから呼び出せます。
ネーム・サービスを起動するには、コマンド行からorbdを実行します。
この例の場合、Solarisオペレーティング・システムでは次のコマンドを実行します。
orbd -ORBInitialPort 1050&
Windowsオペレーティング・システムでは、次のコマンドを実行します。
start orbd -ORBInitialPort 1050
orbdを実行するポートを指定する必要があります。 Solarisオペレーティング環境では、1024より番号の小さいポート上でプロセスを開始するにはスーパー・ユーザーになる必要があるため、この例では、ポート1050が選択されています。 orbdツールの詳細は、orbdのマニュアル・ページ(Solaris、LinuxまたはMac OS X用またはWindows用)を参照してください。
リモート・インタフェースを変更したり、変更または追加されたリモート・インタフェースをリモート・オブジェクトの実装で使用する場合は、必ずサーバーをいったん停止してから再起動する必要があります。 そうしないと、ネーム・サービスでバインドされるオブジェクト参照の型が、変更されたクラスと一致しなくなります。
端末ウィンドウをもう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、LinuxまたはMac OS X用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、LinuxまたはMac OS Xでは、これらのプロセスを停止する場合に、端末ウィンドウからpkill orbdおよびpkill HelloServerコマンドを実行します。 Windowsの場合は、プロンプト・ウィンドウ内でCtrl+Cと入力します。
これで基礎的なRMI-IIOPチュートリアルを終わります。 さらに複雑なアプリケーションの作成に進む場合は、次に挙げる情報が役に立ちます。