cypher256's blog

Pleiades とか作った

AOP 翻訳コンテナーの仕組み

AOP による翻訳を実現するにあたり、以下の要件を満たしたいと思いました。

  1. 元のソースコードは一切変更しない(特定の DI コンテナなどに依存するファクトリーは使わない)
  2. 元のクラスファイルは一切変更しない(静的 AOP は使わない)
  3. static や private メソッド、final クラス、static イニシャライザに対して AOP を適用できる
  4. JointPoint? の指定にクラスやメソッド名だけでなく、呼び出し元や呼び出しスタックによる除外、適用を制御できる(これは後付け)

AOP を実現する高レベルな仕組みとして Spring、Seasar2、JBossAOP、AspectJ(+=AspectWerkz)などがありますが、まず Spring、Seasar2AspectJ は一番重要視した 1 を含めほとんどの要件を満たせません。次に JBossAOP や AspectWerkz はクラスロード時にウィービング可能で機能的にも AOP Alliance に縛られることもなく強力で理想に近かったのですが、独自クラスローダーを使用するため、EclipseClassLoader との兼ね合いに不安がありました。

そういう経緯があり、それらの問題を解消したコンテナが Pleiades です。クラスローダーのフックとしては Java5 で追加された javaagent 機能を利用し、AOP には直接バイトコード操作を行う API を使用するようにしています。バイトコード操作ライブラリとしては Javassist、cglib、ASM、BCEL、SERP などがありますが、高パフォーマンス、高レベル、高い実績を考慮し、Javassist を選びました。cglib は高レベルで最も美しかったのですが、バイトコード操作というより基本はロード後の操作なので要件を満たせませんでした。

結果的に出来上がったのは、作成者の意図に一切依存せず実行時に何でも勝手に変更し放題の AOP コンテナです。調子こいて言うと、Pleiades 上で Eclipse が動いています。


ちょっと関係ないことですが、AOP も DI もコンテナ実装の観点から見るとそんなに変わりません。上記の 1 を満たす DIxAOP コンテナがあってもいいと思うのですがどうなんでしょう。既存の DI コンテナは各フレームワークを容易に接続するといっても、DI コンテナの存在を意識していない各フレームワーク内でのオブジェクト生成とライフサイクルを制御するには、そのフレームワーク自体を継承するなりして静的に拡張しなくてはなりません。いや拡張しても良いのですが、各フレームワークがバージョンアップしたときの対応がアホくさいと思うのです。new している部分をコンテナが動的に制御すれば、そんなことをする必要はありません。ソースコード上ではオブジェクトはやっぱり基本はちゃんと自分で new して、ファクトリーが必要なところはちゃんと設計すると。new してしまうと想定外の問題が発生したときに差し替えできない、という問題はクラスロード時に書き換えるコンテナであれば new AService() を new BService() に実行時に置き換え、テストの場合は new AServiceTest?() に実行時に置き換えるだけです。new されたオブジェクトに対してはコンテナが singleton か prototype を管理し、デフォルトはもちろん普通に prototype、というか管理しない。現在の DI コンテナが、ステートレスな設計があたかも正しいように傾倒するのはコンテナ作成側の都合でしかないのかなーと。

つまり、DI されることを基本にした設計はどうなんだろうと思うのです。拡張が要求される部分はちゃんと設計して、インターフェースは必要な部分のみちゃんと設計する。外部からしかインジェクションできない現在のコンテナは結局、将来拡張もしないかもしれない部分を DI させ、DI しなかった部分はインスタンスの差し替えができません。テスト時や想定外のインスタンスの差し替えや拡張に対しては、外側からインジェクションするのではなく、なんでも変更し放題のコンテナが必要なときに内側からインジェクションする。コードは普通に作りましょうと。Eclipse の実装のように。