JavaTM RMI の使用による動的なコードのダウンロード (java.rmi.server.codebase プロパティを使用)


このチュートリアルの構成は、次のとおりです。

  1. Starting out
  2. コードベースとは
  3. コードベースの動作
  4. スタブのダウンロード以外の目的で Java RMI でコードベースを使用する
  5. コマンド行の例
  6. トラブルシューティングのヒント

1.0 はじめに

JavaTM プラットフォームのもっとも重要な機能の 1 つとして、通常は異なる物理システム上で稼動する別のプロセスで実行中の仮想マシン (VM) に、Uniform Resource Locator (URL) から Java ソフトウェアを動的にダウンロードする機能があります。この結果リモートシステムは、そのシステムのディスク上にインストールされていないアプレットなどのプログラムを実行できます。このドキュメントの最初の部分では、アプレットに関連したコードベースについて説明し、Java Remote Method Invocation (Java RMI) に関連したコードベースについて解説します。

たとえば、Web ブラウザの内部から実行されている VM は、java.applet.Applet のサブクラス、およびそのアプレットが必要とするその他のクラスのバイトコードをダウンロードできます。ブラウザが動作中のシステムは多くの場合、このアプレットを以前に実行したことも、ディスク上にインストールしたこともありません。必要なすべてのクラスがサーバからダウンロードされると、ブラウザは、このクライアントブラウザが実行されているシステムのローカルリソースを使って、アプレットプログラムの実行を開始できます。

Java RMI は、この機能を利用して、該当するクラスがディスクにインストールされたことのないシステム上にそのクラスをダウンロードし、実行します。Java RMI API を使うと、ブラウザ内の VM だけでなく、任意の VM が、特別な Java RMI スタブクラスを含む任意の Java クラスをダウンロードできます。 こうして、リモートサーバ上でサーバシステムのリソースを使ってメソッド呼び出しを実行することが可能になります。

コードベースの考え方は、Java プログラミング言語での ClassLoader の使用に基づいています。Java プログラムが ClassLoader を使用する場合、そのクラスローダは、クラスをどの位置からロードできるかを知る必要があります。通常、クラスローダは、Java プラットフォームにコンパイル済みのクラスを提供する HTTP サーバと共に使われます。多くの場合、ユーザが最初に扱うようになる ClassLoader とコードベースのペアは、AppletClassLoader と、HTML <applet> タグの「codebase」部分のペアでしょう。 そのため、このチュートリアルでは、ユーザに Java RMI のプログラミング経験だけでなく、アプレットタグを含む HTML ファイルを記述した経験があることを想定しています。たとえば、HTML ソースには次のようなコードが含まれます。

<applet height=100 width=100 codebase="myclasses/" code="My.class">
	<param name="ticker">
</applet>

2.0 コードベースとは

コードベースとは、仮想マシンにクラスをロードする際のソース、つまり位置であると定義できます。たとえば、新しい友達を夕食に招待する場合、あなたの家までの道順をその友達に伝える必要があるでしょう。 そうすれば、友達はあなたの家を見つけることができます。同じように、コードベースの位置を、VM に指示すると考えることができます。 そうしておけば、VM は「リモート可能」クラスを見つけることができます。

CLASSPATH は、ローカルクラスのロード元となるディスク上の位置のリストであるため、「ローカルコードベース」であると考えることができます。ローカルディスクをベースにしたソースからクラスをロードする場合は、CLASSPATH 変数が参考にされます。CLASSPATH は、クラスファイルのディレクトリまたはアーカイブ、あるいはその両方までの相対パスと絶対パスのどちらをとるようにも設定できます。CLASSPATH が「ローカルコードベース」の一種であるのと同じく、アプレットおよびリモートオブジェクトによって使われるコードベースは、「リモートコードベース」であると考えることができます。

3.0 コードベースの動作

3.1 アプレットでのコードベースの動作

アプレットと対話を行うには、リモートクライアントは、そのアプレットと、アプレットが実行する必要のあるすべてのクラスにアクセスできる必要があります。アプレットは、「ftp://」やローカルの「file:///」 URL からのアクセスも可能ですが、通常は、リモート HTTP サーバからアクセスされます。

  1. クライアントブラウザがクライアントの CLASSPATH に存在しないアプレットクラスを要求する
  2. アプレット (およびアプレットが必要とするその他のクラス) のクラス定義が、HTTP を使ってサーバからクライアントにダウンロードされる
  3. クライアント上でアプレットが実行される
上記 3 つの手続きを図示する

図 1: アプレットのダウンロード

アプレットのコードベースは、<applet> タグが含まれる HTML ページの URL と常に関連しています。

3.2 Java RMI でのコードベースの動作

Java RMI を使うと、アプリケーションは、ほかの VM でクライアントからのメソッド呼び出しを受け入れるリモートオブジェクトを作成できます。クライアントがリモートオブジェクトのメソッドを呼び出すには、クライアントにリモートオブジェクトと通信する手段が必要です。Java RMI では、クライアントがリモートオブジェクトのプロトコルで通信を行うようにプログラミングするのではなく、スタブという特別なクラスを使用します。 スタブは、リモートオブジェクトとの通信に使用する (リモートオブジェクトに対してメソッド呼び出しを行う) クライアントにダウンロードできます。java.rmi.server.codebase プロパティの値は、これらのスタブ (およびスタブが必要とするすべてのクラス) をダウンロードできる 1 つまたは複数の URL 位置を表します。

アプレットと同様、リモートメソッド呼び出しの実行に必要なクラスは「file:///」URL からダウンロードできます。 ただし、アプレットの場合と同じく、URL によって参照されるファイルシステムが NFS などの別のプロトコルを使って利用可能になる場合を除き、「file:///」URL を使うには、一般的にクライアントとサーバが同じ物理ホスト上に存在する必要があります。

一般に、リモートメソッド呼び出しを実行するために必要なクラスは、HTTP または FTP サーバなどのネットワークリソースからアクセスできるようにする必要があります。

下に示すとおり、スタブのダウンロードプロセスの最初の 5 ステップを図示する

図 2: Java RMI スタブのダウンロード

  1. リモートオブジェクトのコードベースは、リモートオブジェクトのサーバが java.rmi.server.codebase プロパティを設定することによって指定されます。Java RMI サーバは、名前にバインドされたリモートオブジェクトを Java RMI レジストリに登録します。サーバ VM 上で設定されたコードベースには、Java RMI レジストリ内でリモートオブジェクト参照が注釈として付けられます。
  2. Java RMI クライアントは、名前付きのリモートオブジェクトへの参照を要求します。参照 (リモートオブジェクトのスタブインスタンス) は、クライアントがリモートオブジェクトに対してリモートメソッド呼び出しを行うために使うものです。
  3. Java RMI レジストリは、要求されたクラスに参照 (スタブインスタンス) を返します。スタブインスタンスのクラス定義がクライアントの CLASSPATH 内でローカルに検出できる場合は、クライアントはそのクラスをローカルにロードします (CLASSPATH は、コードベースでの検索前に常に検索される)。スタブのクラス定義がクライアントの CLASSPATH 内で検出されなかった場合は、クライアントはリモートオブジェクトのコードベースからクラス定義を取得しようとします。
  4. クライアントはコードベースからクラス定義を要求します。クライアントが使うコードベースは、スタブクラスがレジストリによってロードされたときにスタブインスタンスに注釈として付けられた URL です。ステップ 1 では、エクスポートされるオブジェクトに注釈として付けられたスタブはそののち、名前にバインドされた Java RMI レジストリに登録されます。
  5. スタブ (およびスタブが必要とするすべてのクラス) のクラス定義がクライアントにダウンロードされます。
  6. 注: ステップ 4 および 5 は、リモートオブジェクトが Java RMI レジストリで (Java RMI レジストリに登録された) 名前にバインドされたときに、レジストリがリモートオブジェクトクラスをロードするためにとった手順と同じです。レジストリがリモートオブジェクトのスタブクラスをロードしようとしたときは、レジストリはそのリモートオブジェクトに関連付けられたコードベースからクラス定義を要求しました。
  7. クライアントはこれで、リモートオブジェクト上のリモートメソッドの呼び出しに必要なすべての情報を入手しました。スタブインスタンスが、サーバ上に存在するリモートオブジェクトに対してプロキシとして動作します。 このため、Java RMI クライアントは、コードベースを使ってローカル VM でコードを実行するアプレットとは異なり、図 3 に示すように、リモートオブジェクトのコードベースを使って、別の、リモート可能な VM にあるコードを実行します。
上記手順の最終ステップを図示する

図 3:リモートメソッド呼び出しを行う Java RMI クライアント

4.0 スタブのダウンロード以外の目的で Java RMI でコードベースを使用する

java.rmi.server.codebase プロパティは、スタブとスタブに関連するクラスをクライアントにダウンロードすることのほかにも、スタブだけでなく任意のクラスのダウンロード元となる位置を指定するために使うことができます。

クライアントがリモートオブジェクトに対してメソッド呼び出しを行う場合、呼び出すメソッドが引数を受け取らないか、または複数の引数を受け取るように記述されていることがあります。メソッドの引数のデータ型に従って、3 つの異なるケースが考えられます。

第 1 のケースでは、メソッドのパラメータ (および戻り値) すべてが基本型であり、リモートオブジェクトがそれらをメソッドのパラメータに変換する方法を知っているため、CLASSPATH またはコードベースをチェックする必要がありません。

第 2 のケースでは、リモートメソッドのパラメータまたは戻り値の少なくとも 1 つがオブジェクトであり、そのオブジェクトについて、リモートオブジェクトが CLASSPATH 内でローカルにクラス定義を検出できます。

第 3 のケースでは、(図 4 のステップ 6 で示されているように)、リモートメソッドがオブジェクトインスタンスを受け取り、そのオブジェクトインスタンスについて、リモートオブジェクトが CLASSPATH 内でローカルにクラス定義を検出できません。この種のリモートメソッド呼び出しを、図 4 に示します。クライアントによって送信されるオブジェクトのクラスは、宣言されたパラメータの型のサブタイプになります。サブタイプは、次のどちらかです。

上または下に説明してあるメソッドパラメータとしての、未知のサブタイプの引き渡しの説明

図 4:未知のサブタイプをメソッドのパラメータとして渡してリモートメソッド呼び出しを行う Java RMI クライアント

7. アプレットのコードベースと同様に、Remote クラス、非リモートクラス、およびほかの VM へのインタフェースをダウンロードするには、クライアントによって指定されたコードベースを使います。codebase プロパティがクライアントアプリケーション上で設定されている場合は、サブタイプのクラスがクライアントによってロードされるときに、コードベースにサブタイプインスタンスの注釈が付きます。コードベースがクライアント上で設定されていない場合は、リモートオブジェクトは誤って独自のコードベースを使ってしまいます。

5.0 コマンド行の例

アプレットの場合、このチュートリアルの最初の項で示した HTML の例のように、アプレットのコードベースの値は HTML ページに組み込まれます。

Java RMI コードベースの場合は、HTML ページにクラスへの参照を組み込むのではなく、クライアントはまず、Java RMI レジストリに問い合わせてリモートオブジェクトへの参照を要求します。リモートオブジェクトのコードベースは、既知の URL に関連した URL だけでなく任意の URL を参照できるので、Java RMI コードベースの値は、スタブクラス、およびスタブクラスが必要とするその他のクラスの位置に対する絶対的な URL でなければなりません。この codebase プロパティの値は、次のものを参照できます。

注: codebase プロパティの値をディレクトリの URL に設定する場合は、値の末尾に「/」を付ける必要があります。

ダウンロード可能なクラスの位置が、「webvector」という名前の HTTP サーバ上のディレクトリ「export」(Web ルートの直下) にある場合は、codebase プロパティの設定は次のようになります。

	-Djava.rmi.server.codebase=http://webvector/export/

ダウンロード可能なクラスの位置が、「webline」という名前の HTTP サーバ上のディレクトリ「public」(Web ルートの直下) 内の JAR ファイル「mystuff.jar」にある場合は、codebase プロパティの設定は次のようになります。

	-Djava.rmi.server.codebase=http://webline/public/mystuff.jar

ここで、ダウンロード可能なクラスの位置が「myStuff.jar」と「myOtherStuff.jar」の 2 つの JAR ファイルに分割されている場合を想定します。これらの JAR ファイルがそれぞれ「webfront」と「webwave」という別々のサーバ上にある場合は、codebase プロパティの設定は次のようになります。

	-Djava.rmi.server.codebase="http://webfront/myStuff.jar http://webwave/myOtherStuff.jar"

6.0 トラブルシューティングのヒント

Java RMI スタブを含むすべての直列化可能クラスは、Java RMI プログラムが正しく設定されていればダウンロードできます。スタブの動的なダウンロードが可能であるための条件を次に示します。

  1. スタブクラス、およびスタブが必要とするすべてのクラスがクライアントからアクセス可能な URL で提供されている

  2. bind または rebind への呼び出しを行うサーバプログラム (または、起動の場合は「セットアップ」プログラム) 上で、java.rmi.server.codebase プロパティが次のように設定されている

  3. rmiregistry が、スタブクラスまたはスタブクラスが必要とするクラスを CLASSPATH 内で検出できない。このため、サーバまたはセットアップコード内の bind または rebind への呼び出しの結果として、レジストリがスタブのクラスロードを行うときに、コードベースにスタブの注釈が付けられている

  4. スタブのダウンロードを可能にする SecurityManager がクライアントにインストールされている。つまり、Java 2 SDK, Standard Edition, v1.2 以降のバージョンでは、クライアントではセキュリティポリシーファイルが適切に設定されている必要がある

java.rmi.server.codebase に関連した一般的な問題が 2 つあります。 次にこれらの問題について説明します。

6.1 Java RMI サーバの実行で問題が発生する場合

まず最初に直面する可能性のある問題は、bind または rebind によってレジストリ内の名前にリモートオブジェクトをバインドしようとするときに、ClassNotFoundException が返されるというものです。通常、この例外は、codebase プロパティの設定が適切でないために、リモートオブジェクトのスタブまたはスタブが必要とするその他のクラスをレジストリが検索できないために発生します。

リモートオブジェクトのスタブは、リモートオブジェクト自体と同じインタフェースをすべて実装するため、メソッドのパラメータまたは戻り値として宣言されるほかのカスタムクラスと同様に、これらのインタフェースも指定されたコードベースからダウンロードできなければなりません。

この例外は、多くの場合、プロパティの URL に末尾のスラッシュを付けなかったためにスローされます。ほかにも、プロパティの値が URL ではない、URL に指定されたクラスへのパスが正しくないかスペルが間違っている、スタブクラスまたはほかに必要なクラスが指定された URL から利用できない、などの理由があります。

このような場合にスローされる例外を次に示します。

java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 
	java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
	java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
	java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub
java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(Compiled Code)
	at sun.rmi.transport.StreamRemoteCall.executeCall(Compiled Code)
	at sun.rmi.server.UnicastRef.invoke(Compiled Code)
	at sun.rmi.registry.RegistryImpl_Stub.rebind(Compiled Code)
	at java.rmi.Naming.rebind(Compiled Code)
	at examples.callback.MessageReceiverImpl.main(Compiled Code)
RemoteException occurred in server thread; nested exception is:
	java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 
	java.lang.ClassNotFoundException: examples.callback.MessageReceiverImpl_Stub

6.2 Java RMI クライアントの実行で問題が発生する場合

次に直面する可能性がある問題は、lookup によってレジストリ内でリモートオブジェクトを検索しようとするときに、ClassNotFoundException が返されるというものです。Java RMI クライアントコードを実行しようとした結果、スタックトレース内でこの例外が返された場合は、Java RMI レジストリが検索を開始した CLASSPATH に問題があります。6.0 項の条件 C を参照してください。この場合にスローされる例外を次に示します。

java.rmi.UnmarshalException: Return value class not found; nested exception is:
	java.lang.ClassNotFoundException: MyImpl_Stub
	at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:109
	at java.rmi.Naming.lookup(Naming.java:60)
	at RmiClient.main(MyClient.java:28)

その他のリソース

コードベースの問題が依然として解決できない場合は、まず、RMI ユーザのメーリングリストのアーカイブを参照してください。

RMI ユーザのメーリングリストに参加することができます。

このチュートリアルが有用な情報源となることを願っています。コメントや提案をお寄せください。その際は、タイトルを「codebase tutorial」として、rmi-comments@java.sun.com までお送りください。


Copyright © 2004 Sun Microsystems, Inc.All Rights Reserved.
コメントの送付先:rmi-comments@java.sun.com
Sun