单从Advice(通知)实现AOP
如果你在实际开发中没感觉到OOP的一些缺陷,就不要往下看了!
如果你不了解AOP,或类似AOP的思路,请先去了解一下AOP相关的认识。
如果你是概念党,或是经验党,或是从众党,也请不要看了!
我实现的只是一个小功能,是不是AOP我并不清楚,也不主要,标题那样写只是让大家一看就明白本文讲的大概是什么,况且我也想不出写个什么标题。
因为我看了别人谈论的AOP,扯到好多东西,代理啦、emit啦等等天花乱坠的,不过一个也没看懂(I hate complex)!
但有一点对我很有用,就是几种通知类型:
1,目标方法调用前(before)
2,目标方法调用后(after)---目标方法异常也执行
3,目标方法返回后(after return)---目标方法异常不执行
3,目标方法调用前后(around)
4,目标方法抛出异常时(throw)
我感觉稍微整理一下更好理解,所以下方的实现是基于这6种类型的:
1,目标方法调用前(Before)
2,目标方法调用后(After)--目标方法异常是不执行的!
3,目标方法调用后(AfterEnsure) --目标方法异常也执行。
4,目标方法调用前后(Around)--目标方法异常,After是不执行的
5,目标方法调用前后(AroundEnsure)--目标方法异常,After也执行
6,目标方法抛出异常时(Throw)
考虑到多种通知处理,就需要一个统一接口(泛指接口), 而这里抽象类比.net接口更合适:(这个组件只有两个类,这是其中之一)
- public abstract class AspectAdvice
- {
- public virtual void Before(object target, MethodInfo mi, params object[] args) { }
- public virtual void After(object target, MethodInfo mi, params object[] args) { }
- public virtual void Throw(object target, MethodInfo mi, params object[] args) { }
- }
三个方法就够了,这三个方法与目标方法不同的调用逻辑可以满足上面6种通知,下面再分析。
为了好描述,我也以日志记录为例,下面分别是目标类,和日志类(可以叫它切面吗?呵呵)
- public class MyClass
- {
- //无返回值
- public void Act()
- {
- Console.WriteLine("MyClass.Act()");
- }
- //有返回值
- public int Fun(string str)
- {
- return str.Length;
- }
- ;
- //用到对象成员m_num,可能发生异常
- public int Divide(int n)
- {
- return m_num / n;
- }
- //静态方法
- public static void SFun(string str)
- {
- Console.WriteLine(str);
- }
- }
通知处理类都要实现AspectAdvice, 如日志类:
- public class Log : AspectAdvice
- {
- public override void Before(object target, MethodInfo mi, params object[] args)
- {
- Console.WriteLine("===========Log Before===========");
- }
- public override void After(object target, MethodInfo mi, params object[] args)
- {
- Console.WriteLine("===========Log After===========");
- }
- public override void Throw(object target, MethodInfo mi, params object[] args)
- {
- Console.WriteLine("===========Log Exception===========");
- }
- private Log() { }
- static Log m_instance;
- public static Log Instance
- {
- get
- {
- if (m_instance == null)
- m_instance = new Log();
- return m_instance;
- }
- }
- }
请不要纠结用单件实例化,在这里只是单件适合一点,其实一个Log对象是可以做为容器上下文的,比如在用Around通知时,可以在Before方法里对Log对象的一个成员(上面没给出)赋值,After方法可以使用。
如何调用呢?直接调用一定是这样的:
现在就要去实现组件的第二个也是最后一个类了,AspectInvoker类,AspectInvoker类很大,1700多行, 但也很简单,只有6个方法,每个方法有34个重载,就不贴全码了,最后给两个类的下载。
先来个简单的调用一睹为快:
是不是已经有点意思了呢?AspectInvoker.Beore有两个参数,第一个是AspectAdvice的子类,这里是个Log类的对象,第二个是一个Action委托,这里是myClass.Act方法。AspectInvoker.Beore方法是超级简单的:
- public static void Before(AspectAdvice advice, Action act)
- {
- advice.Before(act.Target, act.Method);
- act();
- }
怎么样?最复杂的思路已经完成了!执行上有效率问题吗?
再来个调用MyClass类的Fun方法的,这个方法有个参数,有个返回值:
这个是Around的调用,第一个参数不变,第二个参数不变,因为myClass.Fun有参数,所以第三个是myClass.Fun的第一个参数(如果有多个,以次往后写就是了),最后一个输出参数是myClas.Fun的返回值。AspectInvoker.Around的实现只是比猫画虎罢了,前面说了,最复杂的思路已经完成!
- public static void Around<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
- {
- advice.Before(fun.Target, fun.Method, t);
- result = fun(t);
- advice.After(fun.Target, fun.Method, t);
- }
再来个抛异常的,这个可以用AspectInvoker.AfterEnsure或AspectInvoker.Throw来测试,下图是AspectInvoker.AfterEnsure的结果:
异常发生了,Log.After也执行了,这就是AspectInvoker.AfterEnsure,确保了After的执行。如果用AspectInvoker.Throw,只有异常时才执行Log.Throw,调用方法的实现还是画虎罢了:
- public static void AfterEnsure<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
- {
- try
- {
- result = fun(t);
- }
- finally
- {
- advice.After(fun.Target, fun.Method, t);
- }
- }
- public static void Throw<T, TResult>(AspectAdvice advice, Func<T, TResult> fun, T t, out TResult result)
- {
- try
- {
- result = fun(t);
- }
- catch (Exception exp)
- {
- advice.Throw(fun.Target, fun.Method, t);
- throw exp;
- }
- }
最后还有MyClass.SFun的静态方法,这里就不多说了,说多了都是Repeat!
总结:
1,AspectInvoker类的6个调用方法都可以调用参数在17个以下的目标方法,因为泛型Action和泛型Func最多都是16个,所以我也写到了16个(这个完全不是限制)。
2,AspectInvoker类不能调用有out参数和ref参数的目标方法。
3,这个组件是简单的,是高效的,和现有的AOP框架肯定是有出入的!请不要和它们对比,这个组件的目的不在于此!
4,MyClass类不用包装,不用继承什么,AspectInvoker关注的只是方法,不管是静态方法还是实例方法,不管是你的方法还是我的方法....
5,你真的知道怎么用了吧?大多数人是没问题的,我担心的只是不爱思考的同学:
1),Log的实现现在是数据库,随时换成记录到文本没问题的
2),AspectAdvice的方法都有三个参数,第一个是目标方法的对象target(如果是静态方法,target是null),第二个是目标方法的MethodInfo,第三个是目标方法的参数数组
比如target类型就可以做些事情等等。
3)
6,下载
完...
2013-09-27修改:
把调用也写到AspectAdvice,调用方法有所改善,返回值不用out参数,而是直接返回,上述例子中新的调用如下:
- static void Main(string[] args)
- {
- MyClass myClass = new MyClass();
- Log.Instance.BeforeAction(myClass.Act);
- int len = Log.Instance.AroundFunc(myClass.Fun, "a string param");
- Console.WriteLine(len);
- );
- Console.WriteLine(result);
- }
注:为什么要分开Action和Func的调用方法名称呢,因为Action<T,T>和Func<T,T,TResult>在C#里无法区分,方法签名不包括返回值,这点使人不是很爽!
单从Advice(通知)实现AOP的更多相关文章
- Spring源码情操陶冶-AOP之Advice通知类解析与使用
阅读本文请先稍微浏览下上篇文章Spring源码情操陶冶-AOP之ConfigBeanDefinitionParser解析器,本文则对aop模式的通知类作简单的分析 入口 根据前文讲解,我们知道通知类的 ...
- iOS 页面间几种传值方式(属性,代理,block,单例,通知)
第二个视图控制器如何获取第一个视图控制器的部分信息 例如 :第二个界面中的lable显示第一个界面textField中的文本 这就需要用到属性传值.block传值 那么第一个视图控制器如何获的第二个视 ...
- iOS--页面间的代理传值(属性、代理(委托)、代码块、单例、通知)
(一)属性传值 (二)代理(委托)传值 代理传值 适用于 反向传值 (从后往前传) 1.1 创建协议 及协议方法 在反向传值的页面(SecondViewController)中 1.2 创建协议类型的 ...
- iOS传值方式:属性,代理,block,单例,通知
正向传值均可,反向传值除属性传值不可,其余均可.下面简单介绍: (一)属性传值 第二个界面中的lable显示第一个界面textField中的文本 首先我们建立一个RootViewControllers ...
- 仿照Spring自己实现有各种通知的AOP,AOP实现的步骤分解
一.需求: 仿照Spring的AOP写的 MyAOP 2.0,有环绕通知.前置通知.后置通知.返回通知.异常通知等. 已实现:①通过动态代理+通知的注解类,实现了前置通知.后置通知等各种通知:②切点( ...
- 日志之环绕通知(AOP)
环绕通知:一个完整的try...catch...finally结构 编写环绕通知方法,环绕通知需要携带ProceedingJoinPoint 这个类型的参数,ProceedingJoinPoint类型 ...
- Spring总结六:AOP(面向切面编程)
概述: AOP(Aspect-Oriented Programming,面向切面的编程),它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.它是一种新的 ...
- SpringAOP+RabbitMQ+WebSocket实战
背景 最近公司的客户要求,分配给员工的任务除了有微信通知外,还希望PC端的网页也能实时收到通知.管理员分配任务是在我们的系统A,而员工接受任务是在系统B.两个系统都是现在已投入使用的系统. 技术选型 ...
- Spring源码学习
Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...
随机推荐
- Html5拖拽复制
拖拽是一种常见的特性,即抓取对象以后拖到另一个位置. 在 HTML5 中,拖拽是标准的一部分,任何元素都能够拖拽. Html5拖拽非常常见的一个功能,但是大部分拖拽的案例都是一个剪切的过程, 项目中需 ...
- 关于CocoaPods update/CocoaPods install 慢、没反应、卡住的解决方案(Pods升级步骤)
pod管理第三方库带来的便利大家有目共睹,但是--,估计有很多人会遇到这样一种尴尬情况: Pod install 或 Pod update 执行之后,就不动了,一直一个界面简直要崩溃... 网上有很 ...
- Linux系统管理命令之用户组管理
涉及的配置文件 /etc/group /etc/gshadow /etc/gshadow- 可用于还原 不同系统的备份文件名称不同:name-或name.old 命令: 添加用户组groupadd 组 ...
- C++杂谈(三)产生随机数与time函数
产生随机数在程序中很有用,这篇文章简单介绍一下产生随机数的方法. 伪随机数 使用标准库<cstdlib>中的rand()函数产生随机数. #include<iostream> ...
- C++之STL
5.子类模板访问基类模板在子类模板中访问那些在基类模板中声明且依赖于模板参数的符号,应该在它前面加上作用域限定符"::" 或者显示使用this指针否则,编译器将试图在全局域中寻找该 ...
- Linux 下安装配置 JDK
JDK 下载地址 http://www.oracle.com/technetwork/java/javase/downloads/index.html 按照自己的情况选择不同的版本下载 cd /usr ...
- linux原始套接字(4)-构造IP_UDP
一.概述 同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...
- 关于response.getWriter()写回数据的实际发生时间点
只能说自己平时太粗心了,一些细节问题虽然几次路过,都没有注意过,也没有好好想过. 同事负责的一段微信模块的小逻辑,为了防止微信服务器认为没有接收到请求而重发消息,所以再收到微信服务器发回的消息后,马上 ...
- 150929-拖延高于懒-HTML(End)
四天未更了,分别是因为Xshell和虚拟机链接不好,累,懒(好像是三天..) 就像我一直嗷嗷着要去学开出一样,5年都没有去......拖延症似乎比懒癌更可怕.慢慢的慢慢的,人长大了,小时候的一些东西才 ...
- 清除浮动clear/BFC
浮动的清除有两种方式: 一.clear clear:both/left/right; 二.创建BFC (1)什么是BFC? BFC,块级格式化上下文,是一个独立的渲染区域,只有Block-level ...