Spiga

使用PostSharp进行AOP框架设计:一个简单的原型

2007-09-13 20:28 by 冰戈, 3767 visits, 网摘, 编辑

AOP已经不是一个什么新名词了,在博客园使用关键字搜索可以查出n多条关于AOP的介绍,这里就不再赘述了。

Bruce Zhang's Blog里面有很多关于AOP介绍及其在.net下实现研究,总觉得如果什么都从头来写难免有自造轮子的嫌疑,但是目前也没有很成熟的AOP框架让我们能轻松完成基于AOP架构,不过一直以来都在关注的PostSharp开源项目日趋成熟,目前已发布了PostSharp 1.0 (Beta release 3)。即使如此,也还没能到应用到产品上的时候。

前段时间一直在封装一个权限系统,时常为如何给调用方提供一个良好的编程接口烦恼,加之前前段时间考虑的日志、异常接管、事务、缓存等等一些横向组件的架构分析,自然就想用AOP技术实现,但是由于实现难度实在不小作罢;这两天又重新学习研究了PostSharp的架构与实现思想,觉得还是尝试一下,将其融入现有框架;

早在年初就有不少前辈大师就如何使用这个东西撰写过文章,如Q.yuhenPostSharp - Lightweight Aspect-Oriented System该仁兄下面见解很到位:

和以往基于 Dynamic Proxy 方式与 AOP 解决方案做个比较。

  • 由于采用 MSIL Injection,因此静态代码注入的执行效率要高于使用 Reflection Emit。
  • 使用 MSBuild Task,使得开发人员可以像使用编译器内置 Attribute 那样使用 AOP。
  • 可以拦截任意方法,而 Dynamic Proxy 方式的 AOP 往往采取继承方式来拦截 Virtual 方法。
  • 拥有更多的控制权。包括中断执行流程,修改参数和返回值等等。
  • 还可以拦截 Field Access、Exception 等操作。
  • 无需将对象创建代码改成 "new proxy()",更加透明。
  • 可以使用通配符进行多重拦截匹配。
  • 静态注入带来的问题更多的是注入代码的质量和调试复杂度。

另外有一老外的Using AOP and PostSharp to Enhance Your CodeAB两部分,相当精彩,本文就是在参考这两篇好文的基础上做的。

我们假设有这么个场景,其实这也是实际业务中很常见的处理方式:有一定单管理模块,具备新增、删除两功能,我们在新增删除的时候必须校验权限,在删除的时候还必须记录日志,出现异常了还必须捕捉并记录异常;

按以前的写法我们可能很麻烦,我们要如此这般的写:

public class Orders
    {
        
public bool Add(string id, string orderName)
        {
            
try
            {
                
if (User.AddEnable)
                {
                    
//TODO:新增订单的实现
                    Console.WriteLine("正在执行新增订单方法的操作,回车继续……");
                    Console.ReadLine();
                    Console.WriteLine(
"您添加订单成功:编号:{0},名称:{1}", id, orderName);
                    
return true;
                }
                
else
                {
                    
//
                }
            }
            
catch (Exception)
            {
                
//TODO:记录异常的实现
                throw;
            }

            
return true;

        }

        
public bool Delete(string id)
        {
            
try
            {
                
if (User.DeleteEnable)
                {
                    
//TODO:删除订单的实现
                    Console.WriteLine("您删除订单成功:编号:{0}", id);
                }
                
else
                {
                    
//
                }

            }
            
catch (Exception)
            {
                
//TODO:记录异常的实现
                throw;
            }

            
return true;
        }

这种写的弊端我就不多说了,有很多先驱都阐述过……

我要演示的是采用AOP技术的框架原型实现:

首先我们应该安装PostSharp(一定要安装要不能没办法注入处理代码)

然后我们实现Orders对象

using System;

namespace PostSharp.Demo
{
    
public class Orders
    {
        [Permission]
        [Exception]
        
public bool Add(string id, string orderName)
        {
            Console.WriteLine(
"正在执行新增订单方法的操作,回车继续……");
            Console.ReadLine();
            Console.WriteLine(
"您添加订单成功:编号:{0},名称:{1}", id, orderName);
            
return true;
        }

        [Logger]
        [Permission]
        [Exception]
        
public bool Delete(string id)
        {
            Console.WriteLine(
"您删除订单成功:编号:{0}", id);

            
return true;
        }
    }
}

当然还要模拟一个用户资格认证


namespace PostSharp.Demo
{
    
/// <summary>
    
/// 静态的用户对象,用于存放当前登录用户,成员资格
    
/// </summary>
    public static class User
    {
        
private static string _userId;

        
public static string UserId
        {
            
get { return _userId; }
            
set { _userId = value; }
        }

        
public static bool AddEnable
        {
            
get
            {
                
return (_userId.ToLower() == "admin");
            }
        }

        
public static bool DeleteEnable
        {
            
get
            {
                
return (_userId.ToLower() == "admin");
            }
        }
    }
}

再然后我们实现权限控制方面PermissionAttribute,日志方面LoggerAttribute,异常处理方面ExceptionAttribute……

PermissionAttribute

using System;
using PostSharp.Laos;

namespace PostSharp.Demo
{
    [Serializable]
    [global::System.AttributeUsage(AttributeTargets.All, Inherited 
= true, AllowMultiple = false)]
    
public class PermissionAttribute : OnMethodBoundaryAspect
    {
        
public override void OnEntry(MethodExecutionEventArgs eventArgs)
        {
            
if (!User.AddEnable)
            {
                Console.WriteLine(
"用户:【{0}】没有权限:【{1}】", User.UserId, eventArgs.Method);
                eventArgs.FlowBehavior 
= FlowBehavior.Return;
            }

        }
    }
}

LoggerAttribute
using System;
using PostSharp.Laos;

namespace PostSharp.Demo
{
    [Serializable]
    [global::System.AttributeUsage(AttributeTargets.All, Inherited 
= true, AllowMultiple = false)]
    
public sealed class LoggerAttribute : OnMethodInvocationAspect
    {
        
public override void OnInvocation(MethodInvocationEventArgs eventArgs)
        {
            DateTime time 
= DateTime.Now;
            
string log = "时间:{0},操作人员:{1},操作:{2}!";

            
object[] arg = eventArgs.GetArguments();

            log 
= String.Format(log, time, User.UserId, "删除Id为" + arg[0].ToString() + "的订单!");

            System.IO.File.WriteAllText(
"C:\\Log.Txt", log);
        }
    }
}

ExceptionAttribute
using System;
using PostSharp.Laos;

namespace PostSharp.Demo
{
    [Serializable]
    [global::System.AttributeUsage(AttributeTargets.All, Inherited 
= true, AllowMultiple = false)]
    
public class ExceptionAttribute : OnExceptionAspect
    {
        
public override void OnException(MethodExecutionEventArgs eventArgs)
        {
            Console.WriteLine(
"程序出现异常:{0}", eventArgs.Exception.Message);
            eventArgs.FlowBehavior 
= FlowBehavior.Return;
        }
    }
}

然后再用控制台程序测试下能不能成功
Orders order = new Orders();
            Console.WriteLine(
"请输入用户名:");
            User.UserId 
= Console.ReadLine();
            Console.WriteLine(
"请输入密码:");
            Console.ReadLine();
            
string id;

            LRedo:
            Console.WriteLine(
"请输入您要执行的操作:新增(A),删除(D),退出(X)");

            
string opt = Console.ReadLine();

            
if (opt.ToLower() == "a")
            {
                Console.WriteLine(
"请输入订单编号:");
                id 
= Console.ReadLine();

                Console.WriteLine(
"请输入订单名称:");
                
string name = Console.ReadLine();
                order.Add(id, name);
            }
            
else if (opt.ToLower() == "d")
            {
                Console.WriteLine(
"请输入订单编号:");
                id 
= Console.ReadLine();
                order.Delete(id);
            }
            
else if (opt.ToLower() == "x")
            {
            }
            
else
            {
                Console.WriteLine(
"您的输入不正确,请重新输入!");
                
goto LRedo;
            }

            Console.WriteLine(
"按任意键退出……");
            Console.ReadLine();

写完这些我们再反编译一下生成的exe文件,发现里面的Orders成了这模样了,神奇了?
反编译后的代码

代码很简单,我是采用控制台应用实现的,如果您有兴趣,请下载Demo源码玩玩。

Add your comment

32 条回复

  1. #1楼 dixiao[未注册用户]2007-09-13 20:52
    过段时间有空研究研究,先做个记号
      回复  引用    
  2. #2楼 try      2007-09-13 22:19
    日志记录是怎么记录的?在记录成功删除才记录?
      回复  引用  查看    
  3. #3楼 iCaca      2007-09-14 00:29
    学习中...
      回复  引用  查看    
  4. #4楼 jacklee[未注册用户]2007-09-14 02:23
    学习
      回复  引用    
  5. #5楼 厉害[未注册用户]2007-09-14 05:06
    学习。。。
      回复  引用    
  6. #6楼 programdev[未注册用户]2007-09-14 08:31
    学习
      回复  引用    
  7. #7楼 代码乱了      2007-09-14 08:32
    不错,发现使用上比Dynamic Proxy 简单
      回复  引用  查看    
  8. #8楼 qy1141      2007-09-14 08:51
    有点疑问,在异常捕捉的时候,Finally{}中的代码在AOP中如何实现?
      回复  引用  查看    
  9. #9楼[楼主] 冰戈      2007-09-14 09:06
    @qy1141
    如果您对这个感兴趣,请下载源代码跟踪一下,目前我没深入代码分析……

    我这仅仅提供一种使用方法的原型,其实在真正使用的时候还是要认真斟酌使用形式的,特别PostSharp的实现思路,可能过段时间我会发点PostSharp实现代码分析的东西,敬请关注……
      回复  引用  查看    
  10. #10楼 阿牛 - 专注OOP      2007-09-14 09:33
    好文,关注中,简单实用,楼主继续,.NET下的AOP太少了!
      回复  引用  查看    
  11. #11楼[楼主] 冰戈      2007-09-14 10:34
    @阿牛 - 专注OOP
    谢谢关注
      回复  引用  查看    
  12. #12楼 jhtchina      2007-09-14 13:09
    Mark
      回复  引用  查看    
  13. #13楼 tmfc      2007-09-14 13:42
    postsharp确实是目前看来.net里最可能成功的一个AOP实现,现在缺的就是标准的AOP语法支持了,只靠自定义Attribute并不能实现什么复杂的功能,大不了就是做做日志,权限,异常了。
      回复  引用  查看    
  14. #14楼[楼主] 冰戈      2007-09-14 13:54
    @tmfc
    很同意你的看法,继续关注postsharp
      回复  引用  查看    
  15. #15楼 无常      2007-09-14 18:20
    关注这个项目
      回复  引用  查看    
  16. #16楼 bobmazelin      2007-09-14 20:59
    我也在研究AOP,有兴趣的可以到我的blog看看。
      回复  引用  查看    
  17. #17楼 zuki      2007-09-14 23:41
    学习,见识少啊,还有这么多好东西
      回复  引用  查看    
  18. #18楼 Leepy      2007-09-17 02:46
    重新生成解决方案发生如下错误:
    PostSharp: ILASM exited with return code 1.

    这个是怎么回事?
      回复  引用  查看    
  19. #19楼 bobmazelin      2007-09-18 10:37
    看到你的留言了,我的aspectNet和PostSharp有很大不同,比如:不“污染”现有代码。有兴趣可以进一步探讨。
      回复  引用  查看    
  20. #20楼 蛙蛙池塘      2007-11-04 22:20
    很强大呀
      回复  引用  查看    
  21. #21楼 ithurricane      2007-12-29 16:56
    学习一下
      回复  引用  查看    
  22. #22楼 代码乱了      2008-03-23 11:49
    很好!
      回复  引用  查看    
  23. #23楼 ygl[未注册用户]2008-04-25 11:51
    下载不了啊,
      回复  引用    
  24. #24楼 armtop[未注册用户]2008-08-22 22:48
    调试此事例时,如果用户对Add没有权限,那么从OnEntry返回Add时抛出异常“未将对象引用设置到对象的实例”请问这如何解决?
      回复  引用    
  25. #25楼 哒哒鸟[未注册用户]2008-10-07 16:01
    我有一个疑问,不知道代码中的“LRedo”是什么意思?
    在代码中,它起什么作用?

    请楼主解释一下。
      回复  引用    
  26. #26楼 abc1234[未注册用户]2008-11-29 21:43
    OnMethodInvocationAspect

    怎么我用这个的时候log替代了原来的方法,原方法却没有执行
      回复  引用    
  27. #27楼 abc1234[未注册用户]2008-11-29 22:59
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
    DateTime time = DateTime.Now;
    string log = "时间:{0},操作人员:{1},操作:{2}!";

    object[] arg = eventArgs.GetArguments();

    log = String.Format(log, time, User.UserId, "删除Id为" + arg[0].ToString() + "的订单!");

    System.IO.File.WriteAllText("C:\\Log.Txt", log);
    }

    在System.IO.File.WriteAllText("C:\\Log.Txt", log);后面要加上eventArgs.Proceed(); 不然的话会出现未将对象设置到引用
      回复  引用    
  28. #28楼 吾跃乾坤      2008-12-30 13:07
    Demo下不了
      回复  引用  查看    
  29. #29楼 hequnmin@139.com[未注册用户]2009-02-12 09:25
    Demo下载不了!急用,哪位有这个Demo的话麻烦发到我的邮箱hequnmin@139.com,不胜感激!
      回复  引用    
  30. #30楼 雨轩      2009-02-23 16:45
    先做个记号 过段时间有空研究研究,
      回复  引用  查看    
  31. #31楼 雨轩      2009-02-23 17:03
    Demo下载不了! 哪位有这个Demo的话麻烦发到我的邮箱yuanqiang.182@qq.com,不胜感激!
      回复  引用  查看    
  32. #32楼 Bobbyliao      2009-04-01 10:46
    如果用户对Add没有权限,那么从OnEntry返回Add时抛出异常“未将对象引用设置到对象的实例”,是因为在PermissionAttribute中如果权限不满足时没有返回值,应该返回false,因为Orders的Add和Delete方法都是返回bool类型值的,如下:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using PostSharp.Laos;
    using Model;

    namespace AOP
    {
    [Serializable]
    [global::System.AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = false)]
    public class PermissionAttribute : OnMethodBoundaryAspect
    {
    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
    if (!User.AddEnable)
    {
    Console.WriteLine("用户:【{0}】没有权限:【{1}】", User.UserId, eventArgs.Method);
    eventArgs.FlowBehavior = FlowBehavior.Return;
    eventArgs.ReturnValue = false;

    }

    }
    }

    }
      回复  引用  查看    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 892107




相关文章:

相关链接: