在以前一篇隨筆《在.NET項目中使用PostSharp,實現AOP面向切面編程處理》介紹了PostSharp框架的使用,試用PostSharp能給我帶來不少便利和優點,減小代碼冗餘,提升可讀性,而且能夠更加優雅的實現常規的日誌、異常、緩存、事務等業務場景的處理。本篇主要介紹使用MemoryCache實現緩存的處理。html
上篇沒有說起緩存的處理,通常狀況下,緩存的處理咱們能夠利用微軟的分佈式緩存組件MemoryCache進行緩存的處理操做。MemoryCache的使用網上介紹的很少,不過這個是.NET4.0新引入的緩存對象,主要是替換原來企業庫的緩存模塊,使得.NET的緩存能夠無處不在,而不用基於特定的Windows版本上使用。數據庫
緩存在不少狀況下須要用到,合理利用緩存能夠一方面能夠提升程序的響應速度,同時能夠減小對特定資源訪問的壓力。本文主要針對本身在Winform方面的緩存使用作一個引導性的介紹,但願你們可以從中瞭解一些緩存的使用場景和使用方法。緩存是一箇中大型系統所必須考慮的問題。爲了不每次請求都去訪問後臺的資源(例如數據庫),咱們通常會考慮將一些更新不是很頻繁的,能夠重用的數據,經過必定的方式臨時地保存起來,後續的請求根據狀況能夠直接訪問這些保存起來的數據。這種機制就是所謂的緩存機制。編程
.NET 4.0的緩存功能主要由三部分組成:System.Runtime.Caching,System.Web.Caching.Cache和Output Cache。緩存
System.Runtime.Caching這是在.NET 4.0中新增的緩存框架,主要是使用MemoryCache對象,該對象存在於程序集System.Runtime.Caching.dll。服務器
System.Web.Caching.Cache這個則是在.NET2.0開始就一直存在的緩存對象,通常主要用在Web中,固然也能夠用於Winform裏面,不過要引用System.Web.dll。框架
Output Cache則是Asp.NET裏面使用的,在ASP.NET 4.0以前的版本都是直接使用System.Web.Caching.Cache來緩存HTML片斷。在ASP.NET 4.0中對它進行了從新設計,提供了一個OutputCacheProvider供開發人員進行擴展,可是它默認狀況下,仍然使用System.Web.Caching.Cache來作作緩存分佈式
我在以前的一篇隨筆《Winform裏面的緩存使用》曾經介紹了MemoryCache輔助類的處理,用來方便實現緩存的數據操做。它的輔助類主要代碼以下所示。ide
/// <summary> /// 基於MemoryCache的緩存輔助類 /// </summary> public static class MemoryCacheHelper { private static readonly Object locker = new object(); /// <summary> /// 建立一個緩存的鍵值,並指定響應的時間範圍,若是失效,則自動獲取對應的值 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="key">對象的鍵</param> /// <param name="cachePopulate">獲取緩存值的操做</param> /// <param name="slidingExpiration">失效的時間範圍</param> /// <param name="absoluteExpiration">失效的絕對時間</param> /// <returns></returns> public static T GetCacheItem<T>(String key, Func<T> cachePopulate, TimeSpan? slidingExpiration = null, DateTime? absoluteExpiration = null) { if(String.IsNullOrWhiteSpace(key)) throw new ArgumentException("Invalid cache key"); if(cachePopulate == null) throw new ArgumentNullException("cachePopulate"); if(slidingExpiration == null && absoluteExpiration == null) throw new ArgumentException("Either a sliding expiration or absolute must be provided"); if(MemoryCache.Default[key] == null) { lock(locker) { if(MemoryCache.Default[key] == null) { var item = new CacheItem(key, cachePopulate()); var policy = CreatePolicy(slidingExpiration, absoluteExpiration); MemoryCache.Default.Add(item, policy); } } } return (T)MemoryCache.Default[key]; } private static CacheItemPolicy CreatePolicy(TimeSpan? slidingExpiration, DateTime? absoluteExpiration) { var policy = new CacheItemPolicy(); if(absoluteExpiration.HasValue) { policy.AbsoluteExpiration = absoluteExpiration.Value; } else if(slidingExpiration.HasValue) { policy.SlidingExpiration = slidingExpiration.Value; } policy.Priority = CacheItemPriority.Default; return policy; } /// <summary> /// 清空緩存 /// </summary> public static void ClearCache() { List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList(); foreach (string cacheKey in cacheKeys) { MemoryCache.Default.Remove(cacheKey); } } ...//省略部分代碼 }
而咱們在程序中,若是須要使用緩存,那麼調用這個輔助類來解決,也算是比較方便的,實現緩存的代碼以下所示。函數
public static class UserCacheService { /// <summary> /// 獲取用戶所有簡單對象信息,並放到緩存裏面 /// </summary> /// <returns></returns> public static List<SimpleUserInfo> GetSimpleUsers() { System.Reflection.MethodBase method = System.Reflection.MethodBase.GetCurrentMethod(); string key = string.Format("{0}-{1}", method.DeclaringType.FullName, method.Name); return MemoryCacheHelper.GetCacheItem<List<SimpleUserInfo>>(key, delegate() { //return CallerFactory<IUserService>.Instance.GetSimpleUsers(); //模擬從數據庫獲取數據 List<SimpleUserInfo> list = new List<SimpleUserInfo>(); for(int i = 0; i< 10; i++) { var info = new SimpleUserInfo(); info.ID = i; info.Name = string.Concat("Name:", i); info.FullName = string.Concat("姓名:", i); list.Add(info); } return list; }, new TimeSpan(0, 10, 0));//10分鐘過時 } /// <summary> /// 根據用戶的ID,獲取用戶的登錄名稱,並放到緩存裏面 /// </summary> /// <param name="userId">用戶的ID</param> /// <returns></returns> public static string GetNameByID(string userId) { string result = ""; if (!string.IsNullOrEmpty(userId)) { System.Reflection.MethodBase method = System.Reflection.MethodBase.GetCurrentMethod(); string key = string.Format("{0}-{1}-{2}", method.DeclaringType.FullName, method.Name, userId); result = MemoryCacheHelper.GetCacheItem<string>(key, delegate() { //return CallerFactory<IUserService>.Instance.GetNameByID(userId.ToInt32()); return string.Concat("Name:", userId); }, new TimeSpan(0, 30, 0));//30分鐘過時 } return result; }
上面案例我模擬構造數據庫數據返回,不然通常使用BLLFactory<T>、或者混合框架客戶端裏面使用CallerFactory<T>進行調用接口了,至關於須要對它們進行進一步的函數封裝處理才能達到目的。post
案例中能夠設置失效緩存時間,而且失效後,自動經過Func<T> cachePopulate的函數從新獲取緩存內容,在實際狀況下,也是很是智能的一種處理方式。
上面的案例使用MemoryCache輔助類來實現緩存的處理,可以解決實際的問題,不過同時問題也來了,每次緩存處理,都須要寫一段額外的代碼進行處理,代碼的冗餘就很是多了,並且一旦不少地方採用緩存,那麼維護這些代碼就很成問題。
咱們但願引入PostSharp技術,來減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。這種AOP的代碼織入技術可以很好分離橫切面和業務處理,從而實現簡化代碼的目的。
就上面的代碼問題,咱們來看看,引入PostSharp後,咱們的代碼是如何實現緩存處理的。
/// <summary> /// 使用PostSharp,結合MemoryCache實現緩存的處理類 /// </summary> public class CacheService { /// <summary> /// 獲取用戶所有簡單對象信息,並放到緩存裏面 /// </summary> /// <returns></returns> [Cache(ExpirationPeriod = 30)] public static List<SimpleUserInfo> GetSimpleUsers(int userid) {//return CallerFactory<IUserService>.Instance.GetSimpleUsers(); //模擬從數據庫獲取數據 List<SimpleUserInfo> list = new List<SimpleUserInfo>(); for (int i = 0; i < 10; i++) { var info = new SimpleUserInfo(); info.ID = i; info.Name = string.Concat("Name:", i); info.FullName = string.Concat("姓名:", i); list.Add(info); } return list; } /// <summary> /// 根據用戶的ID,獲取用戶的登錄名稱,並放到緩存裏面 /// </summary> /// <param name="userId">用戶的ID</param> /// <returns></returns> [Cache] public static string GetNameByID(string userId) {//return CallerFactory<IUserService>.Instance.GetNameByID(userId.ToInt32()); return string.Concat("Name:", userId); } }
咱們注意到了上面的函數代碼,除了調用業務邏輯(這裏構造數據演示)外,實際上是沒有多餘的其餘代碼的。不過咱們是在函數開始進行了一個特性的標識:
[Cache(ExpirationPeriod = 30)]
或者
[Cache]
這個就是咱們聲明使用緩存處理的函數,如此而已,是否是很是簡單了。
咱們來看看生成後的代碼反編譯獲得的結果,以下所示。
這個和咱們實際的代碼是不太同樣的,這裏整合了PostSharp的織入代碼,從而可以實現緩存的處理操做了,可是咱們在開發過程當中是透明的,只須要維護好本身編寫的代碼便可。
這個裏面須要使用了CacheAttribute來進行標識,這個類的代碼就是使用了PostSharp的基類進行處理了
/// <summary> /// 方法實現緩存的標識 /// </summary> [Serializable] public class CacheAttribute : MethodInterceptionAspect { /// <summary> /// 緩存的失效時間設置,默認採用30分鐘 /// </summary> public int ExpirationPeriod = 30; /// <summary> /// PostSharp的調用處理,實現數據的緩存處理 /// </summary> public override void OnInvoke(MethodInterceptionArgs args) { //默認30分鐘失效,若是設置過時時間,那麼採用設置值 TimeSpan timeSpan = new TimeSpan(0, 0, ExpirationPeriod, 0); var cache = MethodResultCache.GetCache(args.Method, timeSpan); var arguments = args.Arguments.ToList(); var result = cache.GetCachedResult(arguments); if (result != null) { args.ReturnValue = result; return; } else { base.OnInvoke(args); //調用後從新更新緩存 cache.CacheCallResult(args.ReturnValue, arguments); } } }
這個CacheAttribute特性類包含一個設置失效的時間間隔(分鐘),來指定函數返回結果的失效時間的,經過繼承MethodInterceptionAspect基類,咱們重寫了void OnInvoke(MethodInterceptionArgs args)函數,從而對調用過程的橫切面進行介入:
若是調用過程當中得到緩存結果,則直接返回,不須要調用函數業務邏輯;不然調用函數得到返回值,並從新設置緩存結果值。
在函數代碼裏面,經過傳入參數(包括方法對象、超時時間等)實現方法緩存對象的構建。
MethodResultCache.GetCache(args.Method, timeSpan);
在MethodResultCache裏面,咱們就是對方法的緩存進行處理的,首先須要聲明一個MemoryCache的對象用於管理緩存(分佈式緩存)。
/// <summary> /// 初始化緩存管理器 /// </summary> private void InitCacheManager() { _cache = new MemoryCache(_methodName); }
其中經過函數獲取方法和參數的鍵,也就是惟一的鍵。
/// <summary> /// 根據調用方法名稱和參數獲取緩存鍵 /// </summary> /// <param name="arguments">方法的參數列表</param> /// <returns></returns> private string GetCacheKey(IEnumerable<object> arguments) { var key = string.Format("{0}({1})", _methodName, string.Join(", ", arguments.Select(x => x != null ? x.ToString() : "<Null>"))); return key; }
設置緩存的操做,咱們就是調用MemoryCache緩存管理類來實現的鍵值設置的,以下代碼所示。
/// <summary> /// 緩存結果內容 /// </summary> /// <param name="result">待加入緩存的結果</param> /// <param name="arguments">方法的參數集合</param> public void CacheCallResult(object result, IEnumerable<object> arguments) { _cache.Set(GetCacheKey(arguments), result, DateTimeOffset.Now.Add(_expirationPeriod)); }
這樣咱們就設置了一個鍵值的緩存,並指定了緩存的失效時間,在這個時間段內,咱們每次獲取的數據,不須要再次調用外部接口,直接從緩存裏面獲取,速度提升不少,同時也減輕了分佈式構架中的服務器承載的IO壓力。
咱們能夠編寫一小段代碼進行測試出來的效率,以下代碼所示。
//First test DateTime start = DateTime.Now; var list = CacheService.GetSimpleUsers(1); int end = (int)DateTime.Now.Subtract(start).TotalMilliseconds; Console.WriteLine(" first: " + end); //Second test start = DateTime.Now; list = CacheService.GetSimpleUsers(2); end = (int)DateTime.Now.Subtract(start).TotalMilliseconds; Console.WriteLine(" Second: " + end);
得到的結果以下所示(分別介紹得到結果的時間)。
first: 519 Second: 501 first: 0 Second: 0 first: 0 Second: 0
從上面代碼能夠看出,第一次請求數據的有必定的時間差,後面請求毫秒數則是直接0了。
經過上面的 PostSharp和MemoryCache的整合,咱們能夠極大簡化了緩存的處理代碼,而且可以利用較爲不錯的MemoryCache緩存管理類來實現緩存的處理,很是方便和高效了。