Nop中定义了ICacheManger接口,它有几个实现,其中MemoryCacheManager是内存缓存的一个实现。
MemoryCacheManager:
using System; using System.Collections.Generic; using System.Runtime.Caching; using System.Text.RegularExpressions; namespace Nop.Core.Caching { /// <summary> /// Represents a manager for caching between HTTP requests (long term caching) /// </summary> public partial class MemoryCacheManager : ICacheManager { /// <summary> /// Cache object /// </summary> protected ObjectCache Cache { get { return MemoryCache.Default; } } /// <summary> /// Gets or sets the value associated with the specified key. /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="key">The key of the value to get.</param> /// <returns>The value associated with the specified key.</returns> public virtual T Get<T>(string key) { return (T)Cache[key]; } /// <summary> /// Adds the specified key and object to the cache. /// </summary> /// <param name="key">key</param> /// <param name="data">Data</param> /// <param name="cacheTime">Cache time</param> 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); } /// <summary> /// Gets a value indicating whether the value associated with the specified key is cached /// </summary> /// <param name="key">key</param> /// <returns>Result</returns> public virtual bool IsSet(string key) { return (Cache.Contains(key)); } /// <summary> /// Removes the value with the specified key from the cache /// </summary> /// <param name="key">/key</param> public virtual void Remove(string key) { Cache.Remove(key); } /// <summary> /// Removes items by pattern /// </summary> /// <param name="pattern">pattern</param> public virtual void RemoveByPattern(string pattern) { var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); var keysToRemove = new List<String>(); foreach (var item in Cache) if (regex.IsMatch(item.Key)) keysToRemove.Add(item.Key); foreach (string key in keysToRemove) { Remove(key); } } /// <summary> /// Clear all cache data /// </summary> public virtual void Clear() { foreach (var item in Cache) Remove(item.Key); } /// <summary> /// Dispose /// </summary> public virtual void Dispose() { } } }
缓存的添加,在需要的地方构建cache key然后调用ICacheManger接口存储起来:
var cachedModel = _cacheManager.Get(cacheKey, () => { var model = new List<BlogPostYearModel>(); var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id, _workContext.WorkingLanguage.Id); if (blogPosts.Count > 0) { var months = new SortedDictionary<DateTime, int>(); 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<T>获取值,然后缓存起来:
using System; namespace Nop.Core.Caching { /// <summary> /// Extensions /// </summary> public static class CacheExtensions { /// <summary> /// Get a cached item. If it's not in the cache yet, then load and cache it /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="cacheManager">Cache manager</param> /// <param name="key">Cache key</param> /// <param name="acquire">Function to load item if it's not in the cache yet</param> /// <returns>Cached item</returns> public static T Get<T>(this ICacheManager cacheManager, string key, Func<T> acquire) { return Get(cacheManager, key, 60, acquire); } /// <summary> /// Get a cached item. If it's not in the cache yet, then load and cache it /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="cacheManager">Cache manager</param> /// <param name="key">Cache key</param> /// <param name="cacheTime">Cache time in minutes (0 - do not cache)</param> /// <param name="acquire">Function to load item if it's not in the cache yet</param> /// <returns>Cached item</returns> public static T Get<T>(this ICacheManager cacheManager, string key, int cacheTime, Func<T> acquire) { if (cacheManager.IsSet(key)) { return cacheManager.Get<T>(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 { /// <summary> /// Publish event /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="eventMessage">Event message</param> void Publish<T>(T eventMessage); } using System; using System.Linq; using Nop.Core.Infrastructure; using Nop.Core.Plugins; using Nop.Services.Logging; namespace Nop.Services.Events { /// <summary> /// Evnt publisher /// </summary> public class EventPublisher : IEventPublisher { private readonly ISubscriptionService _subscriptionService; /// <summary> /// Ctor /// </summary> /// <param name="subscriptionService"></param> public EventPublisher(ISubscriptionService subscriptionService) { _subscriptionService = subscriptionService; } /// <summary> /// Publish to cunsumer /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="x">Event consumer</param> /// <param name="eventMessage">Event message</param> protected virtual void PublishToConsumer<T>(IConsumer<T> 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<ILogger>(); //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 } } } /// <summary> /// Find a plugin descriptor by some type which is located into its assembly /// </summary> /// <param name="providerType">Provider type</param> /// <returns>Plugin descriptor</returns> 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; } /// <summary> /// Publish event /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="eventMessage">Event message</param> public virtual void Publish<T>(T eventMessage) { var subscriptions = _subscriptionService.GetSubscriptions<T>(); 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 { /// <summary> /// Model cache event consumer (used for caching of presentation layer models) /// </summary> public partial class ModelCacheEventConsumer: //blog posts IConsumer<EntityInserted<BlogPost>>, IConsumer<EntityUpdated<BlogPost>>, IConsumer<EntityDeleted<BlogPost>> { /// <summary> /// Key for blog tag list model /// </summary> /// <remarks> /// {0} : language ID /// {1} : current store ID /// </remarks> public const string BLOG_TAGS_MODEL_KEY = "Nop.pres.blog.tags-{0}-{1}"; /// <summary> /// Key for blog archive (years, months) block model /// </summary> /// <remarks> /// {0} : language ID /// {1} : current store ID /// </remarks> 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<ICacheManager>("nop_cache_static"); } //Blog posts public void HandleEvent(EntityInserted<BlogPost> eventMessage) { _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY); } public void HandleEvent(EntityUpdated<BlogPost> eventMessage) { _cacheManager.RemoveByPattern(BLOG_PATTERN_KEY); } public void HandleEvent(EntityDeleted<BlogPost> 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<EventPublisher>().As<IEventPublisher>().SingleInstance(); builder.RegisterType<SubscriptionService>().As<ISubscriptionService>().SingleInstance();
其中Pub/Sub是其中的精髓,非常值得学习。