はじめてのアスペクト指向

アスペクト指向については、RealProxyやReflectionを使って'モドキ'はネタにしましたが、真っ当なのは試してみたことがありません。

Aspect Weaver Tutorial
↑これを見ると簡単そうだったので、ちょっと実験してみることにしました

以下では、AspectWeaverを使用します。まずは、リンク先からAspectWeaver0.6.zipをダウンロードします。(2005/10/16現在)

zipファイルにはソースファイルまるごと入っていますが、今回必要なファイルは3つだけです。

  • AspectWeaver\bin\Release\AspectWeaver.exe
  • AspectWeaver\AspectWeaver.Aspects\bin\Release\AspectWeaver.Aspects.dll
  • AspectWeaver\AspectWeaver.xsd

これらをどこか適当なディレクトリに纏めて放り込み、Pathを通します。私の環境では「c:\home\bin\AspectWeaver」に放り込んでいますので、今後はここにあるものとして話を進めます。

AspectWeaverを使う準備が整いましたので、サンプルの構成について紹介します。先ずは、before、after、aspectsというディレクトリを用意します。

beforeにはCalc.dllというアスペクトを注入するターゲットとなるアセンブリを、aspectsには注入するアスペクトアセンブリを置くことにします。アスペクトは取りあえず、メソッドの呼び出しを記録するだけの簡単なものにしましょう。最後に、afterにはAspectWeaverによってアスペクトが注入されたCalc.dllがコピーされます。

では、ターゲットとなるCalc.dllを用意しましょう。

using System;

public class Calc {
    public int Plus (int x, int y) {
        return x + y;
    }

    public int Minus (int x, int y) {
        return x - y;
    }

    public void Print (int x, int y) {
        Console.WriteLine ("({0}, {1})", x, y);
    }
}

単純なクラスですね。beforeディレクトリにCalc.csと名前を付けて保存し、コンパイルします。

$ csc /t:library Calc.cs

次に注入するアスペクトを用意しましょう。

using System;
using AspectWeaver.Aspects;

public class MethodSelfIntroduceAspect : Aspect
{
    public MethodSelfIntroduceAspect()
    {
    }

    // 全てのメソッドに対して注入
    [InlineAtStart("//Method")]
    public void Introduce()
    {
        Console.Write(string.Format("Calling method: {0}\n", GetContextInfo()));
        object[] args = GetArguments();
        if (args.Length > 0)
        {
            Console.Write(string.Format("Arguments: {0}", args[0]));
            for (int i = 1; i < args.Length; i++)
            {
                Console.Write(string.Format(", {0}", args[i]));
            }
            Console.Write("\n");
        }
    }
    
    // メソッド名が'P'で始まるものに注入
    [InlineAtStart("//Method[starts-with(@name, 'P')]")]
    public void IntroduceOnlyP ()
    {
        Console.Write(string.Format("Calling method 'P': {0}\n", GetContextInfo()));
    }
}

コメントからソースが何をやっているかは想像がつくと思います。aspectsディレクトリにMyAspectsと名前を付けて保存し、コンパイル

$ csc /t:library /r:c:\home\bin\AspectWeaver\AspectWeaver.Aspects.dll MyAspects.cs

AspectWeaver.Aspects.dllへの参照が必要になるので注意です。これで、注入するアスペクトも出来ました。

最後に、AspectWeaverで注入を行えば完了なのですが、AspectWeaverは設定ファイルを読み込んで動作するので、設定ファイルを用意します。

<?xml version="1.0" encoding="utf-8" ?>
<Configuration logFile="LogWeaving.xml" cleanTempFiles="false">
    <BaseAssembly>before\Calc.dll</BaseAssembly>
    <OutputAssembly>after\Calc.dll</OutputAssembly>
    <AspectAssemblies>
        <AspectAssembly uniqueName="MyAspects.dll" path="aspects\MyAspects.dll" />
    </AspectAssemblies>
</Configuration>

入出力ファイルを指定しているだけの簡単なものですね。このファイルは現在作業しているプロジェクトのトップ(つまり、before/after/aspectsの親ディレクトリ)に置きます。名前はconfig.xmlにでもしましょう。

で、実行

$ AspectWeaver.exe config.xml

以下のメッセージが出力されたら成功です。

Weaved successfully!

早速、afterディレクトリに移動してみましょう。Calc.dllがありますね。では、このCalc.dllを使って見ましょう。

using System;

class Program {
    public static void Main () {
        Calc calc = new Calc ();
        Console.WriteLine (calc.Plus (10, 20));
        Console.WriteLine (calc.Minus (10, 20));
        calc.Print (10, 20);
    }
}

これをコンパイルして実行すると・・・

Calling method 'P': System.Int32 Calc::Plus(System.Int32,System.Int32)
Calling method: System.Int32 Calc::Plus(System.Int32,System.Int32)
Arguments: 10, 20
30
Calling method: System.Int32 Calc::Minus(System.Int32,System.Int32)
Arguments: 10, 20
-10
Calling method 'P': System.Void Calc::Print(System.Int32,System.Int32)
Calling method: System.Void Calc::Print(System.Int32,System.Int32)
Arguments: 10, 20
(10, 20)

見事メソッド呼び出しに割り込んでいますね。'P'で始まるメソッドは2回割り込まれていることにも注目です。


駆け足で紹介しましたが、如何でしたでしょうか?