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


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

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

1.0 はじめに

Javaプラットフォームのもっとも重要な機能の1つとして、通常は異なる物理システム上で稼働する別のプロセスで実行中の仮想マシン(VM)に、Uniform Resource Locator (URL)からJava Softwareを動的にダウンロードする機能があります。この結果リモート・システムは、そのシステムのディスク上にインストールされていないアプレットなどのプログラムを実行できます。このドキュメントの最初の数セクションでは、アプレットに関連したコード・ベースについて説明し、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に指示すると考えることができます。そうしておけば、「リモート可能」クラスを見つけることができます。

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内で検出されなかった場合は、クライアントはリモート・オブジェクトのコード・ベースからクラス定義を取得しようとします。
  4. クライアントはコード・ベースからクラス定義を要求します。クライアントが使うコード・ベースは、スタブ・クラスがレジストリによってロードされたときにスタブ・インスタンスに注釈として付けられたURLです。ステップ1では、エクスポートされるオブジェクトに注釈として付けられたスタブはそののち、名前にバインドされたJava RMIレジストリに登録されます。
  5. スタブ(およびスタブが必要とするすべてのクラス)のクラス定義がクライアントにダウンロードされます。
    注: ステップ4および5は、リモート・オブジェクトがJava RMIレジストリで(Java RMIレジストリに登録された)名前にバインドされたときに、レジストリがリモート・オブジェクト・クラスをロードするためにとった手順と同じです。レジストリがリモート・オブジェクトのスタブ・クラスをロードしようとしたときは、レジストリはそのリモート・オブジェクトに関連付けられているコード・ベースからクラス定義を要求しました。
  6. クライアントはこれで、リモート・オブジェクト上のリモート・メソッドの呼出しに必要なすべての情報を入手しました。スタブ・インスタンスが、サーバー上に存在するリモート・オブジェクトに対してプロキシとして動作します。このため、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プロパティが次のように設定されている
    • codebaseプロパティの値が手順AのURLである

      および

    • codebaseプロパティの値として指定されるURLがディレクトリの場合、末尾が「/」である

  3. rmiregistryが、スタブ・クラスまたはスタブ・クラスが必要とするクラスをCLASSPATH内で検出できない。このため、サーバーまたはセットアップ・コード内のbindまたはrebindへの呼出しの結果として、レジストリがスタブのクラス・ロードを行うときに、コード・ベースにスタブの注釈が付けられている。
  4. スタブのダウンロードを可能にするSecurityManagerがクライアントにインストールされている。つまり、クライアントでセキュリティ・ポリシー・ファイルが適切に構成されている必要がある。

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)


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.