IL EmitによるAOP (2)

.NET上でRealProxyを利用する以外に私が考えられるAOPの実現方法はあと二つあります。

現在の.NETプラットホームで可能なAOP実装のまとめより

  • Annotation 方式
  • TypeEmission 方式

よりシンプルに、RealProxy方式に負けず劣らず簡単に使用できることを考えると悩むところです。Seasar2本家の実装はAspectの編み込みにはJavassistを利用したJavaバイトコード書換えを行っており、それを手本とするならば一番近いのはTypeEmission方式です。この方式に関しては日記を書いた時も以下のようにまとめており

実は.NETにおけるAOP実装を調べた中で最も多かったタイプの方式です。対象の型の情報を元に.NETアセンブリ、モジュール、タイプをオンザフライで生成すると共にフィールド、メソッド等実装の全てをMSILの形式で出力するため本来の型の実装とAspectを編みこむことが可能になっています。しかし中間言語であるMSILの生成は特殊なOPCODEをストリームに出力していくので非常に分かり難くなりがち(少なくとも私には解り難い)です。また、MSILをダイレクトに出力すると柔軟性に欠けるためそれを補うためにクラス設計にコンポジション等を採用したり等、実装が大規模になりがちです。

当初は避けていた方式なのですが良いサンプルが見つかり、その内容を理解、把握できたこと、Annotation 方式ではパフォーマンスヒットが大きいであろうことを予想してTypeEmission方式を使って実現してみることにしました。

話が解り易いように私の書いているソースコードを全て公開すれば良いのですが残念ですが私の仕事の関係上それは無理ですのでここを見ている方が解り易いようにS2.NETの実装をベースに解説をしていこうと思います。

S2.NET(Seasar2ベース)のDIコンテナにおけるAOPを実現する為に必要な機能の概要はおおよそは以下の通りです。

1.定義されたアスペクト、アドバイスからそれぞれのオブジェクトを生成する
2.アスペクトの適用対象となる型の全てのメッセージをインターセプトする
3.メッセージをインターセプトした際にアスペクトのアドバイス、ポイントカットを評価する
4.アドバイス自身を実行する
5.必要であれば元々の型のメッセージを実行する

S2.NET(Seasar2)ではこれらの機能を実現しているのは"S2.NET.Framework.Aop"ネームスペースのクラス群ですが特に重要なのはAopProxyクラスです。このクラスはRealProxyから直接派生しており上記機能の2〜5を担っています。

  • IL EmissionタイプのDynamicProxy

S2.NETAOPの実装の概要が解った所で次は上記の機能をRealProxyではなくILの動的な出力による拡張された型の生成により実現する方法を考えてみます。
いろいろと解説しながら話をすることもできるのですがいきなり本題を見てもらったほうが.NETに詳しい方には早いと思うのでとりあえず以下をどうぞ。

Dynamic Proxy Creation Using C# Emit
(..結局は自分でスクラッチする前に良いものを見つけてしまった訳で...相変わらずへたれな私です....)

この記事中で紹介されているProxyFactoryクラスは任意の型の全てのメソッド呼び出しをインターセプトする機会を得るために拡張された動的な型を生成するファクトリです。
このクラスのファクトリメソッドの代表的なシグネチャは以下の通りです。

public Object Create( IProxyInvocationHandler handler, Type objType )

第一パラメタであるIProxyInvocationHandlerインタフェースは以下のようにただ一つのメソッドが定義されており

public interface IProxyInvocationHandler
{
    Object Invoke( Object proxy, MethodInfo method, Object[] parameters );
}

対象の型のメソッド呼び出しを実行する前に必ずこのメソッドが呼び出されるように新たな型が生成されるってのがミソです。つまりは型を拡張すると言っても継承した型を作る訳ではなく全てはIProxyInvocationHandlerへの委譲によってメソッドインターセプトが実現されている訳です。当初はILで型を生成する際には元々の型を完全に継承する必要があると思ってこりゃ大変だと思ったのですがAOP適用の対象を任意のインタフェースと割り切れば継承した型にする必要は全くありません。

このファクトリにより生成された型は通常では物理的なアセンブリとしては出力されないので中身をIldasmや.NET Reflectorで見る事ができませんがProxyFactory#CreateTypeメソッドのコードを以下の様に書換えることでアセンブリを物理的に出力して内容を確認することができるようになります。生成されたILの実装を覗くことは通常はデバッグ時にしか行わないと思うので条件コンパイルの対象にしておくと良いでしょう。

        //現在のドメイン上でプロキシの為の新たなアセンブリを生成する
#if DEBUG
        AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly( 
            assemblyName, AssemblyBuilderAccess.RunAndSave);//デバッグ時は保存する
#else
        AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly( 
            assemblyName, AssemblyBuilderAccess.Run );
#endif

        //新たなモジュールを生成する
#if DEBUG
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(MODULE_NAME, ASSEMBLY_NAME + ".dll", true); 
#else
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(MODULE_NAME); 
#endif

〜

//リターンする直前に以下のコードを追加
#if DEBUG
       assemblyBuilder.Save(ASSEMBLY_NAME+".dll"); //デバッグ時はアセンブリをカレントパスに出力する
#endif

次回はこのクラスで生成された型がどんな構造を持つのかを書く予定です。