前面兩節談到了 AOP 的概念以及我們使用代理模式來模擬了 AOP ,在代理類中,我們對所有的方法進行了攔截,並沒有做更細的處理。
Spring.Net 中幫我們提供了一套完善的 AOP 框架,對於目前絕大部分的需求都能夠提供完整的支持。Spring.Net 中幫我們提供了多種對方法的攔截的方式,這種對方法進行攔截的方式專業術語又稱 “通知” 。Spring.Net 的通知既可由某個類的所有對象共享,也可由該類型的單個實例獨占。共享的通知稱為基於類型(per-class)的通知,而獨占的通知稱為基於實例(per-instance)的通知。
基於類型的通知最為常用。比如說事務,它就很適合用基於類型的通知實現。它們不依賴於目標對象的狀態,也不會向目標對象添加新狀態,僅僅對方法及其參數進行操作。
基於實例的通知比較適合做引入 ( introductions ) 。此時通知可以向目標對象添加狀態。在 AOP 代理中,可以同時使用基於類型和基於實例的通知。
Spring.Net 幫我們提供了以下通知:
around advice(環繞通知):最常用的通知類型,又稱為方法攔截器。環繞通知繼承自 IMethodInterceptor 接口,在攔截到方法之后,運行程序員在方法調用之前或之后做操作。
before advise(前置通知): 前置通知只在方法調用之前執行,前置通知需要繼承自 IMethodBeforeAdvice 接口。
after returning advise(后置通知): 后置通只在方法調用之后執行,后置通知需要繼承自 IAfterReturningAdvice 接口,如果通知拋出異常,就會沿攔截器鏈向上拋出,從而中斷攔截器鏈的繼續執行。
throws advise(異常通知): 異常通知只在發生異常的情況下執行。
IMethodInterceptor 環繞通知接口:
IMethodBeforeAdvice 前置通知接口 :
IAfterReturningAdvice 后置通知接口 :
IThrowsAdvice 異常通知接口 :
我們還是來上代碼:
有一個銀行卡的接口,其中有兩個方法,存入 Deposit 和 支出 Pay
1 namespace CnblogsLesson_5_2.Interface 2 { 3 public interface ICard 4 { 5 //存入 6 void Deposit(double money); 7 8 //支出 9 void Pay(double money); 10 } 11 }
銀行卡的實現類:
1 using System; 2 using CnblogsLesson_5_2.Interface; 3 4 namespace CnblogsLesson_5_2.Impl 5 { 6 public class Card : ICard 7 { 8 9 /// <summary> 10 /// 存入 11 /// </summary> 12 public void Deposit(double money) 13 { 14 throw new Exception(); 15 Console.WriteLine("存入{0}元", money); 16 } 17 18 /// <summary> 19 /// 支出 20 /// </summary> 21 public void Pay(double money) 22 { 23 Console.WriteLine("支出{0}元",money); 24 } 25 26 } 27 }
Spring.Net 的非侵入性,決定了幾乎所有的功能,都可以通過配置文件配置實現。而要實現 AOP 也是一樣的,也可以通過配置實現。在使用 Spring.Net AOP 功能的時候,需要對項目引入 Spring.Aop.dll 文件。廢話不多說,我們來看一下怎么配置。我們現在要進行方法攔截,攔截到之后做一些事情。我們先得創建一些通知:
一 . 環繞通知
如:環繞通知,我們在攔截到方法之后,可以做一些操作,比如在方法之前輸出一句話或在方法之后做一些事情。我們現在來添加一個 AroundAdvice 類,如下:
1 using AopAlliance.Intercept; 2 using System; 3 4 namespace CnblogsLesson_5_2.Notify 5 { 6 /// <summary> 7 /// 環繞通知 8 /// </summary> 9 public class AroundAdvice : IMethodInterceptor 10 { 11 public object Invoke(IMethodInvocation invocation) 12 { 13 Console.WriteLine("我是環繞通知,參數是{0},我在調用執行方法之前做了一件事!",invocation.Arguments); 14 //執行方法 15 object result = invocation.Proceed(); 16 Console.WriteLine("我是環繞通知,返回值是{0},我在調用執行方法之后做了一件事!",result); 17 return result; 18 } 19 } 20 }
執行程序:
1 using Spring.Context; 2 using Spring.Context.Support; 3 using CnblogsLesson_5_2.Interface; 4 5 namespace CnblogsLesson_5_2 6 { 7 class Program 8 { 9 static void Main(string[] args) 10 { 11 IApplicationContext context = ContextRegistry.GetContext(); 12 13 ICard card = context.GetObject("card") as ICard; 14 15 card.Pay(100); 16 } 17 } 18 }
看一下執行結果,可以看到,card 實例調用 Pay 方法 被攔截到的 環繞通知:
二 . 前置通知
前置通知只在方法調用之前執行 :
1 using Spring.Aop; 2 using System; 3 using System.Reflection; 4 5 namespace CnblogsLesson_5_2.Notify 6 { 7 /// <summary> 8 /// 方法前置通知 9 /// </summary> 10 public class BeforeAdvice : IMethodBeforeAdvice 11 { 12 public void Before(MethodInfo method, object[] args, object target) 13 { 14 Console.WriteLine("方法前置通知調用:方法名稱為" + method.Name + "。參數為" + args + "。目標對象:" + target); 15 } 16 } 17 }
看一下執行結果,可以看到,card 實例調用 Pay 方法 被攔截到的 前置通知:
三 . 后置通知
后置通只在方法調用之后執行,后置通知需要繼承自IAfterReturningAdvice接口,如果通知拋出異常,就會沿攔截器鏈向上拋出,從而中斷攔截器鏈的繼續執行。
1 using System; 2 using Spring.Aop; 3 4 namespace CnblogsLesson_5_2.Notify 5 { 6 public class AfterReturningAdvice : IAfterReturningAdvice 7 { 8 public void AfterReturning(object returnValue, System.Reflection.MethodInfo method, object[] args, object target) 9 { 10 Console.WriteLine("開始調用方法后置通知:返回值為:" + returnValue + "。方法名稱為:" + method.Name + "。參數是:" + args + "。目標對象是:" + target); 11 } 12 } 13 }
看一下執行結果,可以看到,card 實例調用 Pay 方法 被攔截到的 后置通知:
四 . 異常通知
異常通知只在發生異常的情況下執行,我們之前在 Deposit 方法中,手動拋出異常,為 Deposit 方法添加異常通知后,Deposit 方法執行過程中出現異常,將會被異常通知捕獲到。
1 using System; 2 using CnblogsLesson_5_2.Interface; 3 4 namespace CnblogsLesson_5_2.Impl 5 { 6 public class Card : ICard 7 { 8 9 /// <summary> 10 /// 存入 11 /// </summary> 12 public void Deposit(double money) 13 { 14 throw new Exception(); 15 Console.WriteLine("存入{0}元", money); 16 } 17 18 /// <summary> 19 /// 支出 20 /// </summary> 21 public void Pay(double money) 22 { 23 Console.WriteLine("支出{0}元",money); 24 } 25 26 } 27 }
異常通知 類:
1 using System; 2 using Spring.Aop; 3 4 namespace CnblogsLesson_5_2.Notify 5 { 6 /// <summary> 7 /// 異常通知 8 /// </summary> 9 public class ThrowsAdvice : IThrowsAdvice 10 { 11 public void AfterThrowing(Exception ex) 12 { 13 Console.WriteLine("異常被觸發了"); 14 } 15 } 16 }
通過執行程序,可以到看,Deposit 方法手動拋出異常,被異常通知捕獲到:
以上就是常用的四種通知類型,通過下面的配置文件,就可以知道 Spring.Net 中如何來配置它們:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <objects xmlns="http://www.springframework.net"> 3 4 <!--信用卡實例--> 5 <object id="card" type="CnblogsLesson_5_2.Impl.Card,CnblogsLesson_5_2"/> 6 7 <!--配置環繞通知--> 8 <object id="aroundAdvice" type="CnblogsLesson_5_2.Notify.AroundAdvice, CnblogsLesson_5_2"></object> 9 10 <!--配置前置通知--> 11 <object id="beforeAdvice" type="CnblogsLesson_5_2.Notify.BeforeAdvice, CnblogsLesson_5_2"></object> 12 13 <!--配置后置通知--> 14 <object id="afterReturningAdvice" type="CnblogsLesson_5_2.Notify.AfterReturningAdvice, CnblogsLesson_5_2"></object> 15 16 <!--配置異常通知--> 17 <object id="throwsAdvice" type="CnblogsLesson_5_2.Notify.ThrowsAdvice, CnblogsLesson_5_2"></object> 18 19 <!--配置AOP代理對象--> 20 <object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> 21 <!--代理的目標對象列表,如目前只代理了card對象--> 22 <property name="ObjectNames"> 23 <list> 24 <!--這里可能需要代理的對象太多,Spring.Net 幫我們提供了通配符的匹配方式,如:"*name","name*",”*name*“和精確文本如"name"。而且還提供了正則表達式的匹配方式,這里就不舉例了--> 25 <value>car*</value> 26 </list> 27 </property> 28 <!--AOP代理對象中,使用的通知實例--> 29 <property name="InterceptorNames"> 30 <list> 31 <value>aroundAdvice</value> 32 <value>beforeAdvice</value> 33 <value>afterReturningAdvice</value> 34 <value>throwsAdvice</value> 35 </list> 36 </property> 37 38 </object> 39 40 </objects>