476
技術社區[雲棲]
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 屬性定義一個輸出的模板。運行程序,我們會得到這樣的結果,充分證明了事務的存在,錯誤信息也按照我們希望的模板進行輸出。
二、設計概要
同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的定義。
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