面向切面编程(AOP)在.NET中的应用

面向切面编程(AOP)是一种编程范式,它允许程序员将横切关注点(如日志记录、事务管理、安全检查等)与业务逻辑分离开来。这种分离可以提高代码的模块化和可维护性。尽管.NET平台没有内置相关的工具,但有一些第三方解决方案提供了这种能力,例如PostSharp、Unity、Spring.NET、Castle Windsor和Aspect.NET等。选择一个AOP框架时,需要考虑实现横切功能注入的机制及其优缺点。通常,可以识别出两种方法:编译时注入和运行时代理类生成。

编译时注入与运行时代理类生成

编译时注入是首选选项,因为它在程序执行期间不需要额外的计算开销,这对于移动设备尤为重要。而常规的代理类生成虽然实现起来更简单,但除了计算开销外,还有一些限制——方法和属性必须属于接口或为虚拟的,才能通过代理类进行拦截。

PostSharp与AspectInjector

PostSharp为面向切面编程提供了强大的功能,但它是一个商业产品,可能不适合某些项目。作为替代方案,开发并正在不断改进AspectInjector——一个允许在编译时应用切面的框架,它具有简单但灵活的接口。

使用AspectInjector

让快速看看AspectInjector能做什么。可以使用以下命令将库添加到项目中:

Install-Package AspectInjector -Pre

目前只有预发布版本具有完整的功能集,所以需要"-Pre"开关来获取Aspect Injector的最完整功能。

定义切面

假设需要为所有方法添加一个简单的跟踪——只需捕获方法的开始和结束。为了实现这一点,需要定义一个实现所需行为的切面:

public class TraceAspect { [Advice(InjectionPoints.Before, InjectionTargets.Method)] public void TraceStart([AdviceArgument(AdviceArgumentSource.Type)] Type type, [AdviceArgument(AdviceArgumentSource.Name)] string name) { Console.WriteLine("[{0}] Method {1}.{2} started", DateTime.UtcNow, type.Name, name); } [Advice(InjectionPoints.After, InjectionTargets.Method)] public void TraceFinish([AdviceArgument(AdviceArgumentSource.Type)] Type type, [AdviceArgument(AdviceArgumentSource.Name)] string name) { Console.WriteLine("[{0}] Method {1}.{2} finished", DateTime.UtcNow, type.Name, name); } }

这意味着TraceStart和TraceFinish方法将在每个方法的开始和结束时被调用,每次调用都将具有目标方法的名称及其声明类型的信息。

应用切面

要将切面应用于需要它的任何类,只需用Aspect属性标记目标类:

[Aspect(typeof(TraceAspect))] public class SampleClass { public void Method1() { // ... } public int Method2() { // ... } }

结果,TraceStart和TraceFinish的调用将被注入到SampleClass的所有方法中。以下是反编译后的代码:

public class SampleClass { // Added by AspectInjector private TraceAspect __a$i_TraceAspect; public void Method1() { // Added by AspectInjector this.__a$i_TraceAspect.TraceStart(typeof(SampleClass), "Method1"); Console.WriteLine("Inside Method1"); // Added by AspectInjector this.__a$i_TraceAspect.TraceFinish(typeof(SampleClass), "Method1"); } public int Method2() { // Added by AspectInjector this.__a$i_TraceAspect.TraceStart(typeof(SampleClass), "Method2"); Console.WriteLine("Inside Method2"); // Added by AspectInjector int num = 1; int result = num; this.__a$i_TraceAspect.TraceFinish(typeof(SampleClass), "Method2"); return result; } public SampleClass() { // Added by AspectInjector this.__a$_initializeInstanceAspects(); } // Added by AspectInjector private void __a$_initializeInstanceAspects() { if (this.__a$i_TraceAspect == null) { this.__a$i_TraceAspect = new TraceAspect(); } } }

这只是最简单示例之一,并没有展示AspectInjector的所有功能。有关注入目标、目标成员过滤(由Aspect属性上的附加参数控制)和建议参数源的更多选项,可以在文档中找到所有这些信息。

"Around"注入点

有一些特性值得特别关注——例如"Around"注入点。它目前只在Aspect Injector的预发布版本中可用,允许完全包装任何调用。这里最简单的示例之一是跟踪调用持续时间:

public class DurationAspect { [Advice(InjectionPoints.Around, InjectionTargets.Method)] public object TraceDuration([AdviceArgument(AdviceArgumentSource.Type)] Type type, [AdviceArgument(AdviceArgumentSource.Name)] string name, [AdviceArgument(AdviceArgumentSource.Target)] Func target, [AdviceArgument(AdviceArgumentSource.Arguments)] object[] arguments) { var sw = new Stopwatch(); sw.Start(); var result = target(arguments); sw.Stop(); Console.WriteLine("[{0}] Method {1}.{2} took {3} ms", DateTime.UtcNow, type.Name, name, sw.ElapsedMilliseconds); return result; } }

主要思想是,带有InjectionPoints.Around的建议必须接受函数委托和相应的参数——进行原始调用所需的所有必要信息。建议方法可以在调用原始方法之前和之后执行任何额外的工作,如示例所示。然而,"around"注入比"before"和"after"在代码更改方面更重。虽然后两者修改原始方法的主体,"around"创建额外的包装函数,因此建议仅在需要在方法开始和结束之间传递某些数据时使用它。

接口注入

可能需要注入的不仅仅是一些方法、属性或事件的额外代码,还有额外的接口。一个经典的例子是.NETUI框架的INotifyPropertyChanged:

[AdviceInterfaceProxy(typeof(INotifyPropertyChanged))] public class NotifyPropertyChangedAspect : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = (s, e) => { }; [Advice(InjectionPoints.After, InjectionTargets.Setter)] public void RaisePropertyChanged( [AdviceArgument(AdviceArgumentSource.Instance)] object targetInstance, [AdviceArgument(AdviceArgumentSource.Name)] string propertyName) { PropertyChanged(targetInstance, new PropertyChangedEventArgs(propertyName)); } }

之后,将不得不用[Aspect(typeof(NotifyPropertyChangedAspect))]属性装饰所有视图模型类,它们的所有属性将自动变得可通知。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485