2006/10/31

[Java]serialize and nested-class mix like oil and water

java.io.Serializableをimplementsする場合、serialVersionUIDの宣言を義務付けた方がいい。

serialVersionUIDは、JDKのソースなんかを見るとよく宣言されているフィールド。 private static final long serialVersionUID = 123456789L; どこからも使われていないのでパッと見は不要な宣言なんだけど、これはJavaのserializeの機構が、互換性の検証のために利用している。(ベリファイアも絡んでるのかな?)
serialVersionUIDを宣言していないときは、実行時に自動的に計算される。

Javaのオブジェクトをデシリアライズするときは、復元しようとしているオブジェクトと、システム内に存在しているクラスの間に互換性があるかどうかを調べる必要がある。クラスの構成からハッシュ値を計算して、その値が等しいかどうかで、復元しようとしたオブジェクトと、システム内にあるclassに互換性があるかどうかを判定している。

何で自動計算任せだと問題があるかというと、ポイントは以下の2点。

  • ハッシュ値は、クラス名・インターフェース名・フィールド・メソッド から計算される
  • ネストしたクラスを使用していると、コンパイラによって値が変わることがある

メソッド名などで計算されるので、後でメソッドを追加したりすると以前に出力したバイナリが使用できなくなったりする。DBにインスタンスを保存しているような場合には致命的だ。テストの最終段階や、下手すると本番環境で突然落ちることになってしまう。

ネストしたクラスの方の現象は、privateなフィールドやメソッドの参照で起きる。曲者なのが、プライベートメンバへのアクセス時にコンパイラが生成するメソッド名が、コンパイラの裁量に任されている点。(ネストしたクラスがコンパイラとVMでどうやって実現されているのかは、ネストしたクラスを参照。)ビルドする環境が違うと、メソッド名(と、参照を保持するためのフィールド名)が変わる。メソッド名が変わると、ハッシュ値が変わってしまう。

最初は「必要になってからで良いかな」と思った。JDKについてる仕様を見ると、serialVersionUIDを宣言するのは次版からでもいいことになっているし。
開発用のマシンからテスト用のサーバにあるEJBを呼び出そうとすると、「クラスに互換性がない」なんていわれて、困ったことがある。調べてみると、DTOに内部クラスを使った構成が含まれていて、開発環境のEclipseとサーバで自動ビルドされたクラスで相違が出てしまったらしい。
#「んなことすんなよ」といわれてしまうとその通りなんだけど。

ビルドする人間が別のマシンに配置するファイルを別個にビルドしてしまうかもしれない。
バージョンアップ一つを見ても、本番環境にあるclassファイルがないとserialVersionUIDに書くべき値が調べられない。しかも、対象になったクラスをロードするのに必要なCLASSPATHなんかも分かっていないと、serialverコマンドは使えない。

この手間とリスクを考えると、serialVersionUIDの宣言を必須にし、宣言の仕方を規約にでも書いておいた方がいい

ラベル:

0 件のコメント:

コメントを投稿

登録 コメントの投稿 [Atom]

<< ホーム