閱讀476 返回首頁    go 技術社區[雲棲]


Enterprise Library深入解析與靈活應用(6):自己動手創建迷你版AOP框架

基於Enterprise Library PIAB的AOP框架已經在公司項目開發中得到廣泛的使用,但是最近同事維護一個老的項目,使用到了Enterprise Library 2,所以PIAB是在Enterprise Library 3.0中推出的,所以不同直接使用。為了解決這個問題,我寫了一個通過方法劫持(Method Interception)的原理,寫了一個簡易版的AOP框架。(如果對PIAB不是很了解的讀者,可以參閱我的文章MS Enterprise Library Policy Injection Application Block 深入解析[總結篇])。 Souce Code下載:https://files.cnblogs.com/artech/Artech.SimpleAopFramework.rar 

一、如何使用?

編程方式和PIAB基本上是一樣的,根據具體的需求創建相應的CallHandler,通過Custom Attribute的形式將CallHandler應用到類型或者方法上麵。下麵就是一個簡單例子。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         string userID = Guid.NewGuid().ToString();
   6:         InstanceBuilder.Create<UserManager, IUserManager>().CreateDuplicateUsers(userID, Guid.NewGuid().ToString());
   7:         Console.WriteLine("Is the user whose ID is \"{0}\" has been successfully created! {1}", userID, UserUtility.UserExists(userID) ? "Yes" : "No");
   8:     }
   9: }
  10:  
  11: public class UserManager : IUserManager
  12: {
  13:     [ExceptionCallHandler(Ordinal = 1, MessageTemplate = "Encounter error:\nMessage:{Message}")]
  14:     [TransactionScopeCallHandler(Ordinal = 2)]
  15:     public void CreateDuplicateUsers(string userID, string userName)
  16:     {
  17:         UserUtility.CreateUser(userID, userName);
  18:         UserUtility.CreateUser(userID, userName);
  19:     }
  20: }
  21: public interface IUserManager
  22: {
  23:     void CreateDuplicateUsers(string userID, string userName);
  24: }

在上麵例子中,我創建了兩個CallHandler:和,用於進行事務和異常的處理。也就是說,我們不需要手工地進行事務的Open、Commit和Rollback的操作,也不需要通過try/catch block進行手工的異常處理。為了驗證正確性,我模擬了這樣的場景:數據庫中有一個用戶表(Users)用於存儲用戶帳戶,每個帳戶具有唯一ID,現在我通過UserManager的CreateDuplicateUsers方法插入兩個具有相同ID的記錄,毫無疑問,如果沒有事務的處理,第一次用戶添加將會成功,第二次將會失敗。反之如果我們添加的能夠起作用,兩次操作將在同一個事務中進行,重複的記錄添加將會導致事務的回退。

中,會對拋出的SqlException進行處理,在這我們僅僅是打印出異常相關的信息。至於具有要輸出那些信息,可以通過ExceptionCallHandlerAttribute的MessageTemplate 屬性定義一個輸出的模板。運行程序,我們會得到這樣的結果,充分證明了事務的存在,錯誤信息也按照我們希望的模板進行輸出。

image

二、設計概要

同PIAB的實現原理一樣,我通過自定義RealProxy實現對CallHandler的執性,從而達到方法調用劫持的目的(底層具體的實現,可以參閱我的文章Policy Injection Application Block 設計和實現原理)。下麵的UML列出了整個框架設計的所有類型。

image

  • ICallHandler:所有CallHandler必須實現的接口。
  • CallHandlerBase:實現了ICallHandler的一個抽象類,是自定義CallHandler的基類。
  • HandlerAttribute:所有的CallHandler通過相應的HandlerAttribute被應用到所需的目標對象上。HandlerAttribute是一個繼承自Attribute的抽象類,是自定義HandlerAttribute的基類。
  • CallHandlerPipeline:由於同一個目標方法上麵可以同時應用多個CallHandler,在運行時,他們被串成一個有序的管道,依次執行。
  • InterceptingRealProxy<T>:繼承自RealProxy,CallHandlerPipeline最終在Invoke方法中執行,從而實現了“方法調用劫持”。
  • InvocationContext:表示當前方法執行的上下文,Request和Reply成員表示方法的調用和返回消息。
  • InstanceBuidler:由於我們需根據InterceptingRealProxy<T>對象創建TransparentProxy,並通過TransparentProxy進行方法的調用,CallHandler才能在RealProxy中被執行。InstanceBuilder用於方便的創建TransparentProxy對象。

三、具體實現

現在我們來詳細分析實現的細節。下來看看表示方法調用上下文的InvocationContext的定義。

InvocationContext 

   1: public class InvocationContext
   2: {
   3:     public IMethodCallMessage Request
   4:     { get; set; }
   5:  
   6:     public ReturnMessage Reply
   7:     { get; set; }
   8:  
   9:     public IDictionary<object, object> Properties
  10:     { get; set; }
  11: }

Request和Reply本質上都是一個System.Runtime.Remoting.Messaging.IMessage對象。Request是IMethodCallMessage 對象,表示方法調用的消息,Reply則是ReturnMessage對象,具有可以包含具體的返回值,也可以包含拋出的異常。Properties可以供我們自由地設置一些自定義的上下文。

ICallHandler、CallHandlerBase和HandlerAttribute

ICallHandler包含四個成員,PreInvoke和PostInvoke在執行目標方法前後被先後調用,自定義CallHandler可以根據自己的具體需求實現這個兩個方法。PreInvoke返回值可以通過PostInvoke的correlationState獲得。Ordinal表明CallHandler在CallHandler管道的位置,他決定了應用於同一個目標方法上的多個CallHandler的執行順序。ReturnIfError表示CallHandler在拋出異常時是否直接退出。

   1: public interface ICallHandler
   2: {
   3:     object PreInvoke(InvocationContext context);
   4:  
   5:     void PostInvoke(InvocationContext context, object correlationState);
   6:  
   7:     int Ordinal{ get; set; }
   8:  
   9:     bool ReturnIfError{ get; set; }
  10: }

CallHandler的抽象基類CallHandlerBase僅僅是對ICallHandler的簡單實現。

   1: public abstract class CallHandlerBase : ICallHandler
   2: {
   3:     public abstract object PreInvoke(InvocationContext context);
   4:  
   5:     public abstract void PostInvoke(InvocationContext context, object correlationState);
   6:  
   7:     public int Ordinal{ get; set; }
   8:  
   9:     public bool ReturnIfError { get; set; }
  10: }

HandlerAttribute中定義了CreateCallHandler方法創建相應的CallHandler對象,Ordinal和ReturnIfError同上。

   1: public abstract class HandlerAttribute : Attribute
   2: {
   3:     public abstract ICallHandler CreateCallHandler();
   4:  
   5:     public int Ordinal{ get; set; }
   6:  
   7:     public bool ReturnIfError{ get; set; }
   8: }

CallHandlerPipeline是CallHandler的有序集合,我們通過一個IList<ICallHandler> 對象和代碼最終目標對象的創建CallHandlerPipeline。CallHandlerPipeline的核心方法是Invoke。在Invoke方法中按照CallHandler在管道中的次序先執行PreInvoke方法,然後通過反射執行目標對象的相應方法,最後逐個執行CallHandler的PostInvoke方法。

   1: public class CallHandlerPipeline
   2: {
   3:     private object _target;
   4:     private IList<ICallHandler> _callHandlers;
   5:  
   6:     public CallHandlerPipeline(object target): this(new List<ICallHandler>(), target){ }
   7:  
   8:     public CallHandlerPipeline(IList<ICallHandler> callHandlers, object target)
   9:     {
  10:         if (target == null)
  11:         {
  12:             throw new ArgumentNullException("target");
  13:         }
  14:  
  15:         if (callHandlers == null)
  16:         {
  17:             throw new ArgumentNullException("callHandlers");
  18:         }
  19:  
  20:         this._target = target;
  21:         this._callHandlers = callHandlers;
  22:     }
  23:  
  24:     public void Invoke(InvocationContext context)
  25:     {
  26:         Stack<object> correlationStates = new Stack<object>();
  27:         Stack<ICallHandler> callHandlerStack = new Stack<ICallHandler>();
  28:  
  29:         //Preinvoke.
  30:         foreach (ICallHandler callHandler in this._callHandlers)
  31:         {
  32:             correlationStates.Push(callHandler.PreInvoke(context));
  33:             if (context.Reply != null && context.Reply.Exception != null && callHandler.ReturnIfError)
  34:             {
  35:                 context.Reply = new ReturnMessage(context.Reply.Exception, context.Request);
  36:                 return;
  37:             }
  38:             callHandlerStack.Push(callHandler);
  39:         }
  40:  
  41:         //Invoke Target Object.
  42:         object[] copiedArgs = Array.CreateInstance(typeof(object), context.Request.Args.Length) as object[];
  43:         context.Request.Args.CopyTo(copiedArgs, 0);
  44:         try
  45:         {
  46:             object returnValue = context.Request.MethodBase.Invoke(this._target, copiedArgs);
  47:             context.Reply = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, context.Request.LogicalCallContext, context.Request);
  48:         }
  49:         catch (Exception ex)
  50:         {
  51:             context.Reply = new ReturnMessage(ex, context.Request);
  52:         }
  53:  
  54:         //PostInvoke.
  55:         while (callHandlerStack.Count > 0)
  56:         {
  57:             ICallHandler callHandler = callHandlerStack.Pop();
  58:             object correlationState = correlationStates.Pop();
  59:             callHandler.PostInvoke(context, correlationState);
  60:         }
  61:     }
  62:  
  63:     public void Sort()
  64:     {
  65:         ICallHandler[] callHandlers = this._callHandlers.ToArray<ICallHandler>();
  66:         ICallHandler swaper = null;
  67:         for (int i = 0; i < callHandlers.Length - 1; i++)
  68:         {
  69:             for (int j = i + 1; j < callHandlers.Length; j++)
  70:             {
  71:                 if (callHandlers[i].Ordinal > callHandlers[j].Ordinal)
  72:                 {
  73:                     swaper = callHandlers[i];
  74:                     callHandlers[i] = callHandlers[j];
  75:                     callHandlers[j] = swaper;
  76:                 }
  77:             }
  78:         }
  79:  
  80:         this._callHandlers = callHandlers.ToList<ICallHandler>();
  81:     }
  82:  
  83:     public void Combine(CallHandlerPipeline pipeline)
  84:     {
  85:         if (pipeline == null)
  86:         {
  87:             throw new ArgumentNullException("pipeline");
  88:         }
  89:  
  90:         foreach (ICallHandler callHandler in pipeline._callHandlers)
  91:         {
  92:             this.Add(callHandler);
  93:         }
  94:     }
  95:  
  96:     public void Combine(IList<ICallHandler> callHandlers)
  97:     {
  98:         if (callHandlers == null)
  99:         {
 100:             throw new ArgumentNullException("callHandlers");
 101:         }
 102:  
 103:         foreach (ICallHandler callHandler in callHandlers)
 104:         {
 105:             this.Add(callHandler);
 106:         }
 107:     }
 108:  
 109:     public ICallHandler Add(ICallHandler callHandler)
 110:     {
 111:         if (callHandler == null)
 112:         {
 113:             throw new ArgumentNullException("callHandler");
 114:         }
 115:  
 116:         this._callHandlers.Add(callHandler);
 117:         return callHandler;
 118:     }
 119: }

InterceptionRealProxy<T>

InterceptingRealProxy<T>是現在AOP的關鍵所在,我們通過一個IDictionary<MemberInfo, CallHandlerPipeline>和目標對象創建InterceptingRealProxy對象。在Invoke方法中,根據方法表示方法調用的IMethodCallMessage對象的MethodBase為key,從CallHandlerPipeline字典中獲得基於當前方法的CallHandlerPipeline,並調用它的Invoke方法,InvocationContext的Reply即為最終的返回。

   1: public class InterceptingRealProxy<T> : RealProxy
   2: {
   3:     private IDictionary<MemberInfo, CallHandlerPipeline> _callHandlerPipelines;
   4:     public InterceptingRealProxy(object target, IDictionary<MemberInfo, CallHandlerPipeline> callHandlerPipelines)
   5:         : base(typeof(T))
   6:     {
   7:         if (callHandlerPipelines == null)
   8:         {
   9:             throw new ArgumentNullException("callHandlerPipelines");
  10:         }
  11:  
  12:         this._callHandlerPipelines = callHandlerPipelines;
  13:     }
  14:     
  15:     public override IMessage Invoke(IMessage msg)
  16:     {
  17:         InvocationContext context = new InvocationContext();
  18:         context.Request = (IMethodCallMessage)msg;
  19:         this._callHandlerPipelines[context.Request.MethodBase].Invoke(context);
  20:         return context.Reply;
  21:     }
  22: }

InstanceBuidler

同PIAB通過PolicyInjection.Create()/Wrap()創建Transparent Proxy類型,InstanceBuidler也充當這樣的工廠功能。InstanceBuidler的實現原理就是:通過反射獲得目標類型上所有的HandlerAttribute,通過調用HandlerAttribute的CreateCallHandler創建相應的CallHandler。對於每個具體的方法,將應用在其類和方法上的所有的CallHandler組合成CallHandlerPipeline,然後以MemberInfo對象為Key將所有基於某個方法的CallHandlerPipeline構成一個CallHandlerPipeline字典。該字典,連同通過反射創建的目標對象,創建InterceptingRealProxy<T>對象。最後返回InterceptingRealProxy<T>對象的TransparentProxy對象。

   1: public class InstanceBuilder
   2: {
   3:     public static TInterface Create<TObject, TInterface>() where TObject : TInterface
   4:     {
   5:         TObject target = Activator.CreateInstance<TObject>();
   6:         InterceptingRealProxy<TInterface> realProxy = new InterceptingRealProxy<TInterface>(target, CreateCallHandlerPipeline<TObject, TInterface>(target));
   7:         return (TInterface)realProxy.GetTransparentProxy();
   8:     }
   9:     
  10:     public static T Create<T>()
  11:     {
  12:         return Create<T, T>();
  13:     }
  14:     
  15:     public static IDictionary<MemberInfo, CallHandlerPipeline> CreateCallHandlerPipeline<TObject, TInterfce>(TObject target)
  16:     {
  17:         CallHandlerPipeline pipeline = new CallHandlerPipeline(target);
  18:         object[] attributes = typeof(TObject).GetCustomAttributes(typeof(HandlerAttribute), true);
  19:         foreach (var attribute in attributes)
  20:         {
  21:             HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
  22:             pipeline.Add(handlerAttribute.CreateCallHandler());
  23:         }
  24:  
  25:         IDictionary<MemberInfo, CallHandlerPipeline> kyedCallHandlerPipelines = new Dictionary<MemberInfo, CallHandlerPipeline>();
  26:  
  27:         foreach (MethodInfo methodInfo in typeof(TObject).GetMethods())
  28:         {
  29:             MethodInfo declareMethodInfo = typeof(TInterfce).GetMethod(methodInfo.Name, BindingFlags.Public | BindingFlags.Instance);
  30:             if (declareMethodInfo == null)
  31:             {
  32:                 continue;
  33:             }
  34:             kyedCallHandlerPipelines.Add(declareMethodInfo, new CallHandlerPipeline(target));
  35:             foreach (var attribute in methodInfo.GetCustomAttributes(typeof(HandlerAttribute), true))
  36:             {
  37:                 HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
  38:                 kyedCallHandlerPipelines[declareMethodInfo].Add(handlerAttribute.CreateCallHandler());
  39:             }
  40:             kyedCallHandlerPipelines[declareMethodInfo].Combine(pipeline);
  41:             kyedCallHandlerPipelines[declareMethodInfo].Sort();
  42:         }
  43:  
  44:         return kyedCallHandlerPipelines;
  45:     }
  46: }

四、如果創建自定義CallHandler

在一開始的例子中,我們創建了兩個自定義的CallHandler,一個用於進行事務處理的TranactionScopeCallHandler,另一個用於異常處理的ExceptionCallHandler。我們現在就來簡單談談它們的實現。

TranactionScopeCallHandler

先來看看TranactionScopeCallHandler和TranactionScopeCallHandlerAttribute。我們通過TranactionScope的方式實現事務支持。在PreInvoke方法中,創建並返回TranactionScope對象,在PostInvoke中,通過correlationState參數得到該TranactionScope對象,如果沒有異常(context.Reply.Exception == null),調用Complete方法提交事務。最後調用Dispose釋放TranactionScope對象。(TranactionScope具有一係列的屬性,在這裏為了簡單起見,讀采用默認值)

   1: public class TransactionScopeCallHandler : CallHandlerBase
   2: {
   3:     public override object PreInvoke(InvocationContext context)
   4:     {
   5:         return new TransactionScope();
   6:     }
   7:  
   8:     public override void PostInvoke(InvocationContext context, object correlationState)
   9:     {
  10:         TransactionScope transactionScope = (TransactionScope)correlationState;
  11:         if (context.Reply.Exception == null)
  12:         {
  13:             transactionScope.Complete();
  14:         }
  15:         transactionScope.Dispose();
  16:     }
  17: }
  18:  
  19: public class TransactionScopeCallHandlerAttribute : HandlerAttribute
  20: {
  21:     public override ICallHandler CreateCallHandler()
  22:     {
  23:         return new TransactionScopeCallHandler() { Ordinal = this.Ordinal, ReturnIfError = this.ReturnIfError };
  24:     }
  25: }

ExceptionCallHandler

ExceptionCallHandler的MessageTemlate和Rethrow屬性分別表示最終顯示的錯誤信息模板,和是否需要將異常拋出來。由於異常處理發生在目標方法調用之後,所以異常處理邏輯實現在PostInvoke方法中。在這裏,我僅僅將通過模板組裝的出錯消息打印出來而已。 

   1: public class ExceptionCallHandler : CallHandlerBase
   2: {
   3:     public string MessageTemplate{ get; set; }
   4:     public bool Rethrow{ get; set; }
   5:  
   6:     public ExceptionCallHandler()
   7:     {
   8:         this.MessageTemplate = "{Message}";
   9:     }
  10:  
  11:     public override object PreInvoke(InvocationContext context)
  12:     {
  13:         return null;
  14:     }
  15:  
  16:     public override void PostInvoke(InvocationContext context, object correlationState)
  17:     {
  18:         if (context.Reply.Exception != null)
  19:         {
  20:             string message = this.MessageTemplate.Replace("{Message}", context.Reply.Exception.InnerException.Message)
  21:                 .Replace("{Source}", context.Reply.Exception.InnerException.Source)
  22:                 .Replace("{StackTrace}", context.Reply.Exception.InnerException.StackTrace)
  23:                 .Replace("{HelpLink}", context.Reply.Exception.InnerException.HelpLink)
  24:                 .Replace("{TargetSite}", context.Reply.Exception.InnerException.TargetSite.ToString());
  25:             Console.WriteLine(message);
  26:             if (!this.Rethrow)
  27:             {
  28:                 context.Reply = new ReturnMessage(null, null, 0, context.Request.LogicalCallContext, context.Request);
  29:             }
  30:         }
  31:     }
  32: }
  33:  
  34: public class ExceptionCallHandlerAttribute : HandlerAttribute
  35: {
  36:  
  37:     public string MessageTemplate{ get; set; }
  38:  
  39:     public bool Rethrow{ get; set; }
  40:  
  41:     public ExceptionCallHandlerAttribute()
  42:     {
  43:         this.MessageTemplate = "{Message}";
  44:     }
  45:  
  46:     public override ICallHandler CreateCallHandler()
  47:     {
  48:         return new ExceptionCallHandler()
  49:         {
  50:             Ordinal = this.Ordinal,
  51:             Rethrow = this.Rethrow,
  52:             MessageTemplate = this.MessageTemplate,
  53:             ReturnIfError = this.ReturnIfError
  54:         };
  55:     }
  56: }

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 16:04:10

  上一篇:go  [WCF的Binding模型]之三:信道監聽器(Channel Listener)
  下一篇:go  JOIN ON 和 WHERE 條件