15-1 オブジェクトの転送と Serializability


・ RMIは別の VM上のオブジェクトのメソッドを呼び出すという機構です。 Javaのメソッドは一般に引数や返値にオブジェクトをやりとりします。 したがってプログラムの記述の上では非常に自然な形で オブジェクトの転送が実現されることになります。 分散オブジェクトのメソッドに引数を与えればオブジェクトを送り出したことになり、 メソッドの返値は受け取ったオブジェクトになります。 しかし本来 Javaのオブジェクトの実体は個々の VM内に存在するものですから、 RMIによってやり取りされるオブジェクトは、なんらかの仕組みを通じて VM間を移動していることになります。 このような RMIにおけるオブジェクト転送のルールについて少し調べてみましょう。
RMIではオブジェクトの転送は Serializability の機構を利用して実現されます。 Serializabilityとは、 簡単に言うと、オブジェクトの情報をストリームへのバイト列の情報に変換する 仕組みです。java.ioパッケージには ObjectOutputStream および ObjectInputStream という名前のクラスが用意されていました。 これらを利用して、たとえばファイル上に直接オブジェクトを「保存する」 あるいは「復元する」ようなことが可能でした。 ストリームがファイルではなく Socketに置き換われば、 ネットワークを通じてオブジェクトの情報を送り出したり、 受け取ったりすることができます。 RMIが利用しているのはまさにその機能なのです。
  Socketレベルでの通信は RMIの APIの内部に完全におおい隠されています。 したがって RMIのプログラムには直接 ObjectOutputStream や ObjectInputStream の記述は登場しません。 しかし Serializabilityが利用されていることは常に意識しておく必要があります。 なぜなら Serializabilityはすべてのオブジェクトに無条件で適用できるわけでは ないからです。RMIで転送されるオブジェクトも同様の制限を受けることになります。 また Serializeを受けたオブジェクトは、元のオブジェクトそのものではありません。 どの部分の情報が伝えられ、どの部分の情報は伝えられないという区別が存在します。 Serializabilityの機構によって転送されるオブジェクトの クラスMessageを設計してみましょう。

Messageクラスは通信のヘッダ情報(header)と内容(content)を表す 2個のフィールドとそれらにアクセスするためのメソッドを持ちます。 それぞれのフィールドは Stringのオブジェクトです。 重要な点は、このクラスが Serializableを実装しているということです。 java.io.Serializableは Serializabilityの対象となることを表すための インターフェイスでした。(実装すべきメソッドの定義は持ちません。) RMIでやりとりするオブジェクトは 必ず Serializableを継承している必要があります。
このクラスのオブジェクトがどのように Serializeされるのかを確かめるサンプル MessageSaverを紹介しましょう。

 ここではわかりやすいようにファイルへ書き出すことにしました。 結果は Message.ser という名前のファイルに保存します。 Serializeされたオブジェクトのデータはバイナリデータになるので、 そのままでは内容を見ることはできません。 エディタなどで内容を編集することもありません。 (部分を勝手に書き換えると「不正な」データと見なされます。) 通常はブラックボックスとして取り扱うべきなのですが、 UNIXの odコマンドなどで強引に内容を調べてみましょう。
 Serializeされたオブジェクトに保存されている情報は、 クラス名、スーパークラス名、各フィールドの名前と型、 およびその値であることがわかります。 しかし、その他のクラスに関する情報は含まれていないようです。 たとえばコンストラクタやメソッドの情報は見当たりません。
 この区別は次のように考えれば理解しやすいでしょう。 Serializeされる情報は個々のオブジェクト(インスタンス)ごとに異なるものに 限ります。 逆に言えば、 オブジェクトを生成するためのすべての情報がそこに存在するわけではないと いうことです。 クラスの「設計図」に当たる情報は Serializeの対象ではありません。 それらは各クラスのバイトコードをロードして別に手に入れる必要があるのです。
 したがって RMI もしくは RMIのように Serializabilityの機構によって オブジェクトを転送するシステムでは、その過程は厳密には以下のような手順になるわけです。

  1. 送り側がオブジェクトの情報を Serializeしてネットワークのストリームに送り出す
  2. 受け手側のVMは指定された名前のクラスの情報をバイトコードからロードする
  3. 受け手側のVMは Serializeされた情報を基にしてオブジェクトを生成し、 あたかも転送されてきたオブジェクトのように利用する

 転送する情報をSerializeされたものに限ることで、 ネットワーク上を行き来するデータの量は最低限ですみます。 この点は明らかに効率的な設計です。 ただしクラスの情報の入手については注意が必要です。 やりとりされるオブジェクトが JDKの標準のクラスライブラリに含まれている場合 (たとえば Stringクラス)は特に問題は生じません。 受け取り側がクラスのバイトコードをロードすることは確実に保証されているからです。 しかし、プログラマによって独自に定義されたクラス(たとえば上記の Message クラス)の場合には送り出す側と受け取る側の両方でクラスの情報を表す資源 (バイトコード)にアクセスできる必要があります。