バイトコードライブラリとJUnitとメモリリーク その2

前回の日記で話していた、「HibernateのByteCodeProviderがエンハンスしたクラスをシステムクラスローダーに登録してしまう為、JUnitで繰り返し初期化してテストしているとメモリリークが起こってしまう」という件、色々と対応を考えていました。
まずはJPAAPIを色々探して、何か対応できそうなものがないか調査。PersistenceUnitInfoにクラスローダを返すメソッドがあるのに気づきました。このインターフェイスはAPサーバ環境で初期化をする為のものですが、その際にPersistenceProvider側にクラスローダを渡せるみたいです。早速、persistence.xmlからPersistenceUnitInfoを作成するクラスを適当に作成して実験・・・しかし結果は変わらず。PersistenceUnitInfoを使うと、確かにHibernateは初期化時に渡されたクラスローダをコンテキストクラスローダにセットするのですが・・・肝心のByteCodeProviderは関係なく元クラスからクラスローダを取得しているので、同じ結果にしかなりませんでした。
であれば、やはりクラスローダをいじるしかなさそう・・・そこで、persistence.xmlが存在するURLのルートをコンストラクタに渡すURLClassLoaderを作成。loadClassメソッドをオーバーライドして、先にfindClassを実行して、見つからなかったときだけ親クラスローダに委譲するようにしてみました。こうして作成したクラスローダを、TestCase上からコンテキストクラスローダに設定してあげて、テスト実行。こうしてやれば、ByteCodeエンハンスされるクラスはシステムクラスローダにはロードされない筈・・・
結果は失敗orz。URLクラスローダを使ってるのがいけないみたいで、HibernateがByteCodeエンハンスをしてくれません。
結局、この現象が起こっている大元である、JavassistのProxyFactoryに手を入れることにしました。JBossCVSからプロジェクトを取得して、ProxyFactoryのgetClassLoaderを最初にコンテキストクラスローダを見に行くように修正。ビルドして試してみると・・・ようやく成功。javassistによってエンハンスされたクラスが、無事にコンテキストクラスローダでロードされるようになりました。小規模ですが複数テストで実行してもメモリは増えてないみたいだし、これで取りあえず解決かな?
Javassistのこのクラスが、コンテキストクラスローダを利用しない理由って何だろう? 実は現在のソースには、コンテキストクラスローダを取得する処理がコメントアウトされてあります。使うと何か問題が起こったりするのかな? でも、それってJUnitメモリリークすることよりも大きな問題なんだろうか??・・・ただ、このメソッドはprotectedなので本来なら簡単にオーバーライド出来る筈なんですけど、何せHibernate側の呼び出す処理が硬直的すぎて・・・HIbernateの初期化処理って、とにかくロジックが長いのと処理の分離がされてないので、カスタマイズが非常に面倒です。ByteCodeProviderをクラス名で指定する方法を提供するだけで簡単にカスタマイズ出来るのに、全部ソース上にnewでインスタンス作成して、カスタマイズの余地が無いのは如何なものかと・・・
ところで、今回の調査でJPAのPersistenceUnitInfoやClassTransformerインターフェイスを色々いじってみたのですが、これってどうやらTopLinkのByteCode操作方法に関係してるみたいですね。TopLinkはJavaSE環境ではjavaagentを使ってByteCode操作を行います。これをAPサーバ環境で実行する為にこれらのインターフェイスを用意して、そしてGlassFish上にはこのインターフェイスを利用するクラスローダが実装されてありました。JBossの方はどうなんだろうと思って調べてみたのですが・・・ClassTransformerを受け取ったときの処理は、まだ実装されてありませんでした。
javaagentを使うと、元からクラスがエンハンスされた状態で使えるようになるので、使い方によっては面白いことができそうな気がします。ただ、メインメソッド実行時にオプション指定しないと使えない・・・というのがやはりネックですね。もうちょっと簡単に使えたらいいのに。