PIAB の中身を見てみた

はじめに

「使っているものの仕組みをしれ!」みたいな教えを以前受けた気がするので、Policy Injection Application Block のソースコードを覗いてみました。
先日のエントリ『PIABで属性を使ってインターセプト対象メソッドを指定する』で作成したサンプルの

var animal = PolicyInjection.Create<Wankuma>();

の部分で何をやっているのかを、ざっと追っていきます。

PolicyInjection クラス

Create メソッド
public static class PolicyInjection
{
    private static volatile PolicyInjector defaultPolicyInjector;
    private static readonly object singletonLock = new object();

    public static TObject Create<TObject>(params object[] args)
    {
        return DefaultPolicyInjector.Create<TObject>(args);
    }    

デフォルトの PolicyInjector を取得して、Create メソッドを呼んでいます。

DefaultPolicyInjector プロパティ
private static PolicyInjector DefaultPolicyInjector
{
    get
    {
        if( defaultPolicyInjector == null)
        {
            lock(singletonLock)
            {
                if(defaultPolicyInjector == null)
                {
                    IConfigurationSource configurationSource =
                        ConfigurationSourceFactory.Create();
                    defaultPolicyInjector = GetInjectorFromConfig(configurationSource);
                }
            }
        }
        return defaultPolicyInjector;
    }
}

private static PolicyInjector GetInjectorFromConfig(IConfigurationSource configurationSource)
{
    PolicyInjectorFactory injectorFactory = new PolicyInjectorFactory(configurationSource);
    return injectorFactory.Create();
}

構成ファイルの内容を PolicyInjectorFactory に渡し、Create メソッドで PolicyInjector を生成してます。初めてこのプロパティを使うときに、1回だけ生成されるみたいですね。

PolicyInjectorFactory クラス

Create メソッド
public class PolicyInjectorFactory
{
    public PolicyInjector Create()
    {
        return EnterpriseLibraryFactory.BuildUp<PolicyInjector>(configurationSource);
    }

EnterpriseLibraryFactory は内部で ObjectBuilder を使って PolicyInjector を生成しています。

PolicyInjector クラス

Create メソッド
[CustomFactory(typeof(PolicyInjectorCustomFactory))]
public abstract class PolicyInjector
{
    public object Create(Type typeToCreate, Type typeToReturn, params object[] args)
    {
        // 構成ファイルや属性をチェックして、型に適用するポリシーを取り出す
        PolicySet policiesForThisType = policies.GetPoliciesFor(typeToCreate);

        // インターセプト可能かどうかチェックする。
        // インターセプト出来ない場合は ArgumentException を発生させる。
        EnsureTypeIsInterceptable(typeToReturn, policiesForThisType);

        return DoCreate(typeToCreate, typeToReturn, policiesForThisType, args);
    }

インターセプト可能かどうかの判断基準は「ポリシーが指定されているか」「MarshalByRefObject を継承しているか」など。PolicyInjector の実装によって異なります。
あと、実際にインスタンスを生成しているのは DoCreate メソッド。

DoCreate メソッド
protected virtual object DoCreate(Type typeToCreate, Type typeToReturn, PolicySet policiesForThisType, object[] arguments)
{
    object target = Activator.CreateInstance(typeToCreate, arguments);
    return DoWrap(target, typeToReturn, policiesForThisType);
}

リフレクションを使ってインスタンスを生成したあと、DoWrap メソッド呼んでいますね。
DoWrap メソッドは abstract なので、今回は RemotingPolicyInjector クラスの DoWrap を見てみる。

RemotingPolicyInjector クラス

DoWrap メソッド
[ConfigurationElementType(typeof(RemotingInjectorData))]
public class RemotingPolicyInjector : PolicyInjector
{
    protected override object DoWrap(object instance, Type typeToReturn, PolicySet policiesForThisType)
    {
        if (PolicyRequiresInterception(policiesForThisType))
        {
            // instance が既に RealProxy でラップされている場合は、ラップを解いて渡す。
            // ラップされていない場合はそのまま渡す。
            InterceptingRealProxy proxy =
                new InterceptingRealProxy(UnwrapTarget(instance), typeToReturn, policiesForThisType);
            return proxy.GetTransparentProxy();
        }
        return instance;
    }

InterceptingReadProxy は RealProxy を継承したクラスです。透過プロキシを返しているのかぁ。

InterceptingReadProxy クラス

コンストラクタ
public InterceptingRealProxy(object target, Type classToProxy, PolicySet policies)
    : base(classToProxy)
{
    this.target = target;
    this.typeName = target.GetType().FullName;
    Type targetType = target.GetType();

    memberHandlers = new Dictionary<MethodBase, HandlerPipeline>();

  // クラスの情報から CallHandler を取得して memberHandlers に追加します
    AddHandlersForType(targetType, policies);

    // ここではベースクラスから
    Type baseTypeIter = targetType.BaseType;
    while (baseTypeIter != null && baseTypeIter != typeof(object))
    {
        AddHandlersForType(baseTypeIter, policies);
        baseTypeIter = baseTypeIter.BaseType;
    }

    foreach (Type inter in targetType.GetInterfaces())
    {
        // クラスとインタフェースをマッピングしたあと、
        // クラスのメソッド情報をもとに取得した CallHandler を
        // インタフェースのメソッド用の CallHandler として再登録している
        AddHandlersForInterface(targetType, inter);
    }        
}

コンストラクタ内でクラスの情報をもとに CallHandler を取得して、メンバに保存しています。
Create の流れはここで終了。でもせっかくなので、メソッドが実際にインターセプトされる処理も見てみます。

Invoke メソッド
public class InterceptingRealProxy : RealProxy, IRemotingTypeInfo
{
    public override IMessage Invoke(IMessage msg)
    {
        IMethodCallMessage callMessage = (IMethodCallMessage)msg;

        // メソッドをインターセプトして実行する CallHandler を取得する
        HandlerPipeline pipeline;
        if (memberHandlers.ContainsKey(callMessage.MethodBase))
        {
            pipeline = memberHandlers[callMessage.MethodBase];
        }
        else
        {
            pipeline = new HandlerPipeline();
        }

        RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, target);
        IMethodReturn result =
            pipeline.Invoke(
                invocation,
                delegate(IMethodInvocation input, GetNextHandlerDelegate getNext)
                {
                    try
                    {
                        // インターセプト対象のメソッドを実行
                        object returnValue = callMessage.MethodBase.Invoke(target, invocation.Arguments);
                        return input.CreateMethodReturn(returnValue, invocation.Arguments);
                    }
                    catch (TargetInvocationException ex)
                    {
                        // The outer exception will always be a reflection exception; we want the inner, which is
                        // the underlying exception.
                        return input.CreateExceptionMethodReturn(ex.InnerException);
                    }
                });
        return ((RemotingMethodReturn)result).ToMethodReturnMessage();
    }

インターセプト対象メソッドの情報をもとに HandlerPipeline を取得して、その Invoke メソッドを呼び出しているのか。
インターセプトされたメソッドは、リフレクション(MethodBase.Invoke)で呼び出しています。

HandlerPipeline クラス

Invoke メソッド
public class HandlerPipeline
{
    private List<ICallHandler> handlers;

    public IMethodReturn Invoke(IMethodInvocation input, InvokeHandlerDelegate target)
    {
        if( handlers.Count == 0 )
        {
            return target(input, null);
        }

        int handlerIndex = 0;

        // メソッドをインターセプトして実行する処理を、連鎖して呼び出す
        IMethodReturn result = handlers[0].Invoke(input, delegate
                                  {
                                      ++handlerIndex;
                                      if(handlerIndex < handlers.Count)
                                      {
                                          return handlers[handlerIndex].Invoke;
                                      }
                                      else
                                      {
                                          return target;
                                      }
                                  });
        return result;
    }

構成ファイルなどで指定した CallHander がこのクラスに登録されています。
先に登録されているハンドラを呼び出して、最後にインターセプト対象のメソッドが呼ばれる・・・と。

最後に

PIAB では RealProxy を使って AOP を実現しているのか!