RMI 入門


このチュートリアルでは、おなじみの Hello World プログラムの分散システム版を Java の RMI (Remote Method Invocation, リモートメソッド呼び出し) を使って作成する手順を説明します。このチュートリアルを学習するうちに、関連する多くの疑問に直面することでしょう。それらの解決方法については、RMI FAQ や、RMI ユーザのエイリアスの電子メールアーカイブで調べることができます。RMI ユーザの電子メールエイリアスを購読するには、ここをクリックしてください。

分散型の Hello World では、アプレットを使って、そのダウンロード元のサーバへリモートメソッド呼び出しを行い、メッセージ「Hello World!」を取得します。アプレットを実行すると、クライアントのブラウザに、「Hello World!」と表示されます。

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

  1. Java ソースファイルと HTML ファイルの記述
  2. クラスファイルと HTML ファイルのコンパイルおよび配置
  3. RMI レジストリ、サーバ、およびアプレットを起動する
このチュートリアルには、次のファイルが必要です。 :  以降のチュートリアルで、「リモートオブジェクトの実装」、「オブジェクトの実装」、および「実装」と言った場合、すべて、リモートインタフェースを実装するクラス examples.hello.HelloImpl を指します。

このチュートリアル内で使われているソースコードは、次のファイル形式から選ぶことができます。


Java ソースファイルと HTML ファイルの記述

Java では、絶対パスで指定したクラスのパッケージ名とそのクラスへのディレクトリパスとの間にマッピングが必要なので、Java コードを書き始める前に、パッケージとディレクトリの名前を特定する必要があります。このマッピングにより、Java コンパイラは、Java プログラム中に指定されたクラスファイルに関して、どのディレクトリを検索したらよいかを知ることができます。このチュートリアルのプログラムでは、パッケージ名は examples.hello、ソースディレクトリは $HOME/mysrc/examples/hello です。

Solaris 上でソースファイル用ディレクトリを作成するには、次のコマンドを実行します。

mkdir -p $HOME/mysrc/examples/hello Windows プラットフォームでは、目的のディレクトリに移動してから、次のように入力します。

mkdir mysrc
mkdir mysrc¥examples
mkdir mysrc¥examples¥hello

このセクションで行う作業は 3 つあります。
 

  1. リモートクラスの関数を Java インタフェースとして定義する
  2. 実装クラスおよびサーバクラスを記述する
  3. リモートサービスを利用するクライアントプログラムを記述する
 
リモートクラスの関数を Java インタフェースとして定義する
Java では、リモートオブジェクトは、リモートインタフェースを実装するクラスのインスタンスです。リモートインタフェースは、リモートで呼び出す各メソッドを宣言します。リモートインタフェースには、次のような特徴があります。 リモートメソッド呼び出しは、ローカルメソッド呼び出しとは異なる方法でエラーが発生します。これは、ネットワーク通信上の問題およびサーバ上の問題がエラーの原因となるためです。このため、リモートメソッドは、java.rmi.RemoteException をスローすることにより通信エラーを報告します。分散システム上のエラーおよび復元の詳細については、「A Note on Distributed Computing」を参照してください。

実装クラスおよびサーバクラスを記述する

リモートオブジェクトの実装クラスは、少くとも次の条件を備えていなければなりません。

この場合、「サーバ」クラスは、リモートオブジェクトの実装のインスタンスを生成し、そのインスタンスを rmiregistry 内の名前にバインドする main メソッドを持ちます。この main メソッドは、実装クラス内に含まれる場合も、まったく別のクラスに含まれる場合もあります。

この例では、main メソッドは、examples.hello.HelloImpl の一部です。サーバプログラムが行う事柄は、以下のとおりです。

HelloImpl.java: のソースをもとにして、上記の 6 つの各ステップを説明します。
package examples.hello; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.RMISecurityManager; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject     implements Hello {     public HelloImpl() throws RemoteException {         super();     }     public String sayHello() {         return  "Hello World!";     }     public static void main(String args[]) {

        // Create and install a security manager
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        try {
            HelloImpl obj = new HelloImpl();
            // Bind this object instance to the name "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();
        }
    }
}

リモートインタフェースを実装する
Java では、クラスがインタフェースの実装を宣言すると、クラスとコンパイラの間で契約が結ばれます。この契約によって、クラスは、実装するインタフェース内で宣言された各メソッドシグニチャーに対して、メソッドの本体または定義を提供することを約束します。インタフェースメソッドは、暗黙のうちに public および abstract に設定されているため、実装クラスが契約を果たさない場合、クラスは定義に基づき abstract になります。そのクラスが abstract として宣言されていない場合は、コンパイラによってそのことが指摘されます。

この例の実装クラスは、examples.hello.HelloImpl です。このクラスは、どのリモートインタフェースを実装するのかを宣言します。以下は、HelloImpl クラスの宣言です。

public class HelloImpl extends UnicastRemoteObject      implements Hello
実装クラスはリモートクラスを継承できます。この例では、リモートクラスは、java.rmi.server.UnicastRemoteObject です。UnicastRemoteObject を継承することにより、HelloImpl クラスから以下の機能を持つリモートオブジェクトを作成できます。 常時稼動するのではなく、クライアントの要求時に起動 (作成) できるリモートオブジェクトが必要な場合は、このチュートリアルを終了したあとで、「リモートオブジェクトの起動」を参照してください。また、RMI がデフォルトで使う TCP ソケットではなく、独自の通信プロトコルを使う方法については、「カスタム RMI ソケットファクトリの作成」で学ぶことができます。
 

リモートオブジェクトのコンストラクタを定義する
リモートクラスのコンストラクタが提供する機能は、リモートでないクラスのコンストラクタが提供する機能と同じで、新しく作成されたクラスのインスタンス変数を初期化して、そのコンストラクタを呼び出したプログラムにクラスのインスタンスを返します。

さらに、リモートオブジェクトのインスタンスは「エクスポート」される必要があります。リモートオブジェクトをエクスポートすると、そのオブジェクトは、匿名ポート上のリモートオブジェクトへの着信呼び出しを監視することによって、着信するリモートメソッド要求を受け入れることができるようになります。java.rmi.server.UnicastRemoteObject または java.rmi.activation.Activatable を継承すると、クラスは作成時に自動的にエクスポートされます。

UnicastRemoteObject または Activatable 以外のクラスからリモートオブジェクトを継承する場合は、クラスのコンストラクタ (または適切な別の初期化メソッド) から UnicastRemoteObject.exportObject メソッドまたは Activatable.exportObject メソッドを呼び出すことにより、明示的にリモートオブジェクトをエクスポートする必要があります。

オブジェクトのエクスポートは、java.rmi.RemoteException をスローする可能性があるため、コンストラクタがほかに何も行わない場合でも、RemoteException をスローするコンストラクタを定義する必要があります。コンストラクタを定義しなかった場合は、javac は、次のエラーメッセージを生成します。

HelloImpl.java:13: Exception java.rmi.RemoteException must be caught, or it must be declared in the throws clause of this method.
        super();
             ^
1 error
復習: リモートオブジェクトの実装クラスが行う必要のある事柄は、以下のとおりです。 以下に、examples.hello.HelloImpl クラスのコンストラクタを示します。
public HelloImpl() throws RemoteException {
        super();
}
次の点に注意してください。 スーパークラスの引数なしのコンストラクタ super() への呼び出しは、省略してもデフォルトで発生しますが、この例では Java VM がクラスの前にスーパークラスを構築することを明確にするために、この呼び出しを省略せずに含めてあります。

各リモートメソッドに実装を提供する
リモートオブジェクトの実装クラスは、リモートインタフェースで指定された各リモートメソッドを実装するコードを含みます。たとえば、次に sayHello メソッドの実装例を示します。この例では、呼び出し側に「Hello World!」という文字列が返されます。

public String sayHello() throws RemoteException {         return  "Hello World!"; }
リモートメソッドに渡す引数、またはリモートメソッドからの戻り値は、Java の持つどの変数型でも構いません。また、インタフェース java.io.Serializable を実装するオブジェクトであれば、どのオブジェクトであっても構いません。java.lang および java.util のほとんどのコア Java クラスは、Serializable インタフェースを実装しています。 クラスは、リモートインタフェースで指定されていないメソッドを定義できますが、これらのメソッドは、サービスを実行する Virtual Machine 内でしか呼び出すことはできず、リモートから呼び出すことはできません。

セキュリティマネージャを作成およびインストールする
サーバの main メソッドは、まず最初にセキュリティマネージャの作成とインストールを行う必要があります。セキュリティマネージャは、RMISecurityManager か、または独自に定義したものを使用します。以下に例を示します。

if (System.getSecurityManager() == null) {
    System.setSecurityManager(new RMISecurityManager());
}
セキュリティマネージャは、ロードされたクラスが許可されていない操作を行わないことを保証するもので、必ず実行されなければなりません。セキュリティマネージャが指定されない場合は、ローカルの CLASSPATH 内のクラス以外には、RMI クライアントまたはサーバによるクラスのロードは許可されません。

リモートオブジェクトの 1 つ以上のインスタンスを生成する
サーバの main メソッドは、サービスを提供するリモートオブジェクトの実装の、1 つ以上のインスタンスを生成する必要があります。次に例を示します。

HelloImpl obj = new HelloImpl();
コンストラクタはリモートオブジェクトをエクスポートします。これは、リモートオブジェクトが作成されると、そのリモートオブジェクトは着信する呼び出しを受け入れる準備ができることを意味します。

リモートオブジェクトを登録する
呼び出し側 (クライアント、ピア、またはアプレット) がリモートオブジェクトのメソッドを呼び出すには、呼び出し側はまずリモートオブジェクトへの参照を取得する必要があります。

ブートストラップ用に、RMI システムはリモートオブジェクトのレジストリを提供します。これにより、//host/objectname という URL 形式の名前をリモートオブジェクトにバインドできます。この URL 形式の名前の objectname の単なる文字列名です。

RMI レジストリは、単純なサーバ側のネームサーバです。RMI レジストリにより、リモートクライアントはリモートオブジェクトへの参照を取得できます。通常、RMI レジストリは、RMI クライアントが最初に通信するリモートオブジェクトの場所を特定するためだけに使われます。次に、RMI クライアントが最初に通信したオブジェクトが、ほかのオブジェクトを見つける上で必要なアプリケーション固有のサポートを行います。

たとえば、参照は、別のリモートメソッド呼び出しに渡すパラメータ、またはリモートメソッド呼び出しからの戻り値として取得できます。詳細は、「RMI にファクトリパターンを適用する」を参照してください。

いったん、リモートオブジェクトがサーバに登録されると、呼び出し側は、オブジェクトを名前で探して、リモートオブジェクトへの参照を得ることができ、そのオブジェクトのメソッドをリモートから呼び出せるようになります。

たとえば、次のコードは HelloServer という名前をそのリモートオブジェクトへの参照にバインドします。

Naming.rebind("//myhost/HelloServer", obj);
rebind メソッド呼び出しの引数については、次の点に注意してください。 セキュリティ上の理由により、アプリケーションがバインド、またはアンバインドできるのは、同一ホスト上で動作中のレジストリに対してだけです。この制限により、クライアントがサーバのリモートレジストリを削除したり上書きしたりすることはありません。ただし、ルックアップはどのホストからでも可能です。
リモートサービスを利用するクライアントプログラムを記述する

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


    package examples.hello;

    import java.applet.Applet;
    import java.awt.Graphics;
    import java.rmi.Naming;
    import java.rmi.RemoteException;

    public class HelloApplet extends Applet {

        String message = "blank";
     
        // "obj" is the identifier that we'll use to refer
        // to the remote object that implements the "Hello"
        // interface
        Hello obj = null;

        public void init() {
            try {
                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. アプレットは、最初にサーバホストの rmiregistry からリモートオブジェクトの実装 (HelloServer として公示されている) への参照を取得する。Naming.rebind メソッドは、Naming.lookup メソッドと同様に、URL 形式の java.lang.String をとる。この例では、アプレットは getCodeBase メソッドを getHost メソッドとともに使用して URL 文字列を構築する。Naming.lookup は、次の作業を行う
     
    • Naming.lookup への引数として提供されたホスト名およびポート番号を使って、レジストリスタブのインスタンスを構築し、サーバのレジストリに接続する
    • レジストリスタブから、URL の名前要素 (HelloServer) を使ってレジストリ上のリモート lookup メソッドを呼び出す
      • レジストリは、その名前にバインドされた HelloImpl_Stub のインスタンスを返す
      • リモートオブジェクトの実装 (HelloImpl) のスタブインスタンスを受け取り、CLASSPATH またはスタブのコードベースからスタブクラス (examples.hello.HelloImpl_Stub) をロードする
    • Naming.lookup は、呼び出し側 (HelloApplet) にスタブを返す
     
  2. アプレットは、サーバのリモートオブジェクトの sayHello メソッドを呼び出す
     
    • RMI は、応答文字列 Hello World! を直列化して返す
    • RMI は、直列化された文字列を復元し、message という名前の変数に格納する
       
  3. アプレットは、paint メソッドを呼び出す。この結果、文字列 Hello World! がアプレットの描画領域に表示される
Naming.lookup メソッドにパラメータとして渡される、構築された URL 文字列には、サーバのホスト名が含まれている必要があります。サーバのホスト名が含まれていないと、アプレットのルックアップはデフォルトでクライアントに対して実行されるため、アプレットはローカルシステムにアクセスできず、アプレットのホストとの通信だけに制限されます。このため、AppletSecurityManager は例外をスローします。

Hello World アプレットを参照する Web ページの HTML コードを次に示します。


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

The message from the HelloServer is:
<p>
<applet codebase="myclasses/"
        code="examples.hello.HelloApplet"
        width=500 height=120>
</applet>
</HTML>

次の点に注意してください。


クラスファイルと HTML ファイルをコンパイルおよび配置する

これで例題 Hello World のソースコードが完成しました。$HOME/mysrc/examples/hello ディレクトリには 4 つのファイルが存在しています。 この節では、.javaこの節では、.class ファイルを作成します。次に rmic コンパイラを実行してスタブとスケルトンを作成します。スタブとは、リモートオブジェクトのクライアント側のプロキシのことで、RMI 呼び出しをサーバ側のディスパッチャーに転送します。次に、ディスパッチャーが、呼び出しを実際のリモートオブジェクトの実装に転送します。

javacrmic コンパイラを使うときは、生成されるクラスファイルをどのディレクトリに置くかを指定しなければなりません。アプレットの場合には、すべてのファイルをアプレットのコードベースディレクトリに置く必要があります。この例では、コードベースディレクトリは $HOME/public_html/myclasses になります。

Web サーバの中には、http://host/‾username/ の形式で構成された HTTP URL 経由でユーザの public_html ディレクトリにアクセスできるものがあります。Web サーバがこの規約をサポートしていない場合、file://home/username/public_html の形式でファイル URL を使うことになります。

ここで行う作業は 4 つあります。

  1. Java ソースファイルをコンパイルする
  2. rmic を使ってスタブまたはスケルトンを生成する
  3. HTML ファイルを配置ディレクトリに移す
  4. ランタイムパスを設定する

    Java ソースファイルをコンパイルする

    コンパイルする前に、生成ファイルの配置ディレクトリ $HOME/public_html/myclasses および開発ディレクトリ $HOME/mysrc/examples/hello の両方に、開発マシン上のローカル CLASSPATH からアクセスできることを確認してください。

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

    javac -d $HOME/public_html/myclasses         Hello.java HelloImpl.java HelloApplet.java
    このコマンドは、ディレクトリ examples/hello をディレクトリ $HOME/public_html/myclasses 内に作成します (まだ存在していない場合)。次に、このコマンドは、examples/hello ディレクトリにファイル Hello.classHelloImpl.class、および HelloApplet.class を書き込みます。これらのファイルはそれぞれ、リモートインタフェース、実装、アプレットです。javac オプションの詳細は、Solaris 用 javac のマニュアルページまたは Win32 用 javac のマニュアルページを参照してください。

    rmic を使ってスタブまたはスケルトンを生成する

    スタブおよびスケルトンファイルを作成するには、my.package.MyImpl のようなリモートオブジェクトの実装を含んだ、コンパイルするクラスファイルの絶対パスによるパッケージ名を指定して、rmic を実行します。rmic コマンドは、引数に 1 つ以上のクラス名をとり、MyImpl_Skel.class および MyImpl_Stub.class の形式のクラスファイルを生成します。

    JDK 1.2 では、rmic はデフォルトで -vcompat フラグをオンにした状態で実行されます。生成されるスタブおよびスケルトンは、次のオブジェクトへのアクセスをサポートします。
     

      1. JDK 1.1 のクライアントからの Activatable でないユニキャストリモートオブジェクト
      2. JDK 1.2 のクライアントからのすべての型のリモートオブジェクト
         
    JDK 1.2 のクライアントだけをサポートする場合は、rmic-v1.2 オプションを指定して実行しても構いません。rmic オプションの詳細については、Solaris 用 rmic のマニュアルページまたは Win32 用 rmic のマニュアルページを参照してください。

    たとえば、HelloImpl リモートオブジェクトの実装のスタブおよびスケルトンを作成するには、次のように rmic を実行します。

    rmic -d $HOME/public_html/myclasses examples.hello.HelloImpl
    -d オプションは、コンパイルされたスタブおよびスケルトンクラスファイルが置かれるルートディレクトリを示します。このため、上のコマンドを実行すると、次のファイルがディレクトリ $HOME/public_html/myclasses/examples/hello に作成されます。
      生成されたスタブクラスは、リモートオブジェクト自体とまったく同じリモートインタフェースのセットを実装します。これは、クライアントが、キャストおよび型チェックに Java の組み込み演算子を使用できること、また、Java のリモートオブジェクトが真のオブジェクト指向多相性をサポートしていることを意味します。

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

    アプレットを参照する Web ページにクライアントがアクセスできるように、hello.html ファイルを開発ディレクトリからアプレットの codebase ディレクトリに移動する必要があります。たとえば次のコマンドを実行します。
     
    mv $HOME/mysrc/examples/hello/hello.html $HOME/public_html/

    ランタイムパスを設定する

    HelloImpl サーバの実行時に、サーバのローカル CLASSPATH から $HOME/public_html/codebase ディレクトリにアクセスできることを確認しておいてください。


RMI レジストリ、サーバ、およびアプレットを起動する

ここで行う作業は 3 つあります。
  1. RMI レジストリを起動する
  2. サーバを起動する
  3. アプレットを実行する

RMI レジストリを起動する

RMI レジストリは、単純なサーバ側のネームサーバで、これによりリモートクライアントは、リモートオブジェクトへの参照を取得できます。通常、RMI レジストリは、アプリケーションが最初に通信するリモートオブジェクトの場所を特定するためだけに使われます。次いで、アプリケーションが最初に通信したオブジェクトが、ほかのオブジェクトを見つける上で必要なアプリケーション固有のサポートを行います。

注:  rmiregistry を起動する前に、registry を実行するシェルとウィンドウのどちらにも、CLASSPATH が設定されていない、または設定されていても、クライアントにダウンロードするクラス (リモートオブジェクトの実装クラスのスタブを含む) へのパスが含まれていないことを確認してください。

rmiregistry を起動し、rmiregistry が CLASSPATH 内にスタブクラスを見つけた場合は、サーバの java.rmi.server.codebase プロパティは無視されます。その結果、クライアントは、そのリモートオブジェクトのスタブコードをダウンロードできません。

サーバ上でレジストリを開始するには、rmiregistry コマンドを実行します。このコマンドは、何も出力せず、通常はバックグラウンドで実行されます。rmiregistry の詳細については、Solaris 用 rmiregistry のマニュアルページまたは Win32 用 rmiregistry のマニュアルページを参照してください。

たとえば、Solaris 上では次のようになります。

rmiregistry &

たとえば、Windows 95 または Windows NT 上では次のようになります。

start rmiregistry
(start コマンドが使えない場合は javaw を使う)

デフォルトでは、レジストリはポート番号 1099 で実行されます。別のポート上でレジストリを実行するには、コマンド行でポート番号を指定します。たとえば、Windows NT システム上のポート 2001 でレジストリを起動するには、次のようにします。

start rmiregistry 2001
デフォルト以外のポートでレジストリを実行している場合は、レジストリを呼び出すときに、java.rmi.Naming クラスの URL ベースのメソッドに渡す名前の中でポート番号を指定する必要があります。たとえば、例題 Hello World で、レジストリをポート番号 2001 で実行する場合、HelloServer の URL をリモートオブジェクト参照にバインドする次の呼び出しが必要になります。
Naming.rebind("//myhost:2001/HelloServer", obj);
リモートインタフェースを変更したり、変更または追加されたリモートインタフェースをリモートオブジェクトの実装で使用する場合は、必ずレジストリをいったん停止してから再起動する必要があります。そうしないと、レジストリでバインドされたオブジェクト参照の型と修正されたクラスとが一致しなくなります。
 
 

サーバを起動する

サーバの起動時に、スタブクラスがレジストリ、次いでクライアントに動的にダウンロードされるように、java.rmi.server.codebase プロパティが指定されている必要があります。コードベースプロパティを実装スタブの位置に設定して、サーバを実行します。コードベースプロパティは、単一のディレクトリしか参照できないので、ダウンロードする可能性のあるほかのクラスも、java.rmi.server.codebase が参照するディレクトリにインストールしておいてください。

java.rmi.server プロパティの説明は、ここをクリックしてください。利用可能なすべての java.rmi.activation プロパティの詳細は、ここをクリックしてください。java オプションの詳細は、Solaris 用 java のマニュアルページまたは Win32 用 java マニュアルページを参照してください。サンプルコードの実行に問題がある場合は、RMI とオブジェクト直列化の FAQ を参照してください。

注: スタブクラスは、クラスがまだローカルで利用できない場合、および java.rmi.server.codebase プロパティがサーバ上のクラスファイルの位置に適切に設定されている場合だけ、クライアントの Virtual Machine に動的にダウンロードされます。

java コマンド、プロパティの「名前」=「値」の 2 つのペア (codebase プロパティには「-D」から最後の「/」まで空白文字がないことに注意)、および絶対パスで指定されたサーバプログラムのパッケージ名の 4 つをこの順序で同じコマンド行に記述する必要があります。 空白文字は、java という語の直後に 1 つと、2 つのプロパティの間に 1 つと、(ブラウザまたは紙上では見にくいのですが) examples という語の直前に 1 つ必要です。次のコマンドは、java.rmi.server.codebase および java.security.policy プロパティを指定して、HelloImpl サーバを開始する方法を示します。

java -Djava.rmi.server.codebase=http://myhost/‾myusrname/myclasses/      -Djava.security.policy=$HOME/mysrc/policy   examples.hello.HelloImpl

このコードをシステム上で実行するには、policy ファイルの位置を、サンプルソースコードをインストールしたシステム上のディレクトリの位置に変更する必要があります。 : ここでは、例を単純にするために、任意の場所にいる任意のユーザにグローバルなアクセス権を与える policy ファイルを使っています。このポリシーファイルは、実際の開発環境では使わないでください。java.security.policy ファイルを使ってアクセス権を適切に指定する方法については、次のドキュメントを参照してください。
    http://java.sun.com/products/jdk/1.2/docs/guide/security/PolicyFiles.html
    http://java.sun.com/products/jdk/1.2/docs/guide/security/permissions.html

コードベースプロパティは、URL として解釈処理されます。このため、プロパティは「http://aHost/somesource/」か「file:/myDirectory/location/」の形式、またはオペレーティングシステムによっては file:///myDirectory/location/ ("file:" のあとにスラッシュが 3 つ) の形式で指定する必要があります。
 
これらのサンプル URL の各文字列には、末尾に「/」があることに注意してください。実装がクラス定義を適切に解釈処理 (検索) するためには、java.rmi.server.codebase プロパティで指定する URL に末尾のスラッシュを記述する必要があります。
 
codebase プロパティ上の末尾のスラッシュを忘れたり、ソースファイルで指定された位置にクラスファイルが見つからない (ダウンロード可能でない) 場合、またはプロパティ名を間違って入力した場合は、java.lang.ClassNotFoundException がスローされます。この例外は、リモートオブジェクトを rmiregistry にバインドしようとした場合、または最初のクライアントがそのオブジェクトのスタブにアクセスしようとした場合にスローされます。後者の場合は、rmiregistry が CLASSPATH 内でスタブを検索したため、別の点にも問題があります。

出力は、次のようになります。

HelloServer bound in registry
 

アプレットを実行する

レジストリおよびサーバがいったん実行されると、アプレットの実行が可能になります。アプレットの実行は、ブラウザに Web ページをロードするか、または次に示すように appletviewer によって行います。
appletviewer http://myhost/‾myusrname/hello.html &
appletviewer を実行すると、次のような出力が画面に表示されます。



Copyright © 1996, 1997, 1998 Sun Microsystems, Inc. All rights reserved.