在.NET項目中使用PostSharp,使用MemoryCache實現緩存的處理

在以前一篇隨筆《在.NET項目中使用PostSharp,實現AOP面向切面編程處理》介紹了PostSharp框架的使用,試用PostSharp能給我帶來不少便利和優點,減小代碼冗餘,提升可讀性,而且能夠更加優雅的實現常規的日誌、異常、緩存、事務等業務場景的處理。本篇主要介紹使用MemoryCache實現緩存的處理。html

一、MemoryCache的介紹回顧

上篇沒有說起緩存的處理,通常狀況下,緩存的處理咱們能夠利用微軟的分佈式緩存組件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的函數從新獲取緩存內容,在實際狀況下,也是很是智能的一種處理方式。

二、結合PostSharp和MemoryCache實現緩存

上面的案例使用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緩存管理類來實現緩存的處理,很是方便和高效了。

相關文章
相關標籤/搜索