第五章 面向方面编程___通知类型
前面两节谈到了 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>