WeldのProxyクラスはどこからやってくるか
CDIの参照実装 Weld では @Dependent を除いて、全てのCDI管理Beanは直接インスタンスがインジェクションされずに、以下のようにプロキシが設定*1される。
test.Message$Proxy$_$$_WeldClientProxy
例外発生時のスタックトレースや、スレッドダンプでも見かける $Proxy$_$$_WeldClientProxy
だが、どこからやってきているのか調べてみた。
疑問に思っていた事
Javaで一般的にプロキシといったら、以下のような手段が取られると思う。
JDK付属プロキシでは、インタフェースに対してプロキシを設定するため、CDIのようにインタフェースなしのクラスに対してプロキシを設定できず、利用できない。また、Weldのpom.xmlを見ても、objenesisやcglibへの依存が見られないため、ライブラリも使っていないようである。
WeldのProxyクラスは一体どこからやってくるのか?
WeldのProxyの生成はどうやっているか
org.jboss.weld.bean.proxy.ProxyFactoryで、プロキシの生成は行われている。
大まかな処理の流れは以下の通り。
- ProxyFactory.create(BeanInstance beanInstance)
- ProxyFactory.run()
- ProxyFactory.getProxyClass()
- ProxyFactory.getProxyClass()
- 対象のプロキシクラスが既にクラスローダにロード済みであれば、newInstanceメソッドで生成。
- 未ロードされていない場合は、createProxyClass()を呼び出して、動的にプロキシクラスの生成とロードをする
- ProxyFactory.createProxyClass()
- Weldとは別モジュールのjboss-classwriterに含まれる、org.jboss.classfilewriter.ClassFileを使って、動的にバイトコード(クラスファイル)を生成。
- org.jboss.weld.util.bytecode.ClassFileUtils.toClassメソッドによって、動的に生成したバイトコードを、ClassLoader.defineClassメソッドをリフレクションで呼び出してクラスロード。
Proxyの生成に関連するところを太字にしたが、Weldは外部OSSライブラリに頼らずに、JBossの関連モジュールのみで、プロキシクラスの動的なバイトコード生成と、クラスローダ経由でのロードを行っている。
org.jboss.classfilewriter.ClassFileを見ると、以下のようにクラスファイル先頭に出てくるCAFEBABE
の出力が見られ、バイトコード作ってる感がある。
205 stream.writeInt(0xCAFEBABE);// magic 206 stream.writeInt(version); 207 constPool.write(stream);
Weldの場合、バイトコードの生成とクラスロードが別々のクラスに分かれているが、やっていることはobjenesisのClassDefinitionUtilsクラスと似たように見える。objenesisでも動的なバイトコード生成と、ClassLoader.defineClassメソッドの呼び出しを行っている。
まとめ
*1:Bean間の循環参照を許容するため、異なるスコープを持ったBeanの参照依存関係を分離するため