はじめに
「使っているものの仕組みをしれ!」みたいな教えを以前受けた気がするので、
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 を実現しているのか!