Nop中定义了ICacheManger接口,它有几个实现,其中MemoryCacheManager是内存缓存的一个实现。
MemoryCacheManager:
using System; using System.Collections.Generic; using System.Runtime.Caching; using System.Text.RegularExpressions; namespace Nop.Core.Caching { ////// Represents a manager for caching between HTTP requests (long term caching) /// public partial class MemoryCacheManager : ICacheManager { ////// Cache object /// protected ObjectCache Cache { get { return MemoryCache.Default; } } ////// Gets or sets the value associated with the specified key. /// ///Type ///The key of the value to get. ///The value associated with the specified key. public virtual T Get(string key) { return (T)Cache[key]; } /// /// Adds the specified key and object to the cache. /// ///key ///Data ///Cache time public virtual void Set(string key, object data, int cacheTime) { if (data == null) return; var policy = new CacheItemPolicy(); policy.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime); Cache.Add(new CacheItem(key, data), policy); } ////// Gets a value indicating whether the value associated with the specified key is cached /// ///key ///Result public virtual bool IsSet(string key) { return (Cache.Contains(key)); } ////// Removes the value with the specified key from the cache /// ////key public virtual void Remove(string key) { Cache.Remove(key); } ////// Removes items by pattern /// ///pattern public virtual void RemoveByPattern(string pattern) { var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); var keysToRemove = new List(); foreach (var item in Cache) if (regex.IsMatch(item.Key)) keysToRemove.Add(item.Key); foreach (string key in keysToRemove) { Remove(key); } } /// /// Clear all cache data /// public virtual void Clear() { foreach (var item in Cache) Remove(item.Key); } ////// Dispose /// public virtual void Dispose() { } } }
缓存的添加,在需要的地方构建cache key然后调用ICacheManger接口存储起来:
var cachedModel = _cacheManager.Get(cacheKey, () => { var model = new List(); var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id, _workContext.WorkingLanguage.Id); if (blogPosts.Count > 0) { var months = new SortedDictionary (); var first = blogPosts[blogPosts.Count - 1].CreatedOnUtc; while (DateTime.SpecifyKind(first, DateTimeKind.Utc) <= DateTime.UtcNow.AddMonths(1)) { var list = blogPosts.GetPostsByDate(new DateTime(first.Year, first.Month, 1), new DateTime(first.Year, first.Month, 1).AddMonths(1).AddSeconds(-1)); if (list.Count > 0) { var date = new DateTime(first.Year, first.Month, 1); months.Add(date, list.Count); } first = first.AddMonths(1); } int current = 0; foreach (var kvp in months) { var date = kvp.Key; var blogPostCount = kvp.Value; if (current == 0) current = date.Year; if (date.Year > current || model.Count == 0) { var yearModel = new BlogPostYearModel { Year = date.Year }; model.Add(yearModel); } model.Last().Months.Add(new BlogPostMonthModel { Month = date.Month, BlogPostCount = blogPostCount }); current = date.Year; } } return model; });
这个ICacheManger的Get方法其实是个扩展方法,当获取不到缓存的时候调用Func
using System; namespace Nop.Core.Caching { ////// Extensions /// public static class CacheExtensions { ////// Get a cached item. If it's not in the cache yet, then load and cache it /// ///Type ///Cache manager ///Cache key ///Function to load item if it's not in the cache yet ///Cached item public static T Get(this ICacheManager cacheManager, string key, Func acquire) { return Get(cacheManager, key, 60, acquire); } /// /// Get a cached item. If it's not in the cache yet, then load and cache it /// ///Type ///Cache manager ///Cache key ///Cache time in minutes (0 - do not cache) ///Function to load item if it's not in the cache yet ///Cached item public static T Get(this ICacheManager cacheManager, string key, int cacheTime, Func acquire) { if (cacheManager.IsSet(key)) { return cacheManager.Get (key); } var result = acquire(); if (cacheTime > 0) cacheManager.Set(key, result, cacheTime); return result; } } }
Cache的移除。Nop缓存的移除比较有意思,它使用Pub/Sub模式来实现。
当你缓存一个Blog的列表,如果后面对某个Blog进行Update的时候,你就有两个选择:1.更新这个Blog的cache 2.移除所有关于Blog的cache。Nop选择的是后者,因为第一种方案实现起来的代价有点大,你可能需要给单独每个Blog指定一个Key来缓存起来,或者遍历所有关于Blog的cache。
当发生Blog的Update的时候,会发送一个通知事件:
public virtual void UpdateBlogPost(BlogPost blogPost) { if (blogPost == null) throw new ArgumentNullException("blogPost"); _blogPostRepository.Update(blogPost); //event notification _eventPublisher.EntityUpdated(blogPost); }
看一下EventPublish的实现 :
public interface IEventPublisher { ////// Publish event /// ///Type ///Event message void Publish(T eventMessage); } using System; using System.Linq; using Nop.Core.Infrastructure; using Nop.Core.Plugins; using Nop.Services.Logging; namespace Nop.Services.Events { /// /// Evnt publisher /// public class EventPublisher : IEventPublisher { private readonly ISubscriptionService _subscriptionService; ////// Ctor /// /// public EventPublisher(ISubscriptionService subscriptionService) { _subscriptionService = subscriptionService; } ////// Publish to cunsumer /// ///Type ///Event consumer ///Event message protected virtual void PublishToConsumer(IConsumer x, T eventMessage) { //Ignore not installed plugins var plugin = FindPlugin(x.GetType()); if (plugin != null && !plugin.Installed) return; try { x.HandleEvent(eventMessage); } catch (Exception exc) { //log error var logger = EngineContext.Current.Resolve (); //we put in to nested try-catch to prevent possible cyclic (if some error occurs) try { logger.Error(exc.Message, exc); } catch (Exception) { //do nothing } } } /// /// Find a plugin descriptor by some type which is located into its assembly /// ///Provider type ///Plugin descriptor protected virtual PluginDescriptor FindPlugin(Type providerType) { if (providerType == null) throw new ArgumentNullException("providerType"); if (PluginManager.ReferencedPlugins == null) return null; foreach (var plugin in PluginManager.ReferencedPlugins) { if (plugin.ReferencedAssembly == null) continue; if (plugin.ReferencedAssembly.FullName == providerType.Assembly.FullName) return plugin; } return null; } ////// Publish event /// ///Type ///Event message public virtual void Publish(T eventMessage) { var subscriptions = _subscriptionService.GetSubscriptions (); subscriptions.ToList().ForEach(x => PublishToConsumer(x, eventMessage)); } } }
很简单,只是获取所有的订阅,然后依次调用其中的PublishToConsumer方法。
那么订阅是在哪里呢?
首先这是Blog消息消费者的定义:
using Nop.Core.Caching; using Nop.Core.Domain.Blogs; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Configuration; using Nop.Core.Domain.Directory; using Nop.Core.Domain.Localization; using Nop.Core.Domain.Media; using Nop.Core.Domain.News; using Nop.Core.Domain.Orders; using Nop.Core.Domain.Polls; using Nop.Core.Domain.Topics; using Nop.Core.Domain.Vendors; using Nop.Core.Events; using Nop.Core.Infrastructure; using Nop.Services.Events; namespace Nop.Web.Infrastructure.Cache { ////// Model cache event consumer (used for caching of presentation layer models) /// public partial class ModelCacheEventConsumer: //blog posts IConsumer<>>, IConsumer<> >, IConsumer<> > { /// /// Key for blog tag list model /// ////// {0} : language ID /// {1} : current store ID /// public const string BLOG_TAGS_MODEL_KEY = "Nop.pres.blog.tags-{0}-{1}"; ////// Key for blog archive (years, months) block model /// ////// {0} : language ID /// {1} : current store ID /// public const string BLOG_MONTHS_MODEL_KEY = "Nop.pres.blog.months-{0}-{1}"; public const string BLOG_PATTERN_KEY = "Nop.pres.blog"; private readonly ICacheManager _cacheManager; public ModelCacheEventConsumer() { //TODO inject static cache manager using constructor this._cacheManager = EngineContext.Current.ContainerManager.Resolve("nop_cache_static"); } //Blog posts public void HandleEvent(EntityInserted eventMessage) { _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY); } public void HandleEvent(EntityUpdated eventMessage) { _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY); } public void HandleEvent(EntityDeleted eventMessage) { _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY); } } }
所有的Blog的key都采用统一的前缀,Nop.pres.blog。这样只要使用这个前缀就能清楚所有关于blog的缓存了。
这个类继承了3个接口所以有3个HandleEvent的实现,都是清楚blog相关的缓存。
这些消费者其实并未主动的去注册订阅,而是通过反射在启动的时候自动加载进IoC容器里的,当需要使用的时候通过接口直接取出来使用。
//Register event consumers var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList(); foreach (var consumer in consumers) { builder.RegisterType(consumer) .As(consumer.FindInterfaces((type, criteria) => { var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()); return isMatch; }, typeof(IConsumer<>))) .InstancePerLifetimeScope(); } builder.RegisterType().As ().SingleInstance(); builder.RegisterType ().As ().SingleInstance();
其中Pub/Sub是其中的精髓,非常值得学习。