在前面一篇文章中,我對Enterprise Library中的PIAB (Policy Injection Application Block)作了簡單的介紹。在這篇文章主要談談我個人對PIAB設計和實現原理的一些理解。在介紹過程中,我盡量采用由淺入深出的方式,同時結合例子、Source Code。希望通過本篇文章讓大家對PIAB有一個全面、深刻的認識。
一、MBR、ObjRef、RealProxy、TransparentProxy
在真正進入PIAB之前,我們現來談論一些與之相關的、必要的背景知識。MBR、ObjRef、RealProxy和TransparentProxy,對於這些名詞,我想熟悉或者接觸過.NET Remoting的人肯定不會不陌生。由於PIAB的實現機制依賴於Remoting的這種Marshaling,如果對此不了解的讀者將對後面的介紹很難理解,所以很有必要先做以下簡單的介紹。
我們知道,CLR通過AppDomain實現不同Application之間的隔離,在通常情況下,不同AppDomain不同共享內存。在一個AppDomain中創建的對象不能直接被運行在另一個AppDomain的程序使用。跨AppDomain對象的共享依賴於一個重要的過程:Marshaling。我們有兩種不同的Marshaling方式:Marshaling by Value和Marshaling by Reference。前者采用的是Serialization/Deserialization的方式,而後者則是采用傳遞Reference的方式來實現,其實現原來如下:
Remoting Infrastructure先生成對象的ObjRef Instance,ObjRef(System.Runtime.Remoting.ObjRef)代表遠程對象的一個Reference,存儲著跨AppDomain遠程調用所需的所有的信息,比如URI、類型的層次結構、以及實現的Interface等等。ObjRef是可以序列化的,也就是說它可以按照by Value的方式進行Marshaling。所以可以這麼說Marshaling by Reference依賴對對ObjRef的Marshaling by Value。
當ObjRef產地到另一個AppDomain的實現,將根據ObjRef的數據生成兩個Proxy對象:RealProxy和TransparentProxy。RealProxy根據ObjRef創建,通過RealProxy創建TransparentProxy。當進行遠程調用的時候,Client直接和TransparentProxy打交道,對TransparentProxy的調用將會Forward到RealProxy,RealProxy通過Remoting Infrastructure的Communicate和Activation機制將Invocate傳遞給被激活的Remote Object。
MBR通常在一個跨AppDomain的環境下實現遠程調用,但是這種機制在用一個AppDomian依然有效,而且可以免去跨AppDomain的性能損失。PIAB就是充分運用了這種在同一個AppDomain下的MBR。
二、Method Interception & Custom RealProxy
在第一部分我們知道,PIAB的實現是通過將Policy應用到對應的Method上,在真正執行目標對象具體的方法的之前,PIAB將整個方法的調用攔截,然後逐個調用應在該Method上的Policy包含的所有CallHandler(在前一章我們說過Policy = Matching Rule + Call Handler),最後再調用真正目標對象的方法。我們把這種機制成為Method Injection。
如何才有實現這樣的Method Injection呢?這就要需要使用到我們在上面一節介紹的MBR了。通過上面的介紹,我們知道我們調用一個MBR Object的過程是這樣的:
Client Code==〉Transparent Proxy==〉Real Proxy==〉Target Object
在上面的Invocate Chain中,由於真正的目標對象的方法在最後一部才被調用,我們完全有可以在中途將調用”劫持”,使之先調用我們的CallHandler。而這種Inject的突破口在於RealProxy。在FCL(Framework Class Library)中RealProxy(System.Runtime.Remoting.Proxies.RealProxy)是個特殊的Abstract Class,你可以繼承RealProxy定義你自己的Custom RealProxy,將你需要注入的操作寫在Invoke方法中。PIAB采用的就是這樣一種解決方案。
我們先不忙介紹PIAB的具體的實現原理,因為相對比較復雜。為了使讀者能夠更加容易地理解PIAB的實現,我寫了一個簡單的例子。我們它能夠大體體現PIAB的實現機制。
這是一個簡單的Console Application,我首先定義了一個Custom RealProxy:
public class MyRealProxy<T>:RealProxy
{
private T _target;
public MyRealProxy(T target)
: base(typeof(T))
{
this._target = target;
}
public override IMessage Invoke(IMessage msg)
{
//Invoke injected pre-operation.
Console.WriteLine("The injected pre-operation is invoked");
//Invoke the real target instance.
IMethodCallMessage callMessage = (IMethodCallMessage)msg;
object returnValue = callMessage.MethodBase.Invoke(this._target, callMessage.Args);
//Invoke the injected post-operation.
Console.WriteLine("The injected post-peration is executed");
//Return
return new ReturnMessage(returnValue, new object[0], 0, null, callMessage);
}
}
這是一個Generic的RealProxy。在Invoke方法中,兩個Console.Write()代表PIAB注入的CallHandler的調用(對於CallHandler的操作可以是在調用Target Object之前調用,也可以在之後調用,我們不妨稱這兩種類型的操作為Pre-operation和Post-op
eration)。而對Target object的調用實際上是通過Reflection的方式調用的(callMessage.MethodBase.Invoke)。MyRealProxy通過傳入Target Instance來創建。
我們在創建一個Factory Class,用於創建TransparentProxy。在PIAB中,這樣一個Class由Microsoft.Practices.EnterpriseLibrary.PolicyInjection.PolicyInjection來充當。
public static class PolicyInjectionFactory
{
public static T Create<T>()
{
T instance = Activator.CreateInstance<T>();
MyRealProxy<T> realProxy = new MyRealProxy<T>(instance);
T transparentProxy = (T)realProxy.GetTransparentProxy();
return transparentProxy;
}
}
先通過Reflection的方式來創建Target Instance。通過該Instance創建我們在上面定義的Custom RealProxy。最後通過RealProxy返回一個TransparentProxy。
有了上面兩個Class,我們的編寫如下的Code來驗證我們自己的Method Injection:
public class Foo:MarshalByRefObject
{
public voidDoSomeThing()
{
Console.WriteLine("The method of target object is invoked!");
}
}
public class Program
{
public static void Main()
{
Foo foo = PolicyInjectionFactory.Create<Foo>();
foo.DoSomeThing();
}
}
我們來看看程序運行後的結果:
可以看到正式我們需要的結果。從這個例子中我們可以看到,我們的Client code中包含的僅僅是Business Logic相關的code, 而另外一些業務無關的Code則是通過Custom RealProxy的形式注入到Invocation Stack中。這充分體現了Business Concern和Crosscutting Concern的分離。
三、Call Handler Pipeline
我想有了上面的理論和例子作基礎,大家對於PIAB真正的實現不難理解了。我們先來介紹一下PI一個重要的概念:CallHandler Pipeline。我們知道Policy被運用Method方面,一個Policy有多個CallHandler。所有一個Method往往關聯著一連串的CallHandler。這種現在很常見,就上我們第一部分給出的例子一樣,一個簡單的ProcessOrder方面需要執行許多額外的業務無關的邏輯:Authorization、Auditing、Transaction Enlist、Exception Handling和Logging。
在PIAB中,這些基於某個Method的所有CallHandler逐個串聯在一起,組成一個CallHandler Pipeline。具體情況如下圖:
我們從Class Diagram的角度來認識CallHandler Pipeline(在PIAB中通過Microsoft.Practices.EnterpriseLibrary.PolicyInjection.HandlerPipeline來表示)。
public delegate IMethodReturnInvokeHandlerDelegate(IMethodInvocation input, GetNextHandlerDelegate getNext);
public interface IMethodInvocation
{
// Methods
IMethodReturnCreateExceptionMethodReturn(Exception ex);
IMethodReturnCreateMethodReturn(object returnValue, params object[] outputs);
// Properties
IParameterCollectionArguments { get; }
IParameterCollectionInputs { get; }
IDictionaryInvocationContext { get; }
MethodBaseMethodBase { get; }
objectTarget { get; }
}
public delegate InvokeHandlerDelegate GetNextHandlerDelegate();
public interface ICallHandler
{
// Methods
IMethodReturnInvoke(IMethodInvocation input, GetNextHandlerDelegate getNext);
}
IMethodInvocation代表對一個方法的調用,它封裝了一個Method Invocation的相關信息。比如:Parameter List、Invocation Context和Target Object.InvokeHandlerDelegate代表的是對CallHandler的調用,由於所有的CallHandler被串成一個CallHandler Pipeline,在調用了當前CallHandler之後,需要調用Pipeline中後一個CallHandler,對後一個CallHandler調用通過一個Delegate,GetNextHandlerDelegate來表示,而該Delegate的返回值是InvokeHandlerDelegate。結合上面的CallHandler Pipeline的鏈狀結構,對這些應該不難理解。
我們最後來看看HandlerPipeline的定義,它的所有的CallHandler通過一個List來表示。
public class HandlerPipeline
{
// Fields
private List<ICallHandler> handlers;
// Methods
public HandlerPipeline();
public HandlerPipeline(IEnumerable<ICallHandler> handlers);
public IMethodReturnInvoke(IMethodInvocation input, InvokeHandlerDelegate target);
}
HandlerPipeline通過Invoke開始對其CallHandler PipeLine的調用:
public IMethodReturn Invoke(IMethodInvocation input, InvokeHandlerDelegate target)
{
if (this.handlers.Count == 0)
{
return target(input, null);
}
int handlerIndex = 0;
return this.handlers[0].Invoke(input, delegate {
handlerIndex++;
if (handlerIndex < this.handlers.Count)
{
ICallHandler local1 = this.handlers[handlerIndex];
return new InvokeHandlerDelegate(local1.Invoke);
}
return target;
});
}
邏輯並不復雜,按照CallHandler List的先後順序逐個調用,最後調用Target Object。方法中的第二個參數代表target代表對Target Object的調用。
四、PIAB中的Custom RealProxy:InterceptingRealProxy
我們一直再強調,PIAB實際上是通過自定義RealProxy來實現的。而且在第二節我們也實驗了這種做法的可行性。我們現在就來看看PIAB的Custom RealProxy:Microsoft.Practices.EnterpriseLibrary.PolicyInjection.RemotingInterception.InterceptingRealProxy。
public class InterceptingRealProxy : RealProxy, IRemotingTypeInfo
{
// Fields
private Dictionary<MethodBase, HandlerPipeline> memberHandlers;
private readonly object target;
private string typeName;
// Methods
public InterceptingRealProxy(object target, Type classToProxy, PolicySet policies);
private void AddHandlersForInterface(Type targetType, Type itf);
private void AddHandlersForType(Type classToProxy, PolicySet policies);
public bool CanCastTo(Type fromType, object o);
public override IMessage Invoke(IMessage msg);
// Properties
public object Target { get; }
public string TypeName { get; set; }
}
上面是它所有的成員定義,其中memberHandlers是一個以MethodBase為Key的Dictionary,表示應用在Target Object上面的所有的CallHandler Pipeline。通過這個很容易獲得某個具體的方法調用的Pipeline。而target值得是真正的Target Object。
我們著重來看看Invoke的定義:
public override IMessage Invoke(IMessage msg)
{
HandlerPipeline pipeline;
IMethodCallMessage callMessage = (IMethodCallMessage) msg;
if (this.memberHandlers.ContainsKey(callMessage.MethodBase))
{
pipeline = this.memberHandlers[callMessage.MethodBase];
}
else
{
pipeline = new HandlerPipeline();
}
RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, this.target);
return ((RemotingMethodReturn) pipeline.Invoke(invocation, delegate (IMethodInvocation input, GetNextHandlerDelegate getNext) {
try
{
object returnValue = callMessage.MethodBase.Invoke(this.target, invocation.Arguments);
return input.CreateMethodReturn(returnValue, invocation.Arguments);
}
catch (TargetInvocationException exception)
{
return input.CreateExceptionMethodReturn(exception.InnerException);
}
})).ToMethodReturnMessage();
}
上面的一段代碼不長,多看幾遍應該不難理解。總的來說上面的Code現根據msg解析出MethodBase,再獲取對應的CallHandler Pipeline,最後調用Pipeline。
五、Policy Injection Transparent Proxy Factory
介紹到這裡,細心的讀者可以意識到了,我們實際上還還有兩件事情沒有解決:CallHandler Pipeline的初始化和Transparent Proxy的創建。這兩件事情都由PolicyInject.Create和方法來完成。
需要指出的,應用PIAB的Class需要具有兩個條件中至少一個:
· Class繼承System.MarshalByRefObject。
· Class實現某個Interface。
PolicyInjectiond提供了兩種類型的Create方式,一種需要制定Interface,另一種不需要:
public static TInterface Create<TObject, TInterface>(params object[] args);
public static TObject Create<TObject>(params object[] args);
其實還有兩個重載,在這裡就不需要多做介紹了。在具體的實現中,最終又是調用一個PolicyInjector對象來實現的。
public static TObject Create<TObject>(params object[] args)
{
return DefaultPolicyInjector.Create<TObject>(args);
}
public static TInterface Create<TObject, TInterface>(params object[] args)
{
returnDefaultPolicyInjector.Create<TObject, TInterface>(args);
}
PolicyInjector是一個Abstract Class。其中Policies屬性代表應用在該對象上的所有Policy(Policy = CallHandler + Matching Rule)
[CustomFactory(typeof(PolicyInjectorCustomFactory))]
public abstract class PolicyInjector
{
// Fields
private PolicySetpolicies;
// Methods
public PolicyInjector();
public PolicyInjector(PolicySet policies);
public TInterface Create<TObject, TInterface>(params object[] args);
public TObject Create<TObject>(params object[] args);
public objectCreate(Type typeToCreate, params object[] args);
publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args);
… … … … …
// Properties
public PolicySetPolicies { get; set; }
}
在PolicyInjection Class中定義了一個叫做DefaultPolicyInjector的屬性,其定義如下:
private static PolicyInjectorDefaultPolicyInjector
{
get
{
if (defaultPolicyInjector == null)
{
lock (singletonLock)
{
if (defaultPolicyInjector == null)
{
defaultPolicyInjector = GetInjectorFromConfig(ConfigurationSourceFactory.Create());
}
}
}
return defaultPolicyInjector;
}
}
由於上面這個方法具體調用的Stack trace太深了,不可能很詳細地指出其具體的實現。簡單地說,該屬性會返回一個默認的PolicyInjector:RemotingPolicyInjector,並對其進行初始化。初始化的內容就包括初始化所有的Policy(這就是我們在本節開始提出的CallHandler Pipeline的初始化)。由於我們可以有兩種方式將Policy映射到目標成員:Attribute和Configuration。所有具體的做法是先通過分析Configuration找出所有通過configuration方式添加的Policy,然後通過Reflection找出所有通過使用Attribute方式添加的Policy。所以,如果你通過兩種方式將相同的Policy應用到同一個對象上,該對象上將會有兩個一樣CallHandler,個人覺得這是一個值得商榷的做法,我不太贊成這樣的行為。
我們接著看PolicyInjector接著是如何工作的:
public TInterface Create<TObject, TInterface>(params object[] args)
{
return (TInterface) this.Create(typeof(TObject), typeof(TInterface), args);
}
public TObject Create<TObject>(params object[] args)
{
return (TObject) this.Create(typeof(TObject), typeof(TObject), args);
}
上面連個方法由於調用到同一個Create Overload:
publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args)
{
PolicySet policiesFor = this.policies.GetPoliciesFor(typeToCreate);
this.EnsureTypeIsInterceptable(typeToReturn, policiesFor);
return this.DoCreate(typeToCreate, typeToReturn, policiesFor, args);
}
首先找出對應Type的Policy,然後判斷該類型是否支持Method Interception。RemotingPolicyInjector對該方法是這樣實現的:要麼繼承MarshalByRefObject的Class,要麼是個Interface。所以我們才有本節開始提出的兩個條件。
public override boolTypeSupportsInterception(Type t)
{
if (!typeof(MarshalByRefObject).IsAssignableFrom(t))
{
return t.IsInterface;
}
return true;
}
上面連個方法由於調用到同一個Create Overload:
publicobjectCreate(Type typeToCreate, Type typeToReturn, params object[] args)
{
PolicySet policiesFor = this.policies.GetPoliciesFor(typeToCreate);
this.EnsureTypeIsInterceptable(typeToReturn, policiesFor);
return this.DoCreate(typeToCreate, typeToReturn, policiesFor, args);
}
首先找出對應Type的Policy,然後判斷該類型是否支持Method Interception。RemotingPolicyInjector對該方法是這樣實現的:要麼繼承MarshalByRefObject的Class,要麼是個Interface。所以我們才有本節開始提出的兩個條件。
protected override object DoWrap(object instance, Type typeToReturn, PolicySet policiesForThisType)
{
if (PolicyInjector.PolicyRequiresInterception(policiesForThisType))
{
InterceptingRealProxy proxy = new InterceptingRealProxy(this.UnwrapTarget(instance), typeToReturn, policiesForThisType);
return proxy.GetTransparentProxy();
}
return instance;
}
和我們給出的例子差不多,創建RealPoxy,根據該RealProxy返回Transparent Proxy。
五、Policy Injection Design
最後給出整個PIAB實現的所使用的Class,基本上所有的Class都在上面的內容中介紹過了:
在本系列的第三部分,我將介紹如何創建Custom Handler,以及如何將他采用不同的方式應用到你的Application中。
Remoting架構的圖