程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Enterprise Library深入解析與靈活應用(6):自己動手創建迷你版AOP框架

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

編輯:關於.NET

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

一、如何使用?

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

namespace Artech.SimpleAopFramework
{
  class Program
  {
    static void Main (string[] args)
    {
      string userID = Guid.NewGuid().ToString();
       InstanceBuilder.Create<UserManager, IUserManager>().CreateDuplicateUsers(userID, Guid.NewGuid().ToString());
       Console.WriteLine("Is the user whose ID is \"{0}\" has been successfully created! {1}", userID, UserUtility.UserExists(userID)?"Yes":"No");
    }
  }
  public class UserManager : IUserManager
  {
    [TransactionScopeCallHandler(Ordinal = 1)]
    [ExceptionCallHandler(Ordinal = 2, MessageTemplate = "Encounter error:\nMessage:{Message}")]
    public void CreateDuplicateUsers(string userID, string userName)
    {
      UserUtility.CreateUser(userID, userName);
       UserUtility.CreateUser(userID, userName);
    }
  }
  public interface IUserManager
  {
     void CreateDuplicateUsers(string userID, string userName);
  }
}

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

在ExceptionCallHandler中,會對拋出的 SqlException進行處理,在這我們僅僅是打印出異常相關的信息。至於具有要輸出那些信息,可以通過ExceptionCallHandlerAttribute的 MessageTemplate 屬性定義一個輸出的模板。

運行程序,我們會得到這樣的結果,充分證明了事務的存在,錯誤信息也按照我們希望的 模板進行輸出。

二、設計概要

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

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的定義。

1、 InvocationContext

namespace Artech.SimpleAopFramework
{
  public class InvocationContext
   {
    public IMethodCallMessage Request
    { get; set; }
    public ReturnMessage Reply
     { get; set; }
    public IDictionary<object, object> Properties
    { get; set; }
  }
}

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

2、ICallHandler、CallHandlerBase和HandlerAttribute

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

namespace Artech.SimpleAopFramework
{
  public interface ICallHandler
  {
    object PreInvoke (InvocationContext context);
    void PostInvoke(InvocationContext context, object correlationState);
     int Ordinal
    { get; set; }
    bool ReturnIfError
    { get; set; }
  }
}

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

namespace Artech.SimpleAopFramework
{
  public abstract class CallHandlerBase : ICallHandler
  {
     #region ICallHandler Members
    public abstract object PreInvoke(InvocationContext context);
    public abstract void PostInvoke(InvocationContext context, object correlationState);
    public int Ordinal
    { get; set; }
    public bool ReturnIfError
    { get; set; }
    #endregion
  }
}

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

namespace Artech.SimpleAopFramework
{
  public abstract class HandlerAttribute : Attribute
   {
    public abstract ICallHandler CreateCallHandler();
    public int Ordinal
    { get; set; }
    public bool ReturnIfError
    { get; set; }
  }
}

3、CallHandlerPipeline

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

namespace Artech.SimpleAopFramework
{
  public class CallHandlerPipeline
  {
    private object _target;
    private IList<ICallHandler> _callHandlers;
    public CallHandlerPipeline(object target)
       : this(new List<ICallHandler>(), target)
    { }
    public CallHandlerPipeline (IList<ICallHandler> callHandlers, object target)
    {
      if (target == null)
       {
        throw new ArgumentNullException("target");
      }
      if (callHandlers == null)
      {
        throw new ArgumentNullException("callHandlers");
      }
      this._target = target;
      this._callHandlers = callHandlers;
    }
    public void Invoke(InvocationContext context)
    {
      Queue<object> correlationStates = new Queue<object>();
      Queue<ICallHandler> callHandlerQueue = new Queue<ICallHandler> ();
      //Preinvoke.
      foreach (ICallHandler callHandler in this._callHandlers)
       {
        correlationStates.Enqueue(callHandler.PreInvoke(context));
        if (context.Reply != null && context.Reply.Exception != null && callHandler.ReturnIfError)
        {
           context.Reply = new ReturnMessage(context.Reply.Exception, context.Request);
          return;
        }
        callHandlerQueue.Enqueue(callHandler);
      }
      //Invoke Target Object.
      object[] copiedArgs = Array.CreateInstance(typeof(object), context.Request.Args.Length) as object[];
      context.Request.Args.CopyTo(copiedArgs, 0);
      try
      {
         object returnValue = context.Request.MethodBase.Invoke(this._target, copiedArgs);
        context.Reply = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, context.Request.LogicalCallContext, context.Request);
       }
      catch (Exception ex)
      {
        context.Reply = new ReturnMessage (ex, context.Request);
      }
      //PostInvoke.
      while (callHandlerQueue.Count > 0)
      {
        ICallHandler callHandler = callHandlerQueue.Dequeue();
        object correlationState = correlationStates.Dequeue();
        callHandler.PostInvoke(context, correlationState);
      }
    }
    public void Sort()
    {
    }
    public void Combine (CallHandlerPipeline pipeline)
    {
    }
    public void Combine(IList<ICallHandler> callHandlers)
    {
    }
    public ICallHandler Add(ICallHandler callHandler)
    {
    }
  }
}

4、InterceptionRealProxy<T>

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

namespace Artech.SimpleAopFramework
{
  public class InterceptingRealProxy<T> : RealProxy
  {
     private IDictionary<MemberInfo, CallHandlerPipeline> _callHandlerPipelines;
    public InterceptingRealProxy (object target, IDictionary<MemberInfo, CallHandlerPipeline> callHandlerPipelines)
      : base(typeof(T))
    {
      if (callHandlerPipelines == null)
      {
        throw new ArgumentNullException("callHandlerPipelines");
      }
      this._callHandlerPipelines = callHandlerPipelines;
    }
    public override IMessage Invoke(IMessage msg)
    {
       InvocationContext context = new InvocationContext();
      context.Request = (IMethodCallMessage)msg;
       this._callHandlerPipelines[context.Request.MethodBase].Invoke(context);
      return context.Reply;
    }
  }
}

5、InstanceBuidler

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

namespace Artech.SimpleAopFramework
{
   public class InstanceBuilder
  {
    public static TInterface Create<TObject, TInterface>() where TObject : TInterface
    {
      TObject target = Activator.CreateInstance<TObject>();
       object[] attributes = typeof(TObject).GetCustomAttributes(typeof(HandlerAttribute), true);
       IList<ICallHandler> callHandlers = new List<ICallHandler>();
      foreach (var attribute in attributes)
      {
        HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
        callHandlers.Add(handlerAttribute.CreateCallHandler());
      }
       InterceptingRealProxy<TInterface> realProxy = new InterceptingRealProxy<TInterface>(target, CreateCallHandlerPipeline<TObject, TInterface>(target));
      return (TInterface) realProxy.GetTransparentProxy();
    }
    public static T Create<T>()
    {
       return Create<T, T>();
    }
    public static IDictionary<MemberInfo, CallHandlerPipeline> CreateCallHandlerPipeline<TObject, TInterfce>(TObject target)
    {
      CallHandlerPipeline pipeline = new CallHandlerPipeline(target);
      object[] attributes = typeof(TObject).GetCustomAttributes (typeof(HandlerAttribute), true);
      foreach (var attribute in attributes)
      {
         HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
        pipeline.Add (handlerAttribute.CreateCallHandler());
      }
      IDictionary<MemberInfo, CallHandlerPipeline> kyedCallHandlerPipelines = new Dictionary<MemberInfo, CallHandlerPipeline>();
       foreach (MethodInfo methodInfo in typeof(TObject).GetMethods())
      {
        MethodInfo declareMethodInfo = typeof(TInterfce).GetMethod(methodInfo.Name, BindingFlags.Public | BindingFlags.Instance);
         if (declareMethodInfo == null)
        {
          continue;
        }
        kyedCallHandlerPipelines.Add(declareMethodInfo, new CallHandlerPipeline(target));
         foreach (var attribute in methodInfo.GetCustomAttributes(typeof(HandlerAttribute), true))
        {
           HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
           kyedCallHandlerPipelines[declareMethodInfo].Add(handlerAttribute.CreateCallHandler());
        }
         kyedCallHandlerPipelines[declareMethodInfo].Combine(pipeline);
        kyedCallHandlerPipelines [declareMethodInfo].Sort();
      }
      return kyedCallHandlerPipelines;
    }
  }
}

四、如果創建自定義CallHandler

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

1、 TranactionScopeCallHandler

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

namespace Artech.SimpleAopFramework.Demos
{
  public class TransactionScopeCallHandler : CallHandlerBase
  {
     public override object PreInvoke(InvocationContext context)
    {
      return new TransactionScope();
    }
    public override void PostInvoke(InvocationContext context, object correlationState)
    {
      TransactionScope transactionScope = (TransactionScope)correlationState;
      if (context.Reply.Exception == null)
      {
        transactionScope.Complete();
      }
      transactionScope.Dispose();
    }
  }
  public class TransactionScopeCallHandlerAttribute : HandlerAttribute
  {
    public override ICallHandler CreateCallHandler()
    {
      return new TransactionScopeCallHandler() { Ordinal = this.Ordinal, ReturnIfError = this.ReturnIfError };
    }
  }
}

2、ExceptionCallHandler

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

namespace Artech.SimpleAopFramework.Demos
{
  public class ExceptionCallHandler : CallHandlerBase
  {
    public string MessageTemplate
    { get; set; }
    public bool Rethrow
    { get; set; }
    public ExceptionCallHandler()
    {
       this.MessageTemplate = "{Message}";
    }
    public override object PreInvoke(InvocationContext context)
    {
      return null;
    }
    public override void PostInvoke (InvocationContext context, object correlationState)
    {
      if (context.Reply.Exception != null)
      {
        string message = this.MessageTemplate.Replace("{Message}", context.Reply.Exception.InnerException.Message)
        .Replace("{Source}", context.Reply.Exception.InnerException.Source)
        .Replace("{StackTrace}", context.Reply.Exception.InnerException.StackTrace)
        .Replace("{HelpLink}", context.Reply.Exception.InnerException.HelpLink)
        .Replace("{TargetSite}", context.Reply.Exception.InnerException.TargetSite.ToString());
        Console.WriteLine(message);
         if (!this.Rethrow)
        {
          context.Reply = new ReturnMessage(null, null, 0, context.Request.LogicalCallContext, context.Request);
        }
      }
    }
  }
  public class ExceptionCallHandlerAttribute : HandlerAttribute
  {
    public string MessageTemplate
    { get; set; }
    public bool Rethrow
    { get; set; }
    public ExceptionCallHandlerAttribute()
    {
      this.MessageTemplate = "{Message}";
    }
    public override ICallHandler CreateCallHandler()
    {
      return new ExceptionCallHandler()
      {
        Ordinal = this.Ordinal,
        Rethrow = this.Rethrow,
        MessageTemplate = this.MessageTemplate,
        ReturnIfError = this.ReturnIfError
      };
    }
  }
}

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved