在现代软件开发中,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会负责将所有必要的代码织入到编译源代码中。写的代码更少,更易读,也更容易维护。