AOP - PostSharp 2.0
PostSharp是一個非常優秀的AOP框架,使用上非常方便,功能強大,對目標攔截的方法不需要做什么修改,但現在已經商業化運作
從PostSharp官方網站下載一個試用版,安裝
簡單示例
PostSharp采用Post-Compile的方式實現AOP,即對已經生成的程序集,按照攔截規則進行修改,對需要攔截的方法注入攔截代碼。這種方式與基于動態代理的方式相比,沒有過多限制,比如不需要目標方法為virtual類型或者實現了接口等
1. 新建一個PostSharp.Test的Console測試項目添加引用: PostSharp、PostSharp.Laos
2. 程序引用的命名空間
using System;
using System.Reflection;
using PostSharp.Laos;
3. 測試用的代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private?static?void?SayHello(string?user, string?title) { ????Console.WriteLine(string.Format("Hello {0} {1}", title, user)); } private?static?void?ThrowException() { ????throw?new?Exception("I'm a test message."); } static?void?Main(string[] args) { ????SayHello("Richie", "Mr."); ????try ????{ ????????ThrowException(); ????} ????catch ????{ ????} ????Console.ReadKey(); ????return; } |
4. 然后在項目中實現一個aspect:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | [Serializable] public?class?OnMyExceptionAspect : OnMethodBoundaryAspect { ????public?override?void?OnEntry(MethodExecutionEventArgs eventArgs) ????{ ????????base.OnEntry(eventArgs); ????????Console.WriteLine(string.Format("Entering method: {0}", eventArgs.Method.Name)); ????????object[] arguments = eventArgs.GetReadOnlyArgumentArray(); ????????ParameterInfo[] parameters = eventArgs.Method.GetParameters(); ????????for?(int?i = 0; arguments != null?&& i < arguments.Length; i++) ????????????Console.WriteLine(string.Format(" arg{0} {1}: {2}", i + 1, parameters[i].Name, arguments[i])); ????} ????public?override?void?OnExit(MethodExecutionEventArgs eventArgs) ????{ ????????base.OnExit(eventArgs); ????????Console.WriteLine(string.Format("Exiting method: {0}", eventArgs.Method.Name)); ????} ????public?override?void?OnException(MethodExecutionEventArgs eventArgs) ????{ ????????Console.WriteLine("There's an error occured:"?+ eventArgs.Exception.Message); ????????base.OnException(eventArgs); ????} } |
我們使用這個aspect對測試代碼進行攔截處理,輸出方法調用的日志信息
5. 接下來的2個步驟就是設置項目,使得PostSharp在編譯結束后能夠對生成的程序集進行修改,假如攔截處理機制
?? ?a). 在項目中添加一個assembly指令,告訴PostSharp對哪些目標實施攔截
[assembly: PostSharp.Test.OnMyExceptionAspect(
?? ?AttributeTargetAssemblies = "PostSharp.Test",
?? ?AttributeTargetTypes = "PostSharp.Test.Program")]
?? ?b). 修改項目文件,讓PostSharp在編譯完成后能夠注入攔截代碼:
?? ?先通過VS的右鍵菜單卸載項目
?? ?
?? ?然后通過VS郵件菜單編輯項目文件,添加一個import指令:
?? ?
?? ?在<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />后面添加一條import指令如下:
?? ?<Import Project="E:\Program Files\PostSharp 2.0\PostSharp.targets" />
6. 重新加載項目,編譯,PostSharp就會在我們指定的位置上使用我們的OnMyExceptionAspect對Program的方法調用注入攔截代碼,運行結果如下:
?? ?
使用Attribute方式
上面使用一個assembly指令告訴PostSharp應該對哪些方法實施攔截,OnMethodBoundaryAspect類已經繼承了Attribute,因此我們可以通過給類或者方法添加Attribute的方式來代替assembly指令
下面示例需要引用的命名空間:
我們用下面的PropertyChangedNotificationAttribute來實現對象屬性更改通知:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | [Serializable] [MulticastAttributeUsage(MulticastTargets.Method)] public?class?PropertyChangedNotificationAttribute : OnMethodBoundaryAspect { ????private?object?_preValue; ????public?override?void?OnExit(MethodExecutionEventArgs eventArgs) ????{ ????????base.OnExit(eventArgs); ????????if?(!eventArgs.Method.IsSpecialName) return; ????????if?(!eventArgs.Method.Name.StartsWith("get_") ????????????&& !eventArgs.Method.Name.StartsWith("set_")) return; ????????bool?isSetter = eventArgs.Method.Name.StartsWith("set_"); ????????string?property = eventArgs.Method.Name.Substring(4); ????????if?(isSetter) ????????????Console.WriteLine(string.Format("Property \"{0}\" was changed from \"{1}\" to \"{2}\"." ????????????????, property ????????????????, this._preValue ????????????????, this.GetPropertyValue(eventArgs.Instance, property))); ????????else ????????????Console.WriteLine(string.Format("Property \"{0}\" was read.", property)); ????} ????public?override?void?OnEntry(MethodExecutionEventArgs eventArgs) ????{ ????????base.OnEntry(eventArgs); ????????//記錄屬性更改前的值 ????????if?(!eventArgs.Method.IsSpecialName) return; ????????if?(!eventArgs.Method.Name.StartsWith("set_")) return; ????????string?property = eventArgs.Method.Name.Substring(4); ????????this._preValue = this.GetPropertyValue(eventArgs.Instance, property); ????} ????private?object?GetPropertyValue(object?instance, string?property) ????{ ????????PropertyInfo getter = instance.GetType().GetProperty(property); ????????return?getter.GetValue(instance, null); ????} } |
然后給測試類添加PropertyChangedNotification的attribute就能通過AOP實現對象屬性更改通知的效果:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | [PropertyChangedNotification] public?class?Person { ????private?string?firstName; ????private?string?lastName; ????public?Person(string?first, string?last) ????{ ????????this.firstName = first; ????????this.lastName = last; ????} ????public?string?FirstName ????{ ????????get?{ return?firstName; } ????????set?{ firstName = value; } ????} ????public?string?LastName ????{ ????????get?{ return?lastName; } ????????set?{ lastName = value; } ????} ????public?string?Name ????{ ????????get?{ return?this.FirstName + " "?+ this.LastName; } ????} } <BR> static?void?Main(string[] args) { ????Person user = new?Person("Richie", "Liu"); ????user.FirstName = "RicCC"; ????Console.WriteLine(string.Format("{{ {0} {1} }}", user.FirstName, user.LastName)); ????Console.ReadKey(); } |
編譯生成,運行結果如下:
?? ?
因為我們在Property的setter方法進入(OnEntry)時用反射讀取了屬性的值,記錄修改之前的屬性值;然后在setter方法結束(OnExit)時又使用反射去讀取了修改之后的屬性值,因此在***was changed from *** to ***這條消息之前,有2條屬性的讀取日志
Cache示例
PostSharp中有個緩存方法調用的示例:第一次調用方法的時候,執行這個方法,并把執行結果緩存起來;后面再調用這個方法時,就直接從緩存中取結果,讓方法返回,而不執行方法體
實現這一功能的aspect類如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | [Serializable] public?sealed?class?CacheAttribute : OnMethodBoundaryAspect { ????// 用來生成緩存的key值,key值中包含方法名、參數值等,因此參數不一時方法會被執行 ????private?MethodFormatStrings formatStrings; ????// 用于緩存 ????private?static?readonly?Dictionary<STRING, object=""> cache = new?Dictionary<STRING, object="">(); <BR> ????// 編譯時刻執行的方法 ????// Cache這個attribute用于方法上,某些方法不允許使用緩存,將在下面進行檢查 ????public?override?bool?CompileTimeValidate(MethodBase method) ????{ ????????// Don't apply to constructors. ????????if?(method is?ConstructorInfo) ????????{ ????????????Message.Write(SeverityType.Error, "CX0001", "Cannot cache constructors."); ????????????return?false; ????????} ????????MethodInfo methodInfo = (MethodInfo)method; ????????// Don't apply to void methods. ????????if?(methodInfo.ReturnType.Name == "Void") ????????{ ????????????Message.Write(SeverityType.Error, "CX0002", "Cannot cache void methods."); ????????????return?false; ????????} ????????// Does not support out parameters. ????????ParameterInfo[] parameters = method.GetParameters(); ????????for?(int?i = 0; i < parameters.Length; i++) ????????{ ????????????if?(parameters[i].IsOut) ????????????{ ????????????????Message.Write(SeverityType.Error, "CX0003", "Cannot cache methods with return values."); ????????????????return?false; ????????????} ????????} ????????return?true; ????} ????// 編譯時刻執行的方法 ????public?override?void?CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) ????{ ????????this.formatStrings = Formatter.GetMethodFormatStrings(method); ????} <BR> ????public?override?void?OnEntry(MethodExecutionArgs eventArgs) ????{ ????????//生成緩存的key值 ????????string?key = this.formatStrings.Format( ????????????eventArgs.Instance, eventArgs.Method, eventArgs.Arguments.ToArray()); ????????lock?(cache) ????????{ ????????????object?value; ????????????//查看是否存在緩存 ????????????if?(!cache.TryGetValue(key, out?value)) ????????????????// 緩存不存在,繼續執行這個方法,并將key存在MethodExecutionTag,在執行完畢的 ????????????????// OnSuccess事件時使用key值將結果放到緩存中 ????????????????eventArgs.MethodExecutionTag = key; ????????????else ????????????{ ????????????????// 已經在緩存中存在,則將執行的返回值直接設置為緩存中的值,不執行方法體而立即返回 ????????????????eventArgs.ReturnValue = value; ????????????????eventArgs.FlowBehavior = FlowBehavior.Return; ????????????} ????????} ????} <BR> ????//這個事件只有在方法體被執行了,并且執行成功沒有異常發生時才會觸發 ????public?override?void?OnSuccess(MethodExecutionArgs eventArgs) ????{ ????????// 取得緩存的key值 ????????string?key = (string)eventArgs.MethodExecutionTag; ????????// 把執行結果放入緩存中 ????????lock?(cache) ????????{ ????????????cache[key] = eventArgs.ReturnValue; ????????} ????} }</STRING,></STRING,> |
測試代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public?static?void?Main( string[] args ) { ????Console.WriteLine( "1 ->"?+ GetDifficultResult( 1 ) ); ????Console.WriteLine( "2 ->"?+ GetDifficultResult( 2 ) ); ????Console.WriteLine( "1 ->"?+ GetDifficultResult( 1 ) ); ????Console.WriteLine( "2 ->"?+ GetDifficultResult( 2 ) ); ????Console.ReadKey(); } [Cache] private?static?int?GetDifficultResult( int?arg ) { ????// 如果方法體被執行了,則會輸出下面的消息,否則不會輸出,說明使用了緩存 ????Console.WriteLine( "Some difficult work!"?); ????Thread.Sleep( 1000 ); ????return?arg; } |
參數為1和2的時候分別執行以下方法體,后面2次調用則都是從緩存中直接取返回值了
DbInvoke示例
這也是PostSharp中一個比較有意思的示例,其工作方式大致如下,先使用下面代碼聲明一些方法:
| 1 2 3 4 5 6 7 8 9 10 | [DbInvoke("ConnectionString")] internal?static?class?DataLayer { #pragma warning disable 626 ????extern?static?public?void?CreateCustomer(string?customerName, out?int?customerId); ????extern?static?public?void?ModifyCustomer(int?customerId, string?customerName); ????extern?static?public?void?DeleteCustomer(int?customerId); ????extern?static?public?void?ReadCustomer(int?customerId, out?string?customerName); #pragma warning restore 626 } |
這里每個方法代表一個數據庫的存儲過程,通過DbInvoke這個attribute實施攔截(DbInvoke繼承自PostSharp.Aspects.ImplementMethodAspect,PostSharp直接使用這個類的OnExecution方法代替對原方法的調用,他用于實現對extern方法、abstract類的方法提供攔截實現),然后將方法名作為存儲過程名字,參數名字則作為存儲過程參數名,將.NET數據類型轉化為數據庫的數據類型,執行存儲過程,并處理out、ref類型的參數,將存儲過程執行結果的參數值設置到方法的相關參數上
通過這樣的方式,將數據庫的存儲過程聲明為.NET中的extern方法,簡化對存儲過程的調用方式
Aspect的類型
1.?OnMethodBoundaryAspect
?? ?可以override OnEntry、OnExit、OnSuccess、OnException等方法實施攔截,可以讀取入參,修改ref、out類型的入參,決定是否調用被攔截的方法體,以及讀取、修改方法的返回值等,一般可以用于自己編寫的assembly,進行日志記錄等操作
?? ?PostSharp對原方法注入一個try{}catch(){}語句,在適當的位置注入各個攔截事件的調用代碼
?? ?當對同一個方法使用多個該類型的aspect時,可以通過設置或者實現AspectPriority來確定各個攔截器的執行順序
?? ?類似于前面的Cache示例中使用到的,在aspect之間或者攔截的各個方法之間,可以通過MethodExecutionEventArgs的MethodExecutionTag屬性來傳遞必要的狀態信息
2.?OnExceptionAspect
?? ?用于實現異常捕獲,可以運用于第三方開發的,沒有源代碼的assembly上
3.?OnFieldAccessAspect
?? ?方便對field的讀取、設置進行攔截處理,override?OnGetValue、OnSetValue方法實施攔截。測試過程中無法讀取到FieldInfo屬性,不知道是不是只有商業版注冊后才可以使用這個功能
?? ?對field訪問的攔截只能適用于當前程序集,如果其他程序集直接誒訪問field無法實現攔截,所以對于public、protected類型的field,PostSharp會將field重命名,然后自動生成一個原field名字的property,這會導致依賴的程序集二進制兼容性被破壞,需要重新編譯。這個行為也可以通過選項進行配置,阻止PostSharp這樣做
4.?OnMethodInvocationAspect
?? ?override OnInvocation方法實施攔截,PostSharp不是直接修改注入目標程序集,而是為目標方法生成一個委托,修改當前程序集中的調用,改為調用委托,從而實現攔截(這種方式叫做Call-site weaving,調用方織入,對應的另一種方式叫做Target-site weaving,目標織入)。這種方式可以實現對第三方程序集方法實施攔截
5.?ImplementMethodAspect
?? ?如前面的DbInvoke示例,這個aspect用于extern方法、abstract類的方法進行攔截,不要求目標方法有具體的實現
Aspect的生命周期
aspect在編譯期實例化,PostSharp將aspect的實例序列化存到assembly中,在運行時再反序列化回來
對multicast類型的attribute,PostSharp會為每個匹配到的類型、方法等單獨創建一個該attribute的實例對象應用于目標上
multicast概念:本來我們寫一個custom attribute,必須在每個需要運用的方法、類型上面使用這個attribute,PostSharp中的multicast指可以指定比較寬的一個范圍,或者使用正則表達式以及一些filter等,將這個attribute應用到匹配到的多個目標對象上面去,類似于多播這樣的效果。這就是在前面使用過的assembly指令
這個概念導致的一個結果,對于理解PostSharp的行為比較重要,如果某個multicast類型的attribute指定為作用于field,但又將這個attribute設置在了type上面,則PostSharp會為該type的所有field運用這個attribute,從而運用攔截處理
另外,PostSharp的示例Advanced\AssemblyExplorer項目演示了如何使用PostSharp的CodeModel。PostSharp沒有使用Mono.Cecil和CCI等開源項目,而是建立了自己的CodeModel來分析和修改assembly的元數據以及IL代碼等,通過這個示例可以大致了解如何使用PostSharp的CodeModel來實現某些元數據、IL層面的操作
參考:
Aspect Oriented Programming 101
本文轉自 netcorner 博客園博客,原文鏈接:?http://www.cnblogs.com/netcorner/p/3756585.html? ,如需轉載請自行聯系原作者
http://www.cnblogs.com/netcorner/p/3756585.html
總結
以上是生活随笔為你收集整理的AOP - PostSharp 2.0的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uml 笔记
- 下一篇: java-第七章-数组-循环输出