動的プロキシが循環参照していた際のシリアライズ問題

S2Wicketの新仕様を実装する際に問題となった,動的プロキシのシリアライズ問題だが,ここで直面した問題を整理したいと思う。そのために,問題領域に限定したコードを作成したので,興味のある方,解決策を知ってるぜ!という方は,ぜひ ここからプロジェクトをダウンロードして欲しい。 では,問題の簡単な説明をしていこう。まず,SwingやSWTなどのコンポーネント関連のクラスでよく見られるコンポジットなクラス構造を想像する。

serialize-test1.jpg Componentクラスはparentフィールドに他のComponentを保持することができ,parentフィールドは親のコンポーネントを表している。Formクラスは子となるComponentクラスを保持することができる。本来であれば複数のコンポーネントを持つことができるようにすべきだが,コード上ではchildフィールド1つとしている。 全てのクラスは,abstractなクラスであり,そのままではインスタンス化ができない。特にFormクラスとComponentクラスを継承したLinkクラスには,onで始まるイベントハンドラメソッドが抽象メソッドとして定義されていて,サブクラスでオーバーライドすることにより,イベント処理を実装することを想定している。 普通であれば,匿名クラスなどのテクニックを使って,各コンポーネントをインスタンス化する際に同時に抽象メソッドを実装するだろう。しかし,今回はProxyFactory#create()メソッドの中で,Javassistを使って動的プロキシクラスを生成し,その結果を元にインスタンスを生成している。動的プロキシクラスは,イベントハンドラ抽象メソッドを実装し,さらにwriteReplace()メソッドを実装している。writeReplace()メソッドの中で,シリアライズ時にSerializedProxyオブジェクトを代わりにシリアライズするようにしている。 SerializeTestテストケースクラスの中で,FormクラスとLinkクラスのインスタンスを生成し,お互いを参照関係にして,シリアライズを行っている。その際,どちらも動的プロキシであり,writeReplace()メソッドを実装させてあるので,シリアライズ時にはSerializedProxyオブジェクトがシリアライズされる。

serialize-test2.jpg さて,DesirializeTestテストケースクラスにおいて,シリアライズされたものを復元しているのだが,ここで問題が発生する。SerializedProxyクラスのreadResolve()メソッドが呼び出された際,SerializedProxyクラスの各種フィールドに保存しておいた情報が一切入っていない状態なのだ。

serialize-test3.jpg こうなると,オブジェクトを復元するための情報がないために,お手上げ状態となってしまう。SerializedProxyクラスはComponentクラスを継承しているために,復元されたオブジェクトグラフにSerializedProxyオブジェクトとしてそのままセットされ,呼び出し時に所定の型にダウンキャストした際にClassCastException例外が発生してしまう。 この動作は,SerializeTestをまず実行してシリアライズし,DeserializeTestを実行した際に再現させられる。いろいろと試してみたが,オブジェクトグラフを正しく復元させる方法を見つけることができていない。 気になっているのが,「Java オブジェクト直列化仕様」の以下のくだりである。

3- オブジェクト入力クラス

注 - オブジェクトが完全に構築されるまでは readResolve メソッドはオブジェクトに呼び出されないため、そのオブジェクトのオブジェクトグラフへの参照は、readResolve によって指定された新しいオブジェクトに更新されません。しかし、writeReplace メソッドによるオブジェクトの直列化の間に、置換オブジェクトのオブジェクトグラフにある元のオブジェクトへの参照はすべて、置換オブジェクトへの参照に置き換えられます。したがって、 直列化されたオブジェクトが置換オブジェクトを指定し、その置換オブジェクトのオブジェクトグラフが元のオブジェクトへの参照を持つ場合、直列化復元を行うと、不正なオブジェクトグラフが作成されます。

まさにこの状態に陥っているのか。。。orz 誰か,助けてください。

このエントリーをはてなブックマークに追加

関連記事

2023年のRemap

Remapにファームウェアビルド機能を追加しました

Google I/O 2023でのウェブ関連のトピック

2022年を振り返って

現在のRemapと今後のRemapについて