替わりのオブジェクトをシリアライズするwriteReplace()/readResolve()
今回は「動的プロキシとシリアライズ」エントリで取り上げた通り,writeReplace()メソッドとreadResolve()メソッドの使い方について紹介してみようと思う。
あるオブジェクトをシリアライズしたくなったとする。しかし,時として,そのオブジェクトをそのままシリアライズすることができない理由が出てきたりする。そのままシリアライズしてしまうとサイズが非常に大きくなってしまう,などがすぐに思い付く代表例だろう。その場合,シリアライズしたい情報を自分で決定し,さらに情報を加工するなどして,自前の形で永続化を行いたくなる。
そのようなニーズに応えるのが,writeReplace()/readResolve()メソッドの機構である。これは,シリアライズ時およびデシリアライズ時に自動的に呼び出されるメソッドであり,これらのメソッドを使うことによって,シリアライズの際に対象のオブジェクトそのものではなく,別のクラスのオブジェクトをシリアライズ対象にすり替えてしまう,という芸当が可能になる。
writeReplace()/readResolve()メソッドは,特に何らかのインタフェースで規定されたメソッドではないので,以下のシグネチャを持つメソッドを対象のクラスに定義してあげれば良い。もちろん,Serializableインタフェースが実装されたクラスが対象なことは言うまでもない。
実際に例を取り上げよう。まず,以下のようなクラスがあったとする。
valueプロパティを持つ簡単なJavaBeansクラスである。さて,このクラスをTargetクラス以外のクラスのオブジェクトとして,わざわざシリアライズさせてみよう。以下が,替わりとなるオブジェクトのクラスである。
永続化したいvalueフィールドの値を保持すると同時に,クラス名を保持するようにしている。Targetクラスのオブジェクトとしてシリアライズする替わりに,SerializedObjectクラスのオブジェクトをシリアライズするためには,Targetクラスに以下のメソッドを追加すれば良い。
SerializedObjectクラスのインスタンスを生成し,value値とクラス名を引数に与えて,その結果を返却している。これにより,以下のようにシリアライズを行った際にwriteReplace()メソッドが呼び出されて,返却したSerializedObjectオブジェクトがシリアライズされる。
さて,実はこれでは”./test.ser”の内容をデシリアライズする際に失敗してしまう。替わりのオブジェクトを永続化したことに対応して,ちゃんと復元ができるようにSerializedObjectクラスに処理を記述してあげないといけない。それが,readResolve()メソッドである。SerializedObjectクラスに,以下のようにメソッドを追加する(本来は例外処理が必要だが,ここでは割愛)。
これにより,SerializedObjectオブジェクトがデシリアライズされる際にreadResolve()メソッドが自動的に呼び出される。そして,保持しておいたクラス名よりインスタンスを生成し(Targetクラスで明示的にキャストしちゃってるので意味はないのだが),保持しておいたvalue値を生成したインスタンスにセットしてあげて,そのインスタンスをデシリアライズ結果として返却している。
以下のように”./test.ser”ファイルの内容を使ってデシリアライズしてあげることで,readResolve()メソッドが呼び出され,元々のTargetオブジェクトと同じ内容のインスタンスを復元することができる。
今回取り上げた例では,writeReplace()/readResolve()メソッドによってもたらされる恩恵が伝わりにくいかも知れない。次回は「Javassistを使った動的プロキシの作成」エントリの内容も踏まえて,動的プロキシとシリアライズについての解説を行う予定である。


