はじめに
ようやく一般化しつつある感のあるAOP(Aspect Oriented Program:アスペクト指向プログラム)ですが、AspectJや、JBossAOPなどの代表的なAOPツールが存在するJavaに比べて、.NETはツールも技術情報も少ない状況で、まだまだ未成熟な印象を受けます。
しかし、未成熟ながらも、.NETのAOP環境において、AspectJやJBossAOPにもひけをとらず、使い勝手の良いAspectDNGというツールがあります。本稿ではAspectDNGの持つ機能や使い方などを紹介します。
対象読者
この記事は、次のような方を対象としています。
- AOPの基本的な概念を理解している
- .NETでのAOPに興味がある
必要な環境
- .NET Framework 2.0
- AspectDNG 0.9.95
AspectDNGの紹介
AspectDNGはGPLライセンスのオープンソースソフトウェアで、AspectDNGのプロジェクトは、SourceForge.net、DotNetGuruを経て、現在、Tigris.orgにホスティングされています。
主な特徴は次の通りです。
- .NETアセンブリ(.exeや.dll)にウィービング可能
- アスペクト対象とアスペクト定義クラスのアセンブリを分離可能
- 通常のクラスを使ってアスペクトを定義可能
- アドバイスの定義に.NETカスタムアトリビュートと外部XMLファイルを使用可能
- アドバイスに設定するクエリ文字列として、XPathと簡易型の正規表現が使用可能
- インタータイプ宣言が可能
- 既存タイプ(クラスなど)へのメソッド、フィールド、プロパティ、内部タイプの追加
- アセンブリへのタイプ(クラスなど)の追加
- GAOP(Generic AOP)機能
- MonoプロジェクトのCecilを使ってアセンブリへの操作を実施
ロギング処理をウィービング
定番的なロギング処理のウィービングを通して、AspectDNGの基本的な使い方を紹介していきます。
アスペクトの適用対象とするクラスを作成
アスペクトのウィービング対象となるクラスを作成します。プロパティX
とY
に設定された数値を加算し結果を返すAdd
メソッドだけを持つ簡単なクラスです。
using System; public class Calc { private int x; private int y; public int X { get { return this.x; } set { this.x = value; } } public int Y { get { return this.y; } set { this.y = value; } } public int Add() { return this.x + this.y; } public static void Main(string[] args) { Calc calc = new Calc(); calc.X = 100; calc.Y = 50; Console.WriteLine("{0} + {1} = {2}", calc.X, calc.Y, calc.Add()); } }
アスペクトの定義クラスを作成
AspectDNGのカスタムアトリビュートを使って、Calc
クラスのプロパティやメソッドの呼び出し時に、ロギングを実施するアドバイスを定義したアスペクトクラスを作成していきます。
AspectDNGのアドバイス定義
アスペクトのクラスを作成する前に、AspectDNGにおけるアドバイスの定義方法を簡単に説明します。
AspectDNGではアドバイスの定義に、XMLファイルとカスタムアトリビュートを使う方法があり、カスタムアトリビュートは、次のようなものが用意されています(カスタムアトリビュートは、アドバイスごとに決められたシグネチャのメソッドに指定します)。
[AroundBody("query")]
[AroundCall("query")]
[AroundFieldRead("query")]
[AroundFieldWrite("query")]
[Insert("query")]
[Warning("query")]
[Error("query")]
[SetBaseType("query")]
[ImplementInstance("query")]
[Generic("query")]
上記のquery
部分にはXPathと簡易型の正規表現のどちらかが使用でき、正規表現には次のような記述が可能です。
SomeReturnType SomeNamespace.SomeType::SomeMethod(FirstParamType, SecondParamType, *)
*::{MethodName|OtherMethodName}(OnlyParamType)
*::FieldName
アドバイス指定先メソッドのシグネチャは、アドバイスの種類によって異なります。AroundBody
やAroundCall
の場合は、以下のメソッドシグネチャが使用可能です。
//(注)Interceptorの部分は任意のメソッド名が使用可能 // 引数はターゲットによって変更可 //ターゲットがメソッドと分かっている場合 public static object Interceptor(MethodJoinPoint jp) //ターゲットがコンストラクタと分かっている場合 public static object Interceptor(ConstructorJoinPoint jp) //ターゲットがメソッドかコンストラクタか不明な場合 public static object Interceptor(OperationJoinPoint jp)
アスペクトクラスLogAspectの作成
それでは、アスペクトの定義クラスを作成していきます。
Calc
クラスの全メソッドと、コンストラクタの呼び出し位置をジョインポイントとするAroundCall
アドバイスを指定したIntercept
メソッド、Calc
クラスの全プロパティ取得の実行位置をジョインポイントとするAroundBody
アドバイスを指定したInterceptProperty
メソッドを定義します。
using System; using DotNetGuru.AspectDNG.Joinpoints; public class LogAspect { //Calcクラスの全メソッドと //コンストラクタの呼び出し位置にウィービング [AroundCall("* Calc::*(*)")] public static object Intercept(OperationJoinPoint jp) { Console.WriteLine("--- before call : {0}", jp); return jp.Proceed(); } //Calcクラスの全プロパティ取得の実行位置にウィービング [AroundBody("* Calc::get_*()")] public static object InterceptPorperty(MethodJoinPoint jp) { Console.WriteLine("--- before property get : {0}", jp); return jp.Proceed(); } }
アスペクトのウィービング
事前準備
AspectDNGのサイトから、Binary distributionをクリックし、最新の「aspectdng-latest-bin.zip」ファイルをダウンロードし、適当なディレクトリに解凍します。
解凍先ディレクトリ内の、「2.0\aspectdng.exe」をサンプルを作成するディレクトリにコピーしておきます。
ビルドと実行
参照先に「aspectdng.exe」を指定して「Calc.cs」と「LogAspect.cs」をビルドし「Calc.exe」を作成します
>csc /r:aspectdng.exe Calc.cs LogAspect.cs
この段階ではCalc
クラスに対してアスペクトはウィービングされていないため、実行結果は次のようになります。
>Calc.exe 100 + 50 = 150
アスペクトのウィービング
引数に「Calc.exe」を指定して「aspectdng.exe」を実行することで、アスペクトウィービング後の「Calc.exe」と、元の「Calc.exe」のバックアップである「Calc.exe.backup」が作成されます。
>aspectdng.exe Calc.exe
ウィービング後の実行
ウィービング後の「Calc.exe」の実行結果は次のようになり、アスペクトが適用されていることを確認できます。
>Calc.exe --- before call : constructor Calc() --- before call : instance method Calc::set_X(100) --- before call : instance method Calc::set_Y(50) --- before call : instance method Calc::get_X() --- before property get : instance method Calc::get_X() --- before call : instance method Calc::get_Y() --- before property get : instance method Calc::get_Y() --- before call : instance method Calc::Add() 100 + 50 = 150
MSIL 逆アセンブラの実施
次に、ウィービング後の「Calc.exe」の内部構成にどのような変化が加わったかをMSIL逆アセンブラ(ildasm.exe)を使って確認してみることにします。
>ildasm Calc.exe
実行結果は次のようになり、ジョインポイントごとにAspectDNG関係のメソッドが多数追加されていることを確認できます。