我正在使用.NET和C#构建一个应用程序,我想通过在方法中使用属性/注释而不是显式代码来缓存一些结果.
我想要一个看起来有点像这样的方法签名:
[Cache, timeToLive=60] String getName(string id, string location)
它应该根据输入创建一个哈希值,并将其用作结果的键.当然,有一些配置文件告诉它如何实际放入memcached,本地字典或其他东西.
你知道这样的框架吗?
我甚至对Java也感兴趣
使用Microsoft Enterprise Library中的 CacheHandler, 您可以轻松实现此目的.例如:
[CacheHandler(0, 30, 0)] public Object GetData(Object input) { }
会使对该方法的所有调用都缓存30分钟.所有调用都根据输入数据和方法名称获取一个唯一的缓存键,因此如果您使用不同的输入调用该方法两次,它将不会被缓存,但如果您在输出相同的时间间隔内调用它> 1次,则方法只执行一次.
我在微软的代码中添加了一些额外的功能:
我的修改版本看起来像这样:
using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.Remoting.Contexts; using System.Text; using System.Web; using System.Web.Caching; using System.Web.UI; using Microsoft.Practices.EnterpriseLibrary.Common.Configuration; using Microsoft.Practices.Unity.InterceptionExtension; namespace Middleware.Cache { ////// An [ConfigurationElementType(typeof (CacheHandler)), Synchronization] public class CacheHandler : ICallHandler { ///that implements caching of the return values of /// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request. /// /// The default expiration time for the cached entries: 5 minutes /// public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0); private readonly object cachedData; private readonly DefaultCacheKeyGenerator keyGenerator; private readonly bool storeOnlyForThisRequest = true; private TimeSpan expirationTime; private GetNextHandlerDelegate getNext; private IMethodInvocation input; public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest) { keyGenerator = new DefaultCacheKeyGenerator(); this.expirationTime = expirationTime; this.storeOnlyForThisRequest = storeOnlyForThisRequest; } ////// This constructor is used when we wrap cached data in a CacheHandler so that /// we can reload the object after it has been removed from the cache. /// /// /// /// /// /// public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest, IMethodInvocation input, GetNextHandlerDelegate getNext, object cachedData) : this(expirationTime, storeOnlyForThisRequest) { this.input = input; this.getNext = getNext; this.cachedData = cachedData; } ////// Gets or sets the expiration time for cache data. /// ///The expiration time. public TimeSpan ExpirationTime { get { return expirationTime; } set { expirationTime = value; } } #region ICallHandler Members ////// Implements the caching behavior of this handler. /// ///object describing the current call. /// delegate used to get the next handler in the current pipeline. /// Return value from target method, or cached result if previous inputs have been seen. public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { lock (input.MethodBase) { this.input = input; this.getNext = getNext; return loadUsingCache(); } } public int Order { get { return 0; } set { } } #endregion private IMethodReturn loadUsingCache() { //We need to synchronize calls to the CacheHandler on method level //to prevent duplicate calls to methods that could be cached. lock (input.MethodBase) { if (TargetMethodReturnsVoid(input) || HttpContext.Current == null) { return getNext()(input, getNext); } var inputs = new object[input.Inputs.Count]; for (int i = 0; i < inputs.Length; ++i) { inputs[i] = input.Inputs[i]; } string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs); object cachedResult = getCachedResult(cacheKey); if (cachedResult == null) { var stopWatch = Stopwatch.StartNew(); var realReturn = getNext()(input, getNext); stopWatch.Stop(); if (realReturn.Exception == null && realReturn.ReturnValue != null) { AddToCache(cacheKey, realReturn.ReturnValue); } return realReturn; } var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments); return cachedReturn; } } private object getCachedResult(string cacheKey) { //When the method uses input that is not serializable //we cannot create a cache key and can therefore not //cache the data. if (cacheKey == null) { return null; } object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey]; var cachedValueCast = cachedValue as CacheHandler; if (cachedValueCast != null) { //This is an object that is reloaded when it is being removed. //It is therefore wrapped in a CacheHandler-object and we must //unwrap it before returning it. return cachedValueCast.cachedData; } return cachedValue; } private static bool TargetMethodReturnsVoid(IMethodInvocation input) { var targetMethod = input.MethodBase as MethodInfo; return targetMethod != null && targetMethod.ReturnType == typeof (void); } private void AddToCache(string key, object valueToCache) { if (key == null) { //When the method uses input that is not serializable //we cannot create a cache key and can therefore not //cache the data. return; } if (!storeOnlyForThisRequest) { HttpRuntime.Cache.Insert( key, valueToCache, null, System.Web.Caching.Cache.NoAbsoluteExpiration, expirationTime, CacheItemPriority.Normal, null); } else { HttpContext.Current.Items[key] = valueToCache; } } } ////// This interface describes classes that can be used to generate cache key strings /// for the public interface ICacheKeyGenerator { ///. /// /// Creates a cache key for the given method and set of input arguments. /// /// Method being called. /// Input arguments. ///A (hopefully) unique string to be used as a cache key. string CreateCacheKey(MethodBase method, object[] inputs); } ////// The default public class DefaultCacheKeyGenerator : ICacheKeyGenerator { private readonly LosFormatter serializer = new LosFormatter(false, ""); #region ICacheKeyGenerator Members ///used by the . /// /// Create a cache key for the given method and set of input arguments. /// /// Method being called. /// Input arguments. ///A (hopefully) unique string to be used as a cache key. public string CreateCacheKey(MethodBase method, params object[] inputs) { try { var sb = new StringBuilder(); if (method.DeclaringType != null) { sb.Append(method.DeclaringType.FullName); } sb.Append(':'); sb.Append(method.Name); TextWriter writer = new StringWriter(sb); if (inputs != null) { foreach (var input in inputs) { sb.Append(':'); if (input != null) { //Diffrerent instances of DateTime which represents the same value //sometimes serialize differently due to some internal variables which are different. //We therefore serialize it using Ticks instead. instead. var inputDateTime = input as DateTime?; if (inputDateTime.HasValue) { sb.Append(inputDateTime.Value.Ticks); } else { //Serialize the input and write it to the key StringBuilder. serializer.Serialize(writer, input); } } } } return sb.ToString(); } catch { //Something went wrong when generating the key (probably an input-value was not serializble. //Return a null key. return null; } } #endregion } }
微软最值得赞扬的是这段代码.我们只在请求级别添加缓存等内容而不是跨请求(比您想象的更有用)并修复了一些错误(例如,将DateTime-objects序列化为不同的值).