在.NET项目中使用PostSharp,实现AOP面向切面编程处理

时间:2023-02-06 17:21:48

PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一些如日志、缓存、事务处理、异常处理等常用的切面处理操作。

AOP(Aspect-Oriented Programming)是一种将函数的辅助性功能与业务逻辑相分离的编程泛型(programming paradigm),其目的是将横切关注点(cross-cutting concerns)分离出来,使得程序具有更高的模块化特性。AOP是面向方面软件开发(Aspect-Oriented Software Development)在编码实现层面上的具体表现。

我们知道,解耦是程序员编码开发过程中一直追求的,AOP也是为了解耦所诞生。引入AOP技术,能很大程度上简化我们编码,减少复制的代码量,也便于统一维护统一的部分代码,如日志、缓存、事务处理、异常处理等常用的处理。

1、AOP框架的介绍

1)AOP技术介绍

AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

2)AOP使用场景

AOP用来封装横切关注点,具体可以在下面的场景中使用:

Authentication 权限

Caching 缓存

Context passing 内容传递

Error handling 错误处理

Lazy loading 懒加载

Debugging  调试

logging, tracing, profiling and monitoring 记录跟踪 优化 校准

Performance optimization 性能优化

Persistence  持久化

Resource pooling 资源池

Synchronization 同步

Transactions 事务

3)PostSharp框架

PostSharp是一个用于在.NET平台上实现AOP的框架,是比较常用的一个AOP框架,官方网站为http://www.sharpcrafters.com。目前最新版本为4.X,但是是收费的AOP软件。

PostSharp使用静态织入方式实现AOP,其连接点非常丰富,使用简单,而且相对其它一些.NET平台上的AOP框架来说,PostSharp较为轻量级,但是功能却一点也不逊色。

总体来说,使用PostSharp,将会带来如下优点:

  • 横切关注点单独分离出来,提高了代码的清晰性和可维护性。
  • 只要在Aspect中编写辅助性功能代码,在一定程度上减少了工作量和冗余代码。

当然,使用PostSharp也会存在一些缺点,主要缺点有如下两方面:

  • 增加了调试的难度。
  • 相比于不用AOP的代码,运行效率有所降低。

不过瑕不掩瑜,相对于这些缺点问题,使用PostSharp可以极大提高开发效率,减少重复代码,从而提高代码的可读性、可维护性。

另外在GitHub上还有一些开源的AOP组件,例如排头位的是KingAOP(https://github.com/AntyaDev/KingAOP),不过由于它采用了Dynamic的方式来实现,如它的构造对象如下所示。

dynamic helloWorld = new HelloWorld();
helloWorld.HelloWorldCall();

因此虽然比较方便,而且号称和PostSharp使用习惯类似,但是改变了对象的创建方式,对一般项目的类对象处理并不太适合。因此我还是比较倾向于使用PostSharp来进行AOP的编程开发。

2、PostSharp框架的使用

1)准备PostSharp的编译环境

PostSharp目前版本是4.x,我在官网下载了进行使用,不过经常发生"Error connecting to the pipe server. See previous warnings for details.",后来干脆使用了3.x版本的,反而能够正常使用,非常不错,呵呵。

PostSharp是一个可以安装在VS上的插件,安装后在VS的菜单栏目里面增加了一个PostSharp的菜单项,如下所示。

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

一般项目如果需要使用PostSharp特性的,在项目属性的【PostSharp】选项页中,使用【Add PostSharp to this project】把PostSharp加入到项目里面进行使用。

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

添加后,会弹出PostSharp的插件提示对话框,提示将加入相应的PostSharp包等内容,如下所示。

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

完成后就可以在项目中使用PostSharp的相关类了。

2)增加PostSharp的AOP切面处理

一般约定每个Aspect类的命名必须为“XXXAttribute”的形式。其中“XXX”就是这个Aspect的名字。PostSharp中提供了丰富的内置“Base Aspect”以便我们继承,其中这里我们继承“OnMethodBoundaryAspect ”,这个Aspect提供了进入、退出函数等连接点方法。另外,Aspect上必须设置“[Serializable] ”,这与PostSharp内部对Aspect的生命周期管理有关。

日志的Aspect类的代码如下所示。

    [Serializable]
public class LogAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
Console.WriteLine(Environment.NewLine); Console.WriteLine("Entering [ {0} ] ...", args.Method); base.OnEntry(args);
} public override void OnExit(MethodExecutionArgs args)
{
Console.WriteLine("Leaving [ {0} ] ...", args.Method); base.OnExit(args);
}
}

异常处理的类代码如下所示。

    [Serializable]
public class ExceptionAttribute : OnExceptionAspect
{
public override void OnException(MethodExecutionArgs args)
{
Console.WriteLine(String.Format("Exception in :[{0}] , Message:[{1}]", args.Method, args.Exception.Message));
args.FlowBehavior = FlowBehavior.Continue; base.OnException(args);
}
}

计时处理的Aspect类代码如下所示。

    [Serializable]
[MulticastAttributeUsage(MulticastTargets.Method)]
public class TimingAttribute : PostSharp.Aspects.OnMethodBoundaryAspect
{
[NonSerialized]
Stopwatch _StopWatch; public override void OnEntry(PostSharp.Aspects.MethodExecutionArgs args)
{
_StopWatch = Stopwatch.StartNew(); base.OnEntry(args);
} public override void OnExit(PostSharp.Aspects.MethodExecutionArgs args)
{
Console.WriteLine(string.Format("[{0}] took {1}ms to execute",
new StackTrace().GetFrame().GetMethod().Name,
_StopWatch.ElapsedMilliseconds)); base.OnExit(args);
}
}

事务处理的Aspect类代码如下所示。

    [Serializable]
[AspectTypeDependency(AspectDependencyAction.Order, AspectDependencyPosition.After, typeof(LogAttribute))]
public class RunInTransactionAttribute : OnMethodBoundaryAspect
{
[NonSerialized]
TransactionScope TransactionScope; public override void OnEntry(MethodExecutionArgs args)
{
this.TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
} public override void OnSuccess(MethodExecutionArgs args)
{
this.TransactionScope.Complete();
} public override void OnException(MethodExecutionArgs args)
{
args.FlowBehavior = FlowBehavior.Continue;
Transaction.Current.Rollback();
Console.WriteLine("Transaction Was Unsuccessful!");
} public override void OnExit(MethodExecutionArgs args)
{
this.TransactionScope.Dispose();
}
}

下面是几个Aspect类的切面处理代码,如下所示。

        [Exception]
[Log]
static void Calc()
{
throw new DivideByZeroException("A Math Error Occured...");
} [Log, Timing]
static void LongRunningCalc()
{
//wait for 1000 miliseconds
Thread.Sleep();
}

从上面我们可以看到,常规的异常处理、日志处理都已经通过Attribute的方式进行处理了,在函数体里面都只是剩下具体的业务逻辑代码了,这样极大提高了代码的可读性,简洁明了。

运行上面的代码函数的调用,我们可以在输出日志里面看到具体的结果内容。

Entering [ Void Calc() ] ...
“System.DivideByZeroException”类型的第一次机会异常在 PostSharpExample.exe 中发生
Exception in :[Void Calc()] , Message:[A Math Error Occured...]
Leaving [ Void Calc() ] ... Entering [ Void LongRunningCalc() ] ...
Leaving [ Void LongRunningCalc() ] ...
[LongRunningCalc] took 1002ms to execute

这样,通过声明的方式,就实现了常规日志 、异常的处理,当然实际项目上使用日志、异常处理的这些代码肯定会更加复杂一些,不过小例子已经实现了切面逻辑的分离处理了,尘归尘、土归土,一切都是那么的简洁安静了。