一、為什麼CallHandler需要進行排序
PIAB為我們提供了一個很好地實現AOP的方式。AOP旨在實現Business Logic和Non-Business Infrastructure Logic的分離。通過PIAB,我們將這些業務無關的邏輯定義在一個個的CallHandler中,然後通過Attribute或者Configuration的方式,將我們所需的CallHandler運用到相應的目標對象中。從這個意義上講,PIAB具有很好的Flexibility和Extensibility。但是,就我看來PIAB也具有一些不足之處,其最大的局限性在於:不能控制運用到某個Method的多個方法的執行順序。而讓CallHandler按照我們希望的順序進行調用是非常有必要的。
舉個例子,假設我們將以下3個CallHandler運用到某個方法中:
ValidationHandler:用於參數參數的驗證,比如是否為null, string的Length是否超出長度等等。
TransactionEnlistHandler: 用於將操作自動納入到一個Transaction中,從而保證數據的一致性。
AuditLoggingHandler:當時操作成功執行後進行Audit Log。
很顯然,正常的執行順序應該是這樣的:在最開始調用ValidationHandler進行參數的驗證;Audit Log需要和目標方法一起納入同一個Transaction中,所以TransactionEnlistHandler的調用緊隨其後,最後才是AuditLoggingHandler。
Microsoft提供的原生的PIAB是無法實現的,好在Enterprise Library是開源的,我們可以修改PIAB的Source Code來使其實現我們的目標。而僅僅是一個很小的改動。接下來我們就來討論一下如何來實現可被排序的CallHandler Pipeline。
二、如何創建Sequential CallHandler Pipeline
如果要了解我們這個Sequential CallHandler Pipeline的實現,需要對PIAB的是實現機制有一定的了解。在本系列的第二部分裡,我對PIAB的實現機制進行了詳細的闡述,在這裡我僅僅簡單介紹一個PIAB是如何實現AOP的。
PIAB對AOP的實現原理可以用一個詞來概括:Method Interception。具體的做法做法是:通過PIAB Factory創建基於Target Type的Real Proxy,然後通過這個Real Proxy創建Transparent Proxy,並通過該Transparent Proxy調用Target Instance。在創建Real Proxy中,將運用到該Type的所有CallHandler緩存起來。當進行調用的時候,Transparent Proxy調用Real Proxy的Invoke方法。在該方法中,在將運用到當前Method的CallHandler構成一個Handler Pipeline。在真正調用Target Instance之前,按照Pipeline的先後順序依次調用每個CallHandler。
而我們實現的切入點就是:在CallHandler Pipeline創建之後,再根據我們希望的順序將所有的CallHander重新排序。
三、Sequential CallHandler Pipeline的實現
實現一個Sequential CallHandler Pipeline的一個前提就是,如何確定一個CallHandler在Pipeline的位置。為此,我們需要我們的Custom CallHandler有一個額外的屬性:Ordinal,表明在Pipeline的序號,序號小的在前,大的在後。如何沒有該屬性,比如PIAB提供的所有CallHandler,我們將其放在最後。
我們僅僅需要修改兩個PIAB Class: Microsoft.Practices.EnterpriseLibrary.PolicyInjection. HandlerPipeline和Microsoft.Practices.EnterpriseLibrary.PolicyInjection. RemotingInterception. InterceptingRealProxy。
對於HandlerPipeline,添加了一個新的Property:Handlers,用於在InterceptingRealProxy中能夠獲得組成Pipeline的所有CallHandler以利於排序。
public class HandlerPipeline
{
private List<ICallHandler> handlers;
///<summary>
/// This property is added to get the CallHandler(s).
///</summary>
public List<ICallHandler> Handlers
{
get { return handlers; }
set { handlers = value; }
}
。。。。。。
}
在才中,添加一個新的方法:ResortHandlers,將所有CallHandler按照Ordinal的大小進行重新排序(通過Reflection得到Ordinal的值)。
public HandlerPipeline ResortHandlers(HandlerPipeline pipeline)
{
HandlerPipeline sequentialPipeline = new HandlerPipeline();
IDictionary<ICallHandler,int> handlerOrdinalPairList = new Dictionary<ICallHandler,int>();
ICallHandler[] handlers = Array.CreateInstance(typeof(ICallHandler), pipeline.Handlers.Count) as ICallHandler[];
int[] ordinals = Array.CreateInstance(typeof(int), pipeline.Handlers.Count) as int[];
for (int i = 0; i < pipeline.Handlers.Count; i++ )
{
ICallHandler handler = pipeline.Handlers[i];
handlers[i] = handler;
Type handlerType = handler.GetType();
MemberInfo[] memberInfos = handlerType.GetMember("Ordinal");
if (memberInfos.Length == 0)
{
ordinals[i] = int.MaxValue;
continue;
}
PropertyInfo propertyInfo = memberInfos[0] as PropertyInfo;
if (propertyInfo == null)
{
ordinals[i] = int.MaxValue;
continue;
}
int ordinal = (int)propertyInfo.GetValue(handler, null);
ordinals[i] = ordinal;
}
ICallHandler swapHandler;
int swapOrdinal;
for (int i = 0; i < pipeline.Handlers.Count - 1; i++)
{
for (int j = i + 1; j < pipeline.Handlers.Count; j++)
{
if (ordinals[i] > ordinals[j])
{
swapOrdinal = ordinals[i];
ordinals[i] = ordinals[j];
ordinals[j] = swapOrdinal;
swapHandler = handlers[i];
handlers[i] = handlers[j];
handlers[j] = swapHandler;
}
}
}
return new HandlerPipeline(handlers);
}
注:采用Reflection的方式獲得Ordinal並不是一種很好的方式,最好是定義一個Abstract CallHandler BaseClass,並將Ordinal Property定義在這個BaseClass中。
該方法將在Ordinal的Invoke中調用:
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage callMessage = (IMethodCallMessage)msg;
HandlerPipeline pipeline;
if (memberHandlers.ContainsKey(callMessage.MethodBase))
{
pipeline = memberHandlers[callMessage.MethodBase];
//Added by Jiang Jin Nan
pipeline = ResortHandlers(pipeline);
}
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();
}
這就是所有需要的改動,為了驗證是否有效,我們照例寫一個測試程序。
四、如何使用Sequential CallHandler的PIAB
為了驗證我們上所做的能否實現我們的目標:讓運用到某個Method上的CallHandler按照我們希望的順序來執行,我們創建了兩個Custom CallHandler: CustomHandlerA 和CustomHandlerB:
namespace Artech.SequentialCallHandlers
{
public class CustomHandlerA: ICallHandler
{
public int Ordinal
{ get; set; }
public CustomHandlerA()
{
this.Ordinal = int.MaxValue;
}
#region ICallHandler Members
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerA is invoked!");
return getNext()(input, getNext);
}
#endregion
}
}
namespace Artech.SequentialCallHandlers
{
public class CustomHandlerB:ICallHandler
{
public int Ordinal
{ get; set; }
public CustomHandlerB()
{
this.Ordinal = int.MaxValue;
}
#region ICallHandler Members
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerB is invoked!");
return getNext()(input, getNext);
}
#endregion
}
}
下面是兩個對應的HandlerAttribute:
namespace Artech.SequentialCallHandlers
{
public class ACustomHandlerAttribute:HandlerAttribute
{
public int Ordinal
{ get; set; }
public override ICallHandler CreateHandler()
{
return new CustomHandlerA() { Ordinal = this.Ordinal };
}
}
}
namespace Artech.SequentialCallHandlers
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class BCustomHandlerAttribute:HandlerAttribute
{
public int Ordinal
{ get; set; }
public override ICallHandler CreateHandler()
{
return new CustomHandlerB() { Ordinal = this.Ordinal };
}
}
}
注:如何定義Custom CallHandler,在本系列的第三部分有詳細的介紹。
然後,我們將這連個Attribute運用到同一個方法中:
class PolicyInjectionType:MarshalByRefObject
{
[BCustomHandlerAttribute(Ordinal = 1)]
[ACustomHandlerAttribute(Ordinal = 2)]
public void DoSomething()
{
Console.WriteLine("The target object is invoked!");
}
}
我們在一個Console Application的Main()種調用這個DoSomething()方法:
class Program
{
static void Main(string[] args)
{
PolicyInjectionType proxy = PolicyInjection.Create<PolicyInjectionType>();
proxy.DoSomething();
}
}
由於CustomHandlerA的Ordinal為2,CustomHandlerB的Ordinal為1,所以他們正確的執行順序為:CustomHandlerB-〉CustomHandlerA。輸出的結果證實了這一點:
我們來改變一下他們的順序:
class PolicyInjectionType:MarshalByRefObject
{
[BCustomHandlerAttribute(Ordinal = 2)]
[ACustomHandlerAttribute(Ordinal = 1)]
public void DoSomething()
{
Console.WriteLine("The target object is invoked!");
}
}
這樣的話,兩個CallHandler的順序將變成:CustomHandlerA-〉CustomHandlerB。我們再來看看輸出的結果:
這正是我們所希望的。
本文配套源碼