RMI を使ってはじめましょう


この章ではおなじみの Hello World プログラムの分散システム版を Java の RMI (Remote Method Invocation) を使って作成する手順をステップを追って説明します。

分散バージョンの Hello World はアプレットを使ってサーバのリモートメソッドを呼び出し、ここからプログラムをダウンロードして "Hello World" というメッセージを取り出します。このアプレットが走るとクライアント側にメッセージが表示されます。

これを完成させるには、次の3つのレッスンを実行しなければなりません。

HTML と Java のソースファイルを書く

Hello World のサーバとアプレットのために4つのファイルが必要です。

  1. Java リモートインタフェース
  2. リモートインタフェースを実装するためのリモートオブジェクト(サーバ)
  3. サーバのメソッドをリモートから呼び出すための Java アプレット
  4. アプレットを参照する web ページのための HTML コード
Java 言語はクラスファイルの完全修飾されたパッケージ名とそのクラスへのディレクトリパスとの間のマッピングを必要とするため、Java のコードを書き始める前にパッケージとディレクトリの名前を決めなければなりません。(このマッピングにより Java コンパイラは Java プログラムが参照するクラスファイルのディレクトリを知ることができます)この章で作成する Hello World プログラムではパッケージ名を examples.hello、そしてルートディレクトリを $HOME/jdk1.1/mysrc/examples/hello とします。
例えば Solaris の場合であればソースファイル用ディレクトリを作るには次のコマンドを実行します。

mkdir $HOME/jdk1.1/mysrc/examples/hello

リモートインタフェースを定義する

リモートメソッド呼び出しは、ネットワーク通信やサーバの問題など、ローカルメソッド呼び出しとは全く異なる理由で失敗することがあります。

リモートオブジェクトであることをはっきりさせるために、オブジェクトはリモートインタフェースを実装しますが、このインタフェースは次の特徴を持っています。

次に Hello World に対するインタフェース定義を示します。インタフェースはメソッド、 sayHello 、を一つだけ含み、呼び出し側に文字列を返します。

package examples.hello;
public interface Hello extends java.rmi.Remote {
	String sayHello() throws java.rmi.RemoteException;
}

実装クラスを書く

リモートオブジェクトを書くときは、一つ以上のリモートインタフェースを実装するクラスを作成します。実装クラスは次の条件を備えなければなりません。

  1. 一つ以上のリモートインタフェースを実装するよう指定します。
  2. リモートオブジェクトのコンストラクタを定義します。
  3. リモートに呼び出される可能性のあるメソッドを実装します。
  4. セキュリティマネージャを作成してインストールします。
  5. リモートオブジェクトのインスタンスを1つ以上作成します。
  6. ブートストラッピングのために、リモートオブジェクトの少なくとも一つを RMI リモートオブジェクトレジストリに登録します。
例えば、次に示す HelloImpl.java ファイルは Hello World サーバのためのコードを含んでいます。このコードは上に説明した6つのステップにしたがって記述されています。

package examples.hello;

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl
	extends UnicastRemoteObject
	implements Hello
{
	private String name;

	public HelloImpl(String s) throws RemoteException {
		super();
		name = s;
	}

	public String sayHello() throws RemoteException {
		return  "Hello World!";
	}
	public static void main(String args[])
	{
		// Create and install a security manager
		System.setSecurityManager(new RMISecurityManager());

		try {
			HelloImpl obj = new HelloImpl("HelloServer");
			Naming.rebind("//myhost/HelloServer", obj);
			System.out.println("HelloServer bound in registry");
		} catch (Exception e) {
			System.out.println("HelloImpl err: " + e.getMessage());
			e.printStackTrace();
		}
	}
}

リモートインタフェースの実装

Hello World での実装クラスは HelloImpl です。実装クラスは自分が実装する一つ以上のリモートインタフェースを指定します。オプションとして、自分が拡張するリモートサーバを指定します。この例では java.rmi.server.UnicastRemoteObject がこれにあたります。 HelloImpl クラス宣言を次に示します。

public class HelloImpl
	implements Hello
	extends java.rmi.server.UnicastRemoteObject
UnicastRemoteObject を拡張するということは HelloImpl クラスを使って単一の(複製されたものでない)リモートオブジェクトを作成し、そのオブジェクトは通信のために RMI のデフォルトソケットをベースとするトランスポートを使うことを示します。 リモートオブジェクトをリモートでないクラスから拡張する場合には、メソッド UnicastRemoteObject.exportObject を呼び出すことにより陽にリモートオブジェクトをエクスポートしなければなりません。

リモートオブジェクトのコンストラクタ定義

リモートクラスのコンストラクタは非リモートクラスのコンストラクタと同じで、新規作成される各クラスインスタンスの変数を初期化します。

次に示すのは HelloImpl クラスのコンストラクタで、private な文字列変数 name をリモートオブジェクトの名前で初期化します。

private String name;
public HelloImpl(String s) throws java.rmi.RemoteException {
	super();
	name = s;
}
次の項目に注意してください。

もし省略するとデフォルトで super の引数無しコンストラクタが呼び出されますが、この例では Java がクラス作成前にスーパークラスを作成することを明示するために省略せずに載せてあります。

それぞれのリモートメソッドに実装を与える

リモートオブジェクトの実装クラスはリモートインタフェースで指定されたそれぞれのリモートメソッドを実装するためのコードを含みます。

一例として次に sayHello メソッドの実装例を示します。この例では文字列 "Hello World!" を呼び出し側に返します。

public String sayHello() throws RemoteException {
	return  "Hello World!";
}
リモートメソッドへ渡す引数、またはリモートメソッドから返される値は Java の持つどの変数型でもかまいませんし、インタフェース java.io.Serializable を実装するものであればオブジェクトであってもかまいません。java.langjava.util のコアとなるほとんどの Java クラスは Serializable インタフェースを実装しています。

クラスはリモートインタフェースで指定されないメソッドを定義することができますが、これらのメソッドはそのサービスを実行する仮想マシンからのみ呼び出し可能であり、リモートからの呼び出しはできません。

セキュリティマネージャの生成とインストール

サービスのメインメソッドはまずセキュリティマネージャの生成とインストールを行わなければなりません。セキュリティマネージャは RMISecurityManager かまたは自分自身で定義したものです。例えば、

System.setSecurityManager(new RMISecurityManager());
セキュリティマネージャはロードされたメソッドを監視して "sensitive" な操作を行わないことを保証するもので、必ず実行されなければなりません。セキュリティマネージャが全く指定されないときは、ローカルであってもそれ以外であっても、RMI クラスからのクラスをロードすることはできません。

一つ以上のリモートオブジェクトインスタンスを作成する

サービスのメインメソッドはサービスを提供するリモートオブジェクトの一つ以上のインスタンスを作成する必要があります。例えば、

HelloImpl obj = new HelloImpl("HelloServer");
コンストラクタはリモートオブジェクトをエクスポートします。この意味は、一度リモートオブジェクトが作成されると、そのリモートオブジェクトは外部からの呼び出しに応える準備ができたことを意味します。

リモートオブジェクトを登録する

呼び出し側 (クライアント、ピア、またはアプレット)がリモートオブジェクトのメソッドを呼び出し可能であるためには、まず呼び出し側がリモートオブジェクトへの参照を得る必要があります。多くの場合この参照は他のリモートメソッドへのパラメータか、または他のリモートメソッドの返り値として得ることができます。

ブートストラッピングの場合には、RMI システムが URL を基礎とするレジストリを与え、これにより //host/objectname の形式の URL を単なる文字列で指定されたリモートオブジェクトに結び付けることができます。いったんリモートオブジェクトがサーバに登録されると、呼び出し側はオブジェクトを名前で探し、リモートオブジェクトへの参照を得ることができ、そのオブジェクトのメソッドをリモートから呼び出すことができるようになります。

例えば、次のコードは HelloServer という名前のリモートオブジェクトの URL をリモートオブジェクトへの参照と結び付けるものです。

Naming.rebind("//myhost/HelloServer", obj);
呼び出しの引数について次の項目に注意が必要です。

セキュリティ上の理由により、アプリケーションがバインド、またはアンバインドできるのは同一ホスト上でレジストリを実行している場合だけです。この制限によりクライアントがサーバのリモートレジストリを削除したり上書きしたりという事故を防ぐことができます。ルックアップはどのホストからでも可能です。

リモートサービスを使うアプレットの書き方

分散システム版 Hello World のアプレット部分は "Hello World!" という文字列を得るために HelloServer の sayHello メソッドをリモートから呼び出し、アプレットを実行するとこの文字列が表示されます。次に示すのがこのアプレットのコードです。

package examples.hello;

import java.awt.*;
import java.rmi.*;

public class HelloApplet extends java.applet.Applet {
	String message = "";
	public void init() {
		try {
			Hello obj = (Hello)Naming.lookup("//" +
				getCodeBase().getHost() + "/HelloServer");
			message = obj.sayHello();
		} catch (Exception e) {
			System.out.println("HelloApplet exception: " +
				e.getMessage());
			e.printStackTrace();
		}
	}
	public void paint(Graphics g) {
		g.drawString(message, 25, 50);
	}
}
  1. アプレットは最初にサーバのレジストリから "HelloServer" への参照を取り出し、getCodeBase メソッドと getHost メソッドを使うことにより URL を作ります。
  2. アプレットはリモートオブジェクト HelloServer の sayHello メソッドをリモートから呼び出し、その結果得られる返り値("Hello World!" という文字列)を message という名前の変数に格納します。
  3. アプレットは paint メソッドを呼び出してアプレットを画面に表示します。この結果 "Hello World!" が画面に現れます。
作成された URL はホストを含んでいなければなりません。そうでないとアプレットのルックアップはデフォルトでクライアントに向けられ、アプレットはローカルシステムにアクセスすることは許されませんから AppletSecurityManager が例外処理にスローしてしまい、その代わりにアプレットホストのみとの交信ができなくなってしまいます。

アプレットを含んだ Web ページを書く

次に示すのは Hello World アプレットへの参照を含んだ Web ページの HTML コードです。

<HTML>
<title>Hello World</title>
<center> <h1>Hello World</h1> </center>

The message from the HelloServer is:
<p>
<applet codebase="../.."
	code="examples.hello.HelloApplet"
	width=500 height=120>
</applet>
</HTML>
次の事項に注意してください。

クラスファイルと HTML ファイルのコンパイルと分配

これで例題 Hello World のためのソースコードが完成し、$HOME/jdk1.1/mysrc/hello ディレクトリに下には4つのファイルが存在します。

この章では、.java ソースファイルをコンパイルして .class ファイルを作成します。次に rmic コンパイラを実行してスタブとスケルトンを作成します。スタブとはリモートオブジェクトのクライアント側のプロキシであり、RMI 呼び出しをサーバ側のスケルトンに伝える働きをします。するとスケルトンは呼び出しを実際のリモートオブジェクトの実装を呼び出します。

javac や rmic コンパイラを使用するときは、その結果生成するクラスファイルをどのディレクトリに置くかを指定しなければなりません。アプレットの場合には全てのファイルをそのアプレットの codebase ディレクトリに置かなければなりません。この例では $HOME/public_html/codebase になります。
Web サーバの中には、"http://host/‾username/" の形式で構成された HTTP URL によってユーザの public_html ディレクトリにアクセスできるものがあります。もし Web サーバがこの規約をサポートしていなければ "file://home/username/public_html" の形式のファイル URL を使うことになります。

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

結果を配置するディレクトリ $HOME/public_html/codebase と開発ディレクトリ $HOME/jdk1.1/mysrc/examples/hello がローカル CLASSPATH によって開発マシン上で明確に定義されていることを確認してください。

Java ソースファイルをコンパイルするには、次の形式の javac コマンドを実行します。:

javac -d $HOME/public_html/codebase
	Hello.java HelloImpl.java HelloApplet.java
このコマンドはディレクトリ examples/hello (もし、まだ存在していなければ) をディレクトリ $HOME/public_html/codebase の下に作成します。このコマンドは今作成したディレクトリにファイル Hello.class, HelloImpl.class, and HelloApplet.class を書き込みます。 これらのファイルはそれぞれリモートインタフェース、サーバ、そしてアプレットです。

スタブとスケルトンの生成

スタブとスケルトンファイルを作成するには、コンパイル済みでその中でリモートオブジェクトの実装名を指定してあるファイル上で rmic コンパイラを実行します。rmic コンパイラには一つ以上のクラス名を入力として与え、出力として myImpl_Skel.classmyImpl_Stub.class の形式のクラスファイルが作られます。

例えば、HelloImpl リモートオブジェクト実装のスタブとスケルトンを作成するには次の形式で rmic を実行します。

rmic -d $HOME/public_html/codebase examples.hello.HelloImpl
-d オプションはコンパイルされたスタブとスケルトンファイルが置かれるルートディレクトリを示します。したがって、上のコマンドを実行すると次のファイルがディレクトリ $HOME/public_html/codebase/examples/hello の下に作成されます。

作成されたスタブはリモートオブジェクトの正確に同一セットのリモートインタフェースを実装することに注意してください。これはクライアントがキャストとタイプチェックのために Java 言語の組み込み演算子を使えることを意味し、同時に Java のリモートオブジェクトが真のオブジェクト指向ポリモーフィズムをサポートしていることを示します。

HTML ファイルを配置ディレクトリへ移す

アプレットを参照する web ページがクライアントから認識できるように、index.html ファイルを開発ディレクトリから codebase ディレクトリに移動させておかなければなりません。例えば次のコマンドを実行します。

mv $HOME/jdk1.1/mysrc/examples/hello/index.html
	$HOME/public_html/codebase/examples/hello

ランタイムパスの設定

HelloImpl サーバを走らせる前に $HOME/public_html/codebase ディレクトリがサーバのローカル CLASSPATH で認識できることを確認しておいてください。

リモートオブジェクトレジストリ、サーバ、そしてアプレットのスタート

RMI ブートストラップレジストリのスタート

RMI レジストリはサーバ側の単純ブートストラップ名前サーバで、これによりリモートクライアントはリモートオブジェクトへの参照を得ることができます。このサーバは典型的には、アプリケーションが最初に通信するリモートオブジェクトの場所を特定するために使用されます。最初に指定されたオブジェクトが今度はアプリケーションが他のオブジェクトを見つけるサポートを行います。

サーバ上でレジストリをスタートされるためには rmiregistry コマンドを実行します。このコマンドは何も出力せず、一般にバックグランドで実行されます。Windows95、および Windows NT では次のようになります。

start rmiregistry
(start コマンドが使えなければ javaw を使う)

Solaris 上では次のようになります。

rmiregistry &
デフォルト条件ではレジストリはポート 1099 で実行されます。異なるポート上でレジストリを実行するにはコマンド中でポート番号を指定しなければなりません。例えば、Windows NT 上でポート番号 2001 でレジストリをスタートさせるには次のようにします。

start rmiregistry 2001
デフォルト条件以外のポートでレジストリを実行する場合は、レジストリ呼び出しをするときに java.rmi.Naming クラスの URL ベースメソッドの中でポート番号を指定する必要があります。例えば、例題 Hello World の中でポート番号 2001 でレジストリを実行するのであれば、HelloServer の URL とリモートオブジェクト参照を結び付けるために次の呼び出しが必要になります。

Naming.rebind("//myhost:2001/HelloServer", obj);
同じような理由により、web ページに格納された URL もデフォルト以外のポートを指定しなければなりません。そうしないと、アプレットがレジストリファイルの中からサーバを探そうとした時点で試みは失敗します。

<PARAM name="url" value="//myhost:2001/HelloServer">
リモートインタフェースを変更するかまたは、リモートオブジェクト実装で変更/追加が発生したときはレジストリを一回停止してから再スタートしなければなりません。そうしないとレジストリで関係付けられたクラスと、修正されたクラスとが一致しなくなります。

サーバのスタート

サーバをスタートさせる時点で java.rmi.server.codebase プロパティが正しく指定され、サーバが作成するリモートオブジェクトへの参照がスタブクラスをクライアントへ動的にダウンロードするための URL を含んでいなければなりません。

次のコマンドは、このプロパティを含んで HelloImpl サーバをスタートさせる方法を示します。

java -Djava.rmi.server.codebase=http://myhost/‾myusrname/codebase/
	examples.hello.HelloImpl &
codebase URL の最後の / は省略できません。

スタブクラスはそれがすでにローカルで利用できる状態になっていない場合に限り、クライアントの仮想マシンへ動的にロードされます。

アプレットの実行

レジストリとサーバが一旦走り出せばアプレットの実行が可能になります。アプレットの実行はブラウザへ該当する web ページをロードするか、または次に示すように appletviewer で行います。

appletviewer
	http://myhost/‾myusrname/codebase/examples/hello/index.html &
Appletviewer を走らせると、次の図のような出力がディスプレイ上に現れます。



Copyright (C) 1996, 1997 Sun Microsystems, Inc. All rights reserved.