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