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是其中的精髓,非常值得學習。