使用PostSharp进行高效的AOP编程

在现代软件开发中,Aspect Oriented Programming(面向切面编程,简称AOP)提供了一种强大的工具,用于处理那些在多个地方重复出现的代码。PostSharp 是一个非常受欢迎的AOP框架,它允许开发者通过定义切面来消除样板代码,并且将额外的代码织入到编译后的应用中。本文将不深入探讨AOP的细节,也不讨论PostSharp是如何工作的,这些内容已经有大量的文献进行了阐述。如果对这些细节感兴趣,推荐访问PostSharp的支持论坛和博客。

最近,遇到了一个有趣的性能问题。应用程序需要一个第三方提供的重型规则处理引擎,作为一个负责任的开发者,选择在使用后正确地释放所有可处置的对象。然而,这导致处理时间翻倍甚至四倍。可能也遇到过类似的情况,比如在昂贵的SOA或数据库调用中。接下来将介绍一种技术,它可以帮助在给定相同参数和时间范围内,响应始终相同的场景。

首先,从使用PostSharp的AOP创建一个缓存属性开始:

using System; using System.Linq; using System.Reflection; using System.Runtime.Caching; using PostSharp.Aspects; using PostSharp.Extensibility; namespace InRuleLocalStressLoad.Lib.Attributes { [Serializable] public class CacheAttribute : MethodInterceptionAspect { [NonSerialized] private static readonly MemoryCache Cache; [NonSerialized] private CacheItemPolicy cachePolicy; private string _methodName; private string _declaringType; private string _prefix; static CacheAttribute() { if (!PostSharpEnvironment.IsPostSharpRunning) { Cache = MemoryCache.Default; } } public override void CompileTimeInitialize(MethodBase method, AspectInfo aspectInfo) { _methodName = method.Name; _declaringType = method.DeclaringType != null ? method.DeclaringType.FullName : String.Empty; _prefix = String.Concat(_declaringType, ".", _methodName, ":"); } public override void RuntimeInitialize(MethodBase method) { cachePolicy = new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(15) }; } public override void OnInvoke(MethodInterceptionArgs args) { var key = BuildCacheKey(args.Arguments); args.ReturnValue = Cache.Get(key, null) ?? Cache.AddOrGetExisting(key, args.Invoke(args.Arguments), cachePolicy, null) ?? Cache.Get(key, null); } private string BuildCacheKey(Arguments arguments) { return String.Concat(_prefix, String.Join("_", arguments.Select(a => a != null ? a.ToString() : String.Empty))); } } }

这个示例中有几个要点需要注意: - PostSharp允许两种类型的初始化:编译时和运行时。选择将昂贵的反射调用放在编译时初始化中,以避免执行期间的性能下降。将CacheItemPolicy的创建放在运行时初始化中,因为最终希望它能够通过应用程序配置文件进行配置。 - MemoryCache类是线程安全的,可以在多线程应用程序中使用。调用AddOrGetExisting将执行args.Invoke()调用,并在值被添加时返回null值,因此使用了两次null-coalescing操作符。这让感到惊讶,因为以为MemoryCache的行为会像ConcurrentDictionary一样,如果值已经存在,则不会执行Invoke调用。 - BuildCacheKey将“总是”是唯一的,给定一个类、方法和参数值。如果需要确保它100%唯一,将需要开发自己的哈希函数。

一旦创建了Cache属性,剩下的就很简单了。假设有一个类,它的构造函数严重依赖于一个文件:

var myInstance = new HeavyObject(someFileName);

只需要利用内存缓存来创建一个辅助方法,并用[Cache]属性装饰它:

[Cache] private HeavyObject GetMyHeavyObject(string fileName) { return new HeavyObject(fileName); }

稍后在代码中:

var myInstance = GetHeavyObject(someFileName);

就是这样。PostSharp会负责将所有必要的代码织入到编译源代码中。写的代码更少,更易读,也更容易维护。

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