WeldのProxyクラスはどこからやってくるか

CDIの参照実装 Weld では @Dependent を除いて、全てのCDI管理Beanは直接インスタンスがインジェクションされずに、以下のようにプロキシが設定*1される。

test.Message$Proxy$_$$_WeldClientProxy

例外発生時のスタックトレースや、スレッドダンプでも見かける $Proxy$_$$_WeldClientProxyだが、どこからやってきているのか調べてみた。

疑問に思っていた事

Javaで一般的にプロキシといったら、以下のような手段が取られると思う。

  • java.util.reflect.Proxy によるプロキシ実装
  • objenesiscglibなどのライブラリ

JDK付属プロキシでは、インタフェースに対してプロキシを設定するため、CDIのようにインタフェースなしのクラスに対してプロキシを設定できず、利用できない。また、Weldのpom.xmlを見ても、objenesisやcglibへの依存が見られないため、ライブラリも使っていないようである。

WeldのProxyクラスは一体どこからやってくるのか?

WeldのProxyの生成はどうやっているか

org.jboss.weld.bean.proxy.ProxyFactoryで、プロキシの生成は行われている。
大まかな処理の流れは以下の通り。

  1. ProxyFactory.create(BeanInstance beanInstance)
  2. ProxyFactory.run()
    • java.security.PrivilegedActionの実装。この後の処理でリフレクションAPIを実行するため、java.security.*によるアクセス制御対象になっている。
  3. ProxyFactory.getProxyClass()
  4. ProxyFactory.getProxyClass()
    • 対象のプロキシクラスが既にクラスローダにロード済みであれば、newInstanceメソッドで生成。
    • 未ロードされていない場合は、createProxyClass()を呼び出して、動的にプロキシクラスの生成とロードをする
  5. ProxyFactory.createProxyClass()

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メソッドの呼び出しを行っている。

まとめ

  • WeldのClientProxyは、$Proxy$_$$_WeldClientProxyを接尾子としたプロキシクラスのバイトコード生成とクラスロードをjava.util.reflect.Proxyや外部OSSライブラリを利用せずに独自に実装している。
  • org.jboss.weld.bean.proxy.ProxyFactoryから追いかけていくと流れがよくわかる。

*1:Bean間の循環参照を許容するため、異なるスコープを持ったBeanの参照依存関係を分離するため