基於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
};
}
}
}
本文配套源碼