基於DDD的.NET開發框架 - ABP緩存Caching實現


返回ABP系列

ABP是“ASP.NET Boilerplate Project (ASP.NET樣板項目)”的簡稱。

ASP.NET Boilerplate是一個用最佳實踐和流行技術開發現代WEB應用程序的新起點,它旨在成為一個通用的WEB應用程序框架和項目模板。

ABP的官方網站:http://www.aspnetboilerplate.com

ABP官方文檔:http://www.aspnetboilerplate.com/Pages/Documents

Github上的開源項目:https://github.com/aspnetboilerplate

一、基本概念

ABP中有兩種cache的實現方式:MemroyCache 和 RedisCache,兩者都繼承至ICache接口(准確說是CacheBase抽象類)。ABP核心模塊封裝了MemroyCache 來實現ABP中的默認緩存功能。 Abp.RedisCache這個模塊封裝RedisCache來實現緩存(通過StackExchange.Redis這個類庫訪問redis)。

1、ICacheManager:

緩存的主要接口是ICacheManager。注入該接口並使用該接口獲得一個緩存對象:

        public class TestAppService : ApplicationService
        {
            private readonly ICacheManager _cacheManager;

            public TestAppService(ICacheManager cacheManager)
            {
                _cacheManager = cacheManager;
            }

            public Item GetItem(int id)
            {
                //從緩存中獲取
                return _cacheManager
                        .GetCache("MyCache")
                        .Get(id.ToString(), () => GetFromDatabase(id)) as Item;
            }

            public Item GetFromDatabase(int id)
            {
                //... 從數據庫中檢索
            }
        }

不要在構造函數中使用GetCache方法。如果你的類是transient(每次使用都會創建)的,那么這可能會釋放緩存,因為第二次創建類的對象時,會再次調用構造函數,之前的第一次的緩存可能會被釋放。

2、ICache:

ICacheManager.GetCache方法返回一個ICache。緩存對象是單例的,第一次請求時會創建緩存,以后都是返回相同的緩存對象。因此,我們可以在不同的類(客戶端)中共享具有相同名字的相同緩存。

在樣例代碼中,我們看到了ICache.Get方法的簡單使用。它有兩個參數:

key:緩存中一個條目的唯一字符串鍵。

factory:沒有找到給定key的緩存條目時調用的action。工廠方法應該創建並返回實際的條目。如果給定的key在緩存中找到了,那么不會調用該action。ICache接口也有像GetOrDefault,Set,Remove,Clear的方法。同時,這些方法也有async版本。

3、ITypedCache:

ICache接口的key為string類型,value為object類型。ITypeCache是ICache的包裝器,提供類型安全、泛型的cache。為了將ICache轉為ITypedCache,我們可以使用AsTyped擴展方法,如下所示:

ITypedCache<int, Item> myCache = _cacheManager.GetCache("MyCache").AsTyped<int, Item>();

這樣,就不需要轉換直接可以使用Get方法。

4、配置

緩存的默認有效期是60min。如果你在60min內都沒有使用緩存中的元素,那么它會自動從緩存中移除。對於所有的緩存或者特定的某個緩存,你都可以配置有效期。

//為所有緩存配置有效期
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

//為特定的緩存配置有效期
Configuration.Caching.Configure("MyCache", cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(8);
});

把上面代碼放到模塊中的PreInitialize方法中。有了這樣的配置,MyCache會有8小時的有效期,而其他cache會有2小時有效期。

cache只要首次創建(第一次請求時),就會調用配置的action。配置並不只局限於DefaultSlidingExpireTime(默認有效期),因為cache對象是一個ICache,你可以使用它的屬性和方法自由地配置並初始化。

5、Redis緩存集成

ABP默認緩存管理是使用內存緩存。可以使用Redis作為分布式緩存服務器。

首先,需要安裝abp.rediscache NuGet包添加到您的應用程序(可以把它安裝到您的Web項目)。然后添加一個AbpRedisCacheModule依賴,在你的模塊PreInitialize配置使用:

//...引入命名空間
using Abp.Runtime.Caching.Redis;
namespace MyProject.AbpZeroTemplate.Web
{
    [DependsOn(
        //...模塊依賴
        typeof(AbpRedisCacheModule))]
    public class MyProjectWebModule : AbpModule
    {
        public override void PreInitialize()
        {
            //...配置
            Configuration.Caching.UseRedis();
        }
        //...other code
    }
}

Abp.RedisCache默認使用“localhost”作為默認連接字符串。您可以將連接字符串添加到配置文件中以重寫它:

<add name="Abp.Redis.Cache" connectionString="localhost"/>

還可以添加設置里設置Redis數據庫ID。例如:

<add key="Abp.Redis.Cache.DatabaseId" value="2"/>

注:在ABP中使用Redis緩存請先安裝Redis服務器,更多redis配置信息

二、ABP緩存Caching源代碼分析

目錄結構:

類圖:

ICache:緩存的接口

using System;
using System.Threading.Tasks;

namespace Abp.Runtime.Caching
{
    /// <summary>
    /// Defines a cache that can be store and get items by keys.
    /// </summary>
    public interface ICache : IDisposable
    {
        /// <summary>
        /// Unique name of the cache.
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Default sliding expire time of cache items.
        /// Default value: 60 minutes. Can be changed by configuration.
        /// </summary>
        TimeSpan DefaultSlidingExpireTime { get; set; }

        /// <summary>
        /// Gets an item from the cache.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="factory">Factory method to create cache item if not exists</param>
        /// <returns>Cached item</returns>
        object Get(string key, Func<string, object> factory);

        /// <summary>
        /// Gets an item from the cache.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="factory">Factory method to create cache item if not exists</param>
        /// <returns>Cached item</returns>
        Task<object> GetAsync(string key, Func<string, Task<object>> factory);

        /// <summary>
        /// Gets an item from the cache or null if not found.
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>Cached item or null if not found</returns>
        object GetOrDefault(string key);

        /// <summary>
        /// Gets an item from the cache or null if not found.
        /// </summary>
        /// <param name="key">Key</param>
        /// <returns>Cached item or null if not found</returns>
        Task<object> GetOrDefaultAsync(string key);

        /// <summary>
        /// Saves/Overrides an item in the cache by a key.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        /// <param name="slidingExpireTime">Sliding expire time</param>
        void Set(string key, object value, TimeSpan? slidingExpireTime = null);

        /// <summary>
        /// Saves/Overrides an item in the cache by a key.
        /// </summary>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        /// <param name="slidingExpireTime">Sliding expire time</param>
        Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null);

        /// <summary>
        /// Removes a cache item by it's key.
        /// </summary>
        /// <param name="key">Key</param>
        void Remove(string key);

        /// <summary>
        /// Removes a cache item by it's key (does nothing if given key does not exists in the cache).
        /// </summary>
        /// <param name="key">Key</param>
        Task RemoveAsync(string key);

        /// <summary>
        /// Clears all items in this cache.
        /// </summary>
        void Clear();

        /// <summary>
        /// Clears all items in this cache.
        /// </summary>
        Task ClearAsync();
    }
}
View Code

CacheBase:緩存基類

using System;
using System.Threading.Tasks;
using Nito.AsyncEx;

namespace Abp.Runtime.Caching
{
    /// <summary>
    /// Base class for caches.
    /// It's used to simplify implementing <see cref="ICache"/>.
    /// </summary>
    public abstract class CacheBase : ICache
    {
        public string Name { get; private set; }

        public TimeSpan DefaultSlidingExpireTime { get; set; }

        protected readonly object SyncObj = new object();

        private readonly AsyncLock _asyncLock = new AsyncLock();

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="name"></param>
        protected CacheBase(string name)
        {
            Name = name;
            DefaultSlidingExpireTime = TimeSpan.FromHours(1);
        }

        public virtual object Get(string key, Func<string, object> factory)
        {
            var cacheKey = key;
            var item = GetOrDefault(key);
            if (item == null)
            {
                lock (SyncObj)
                {
                    item = GetOrDefault(key);
                    if (item == null)
                    {
                        item = factory(key);
                        if (item == null)
                        {
                            return null;
                        }

                        Set(cacheKey, item);
                    }
                }
            }

            return item;
        }

        public virtual async Task<object> GetAsync(string key, Func<string, Task<object>> factory)
        {
            var cacheKey = key;
            var item = await GetOrDefaultAsync(key);
            if (item == null)
            {
                using (await _asyncLock.LockAsync())
                {
                    item = await GetOrDefaultAsync(key);
                    if (item == null)
                    {
                        item = await factory(key);
                        if (item == null)
                        {
                            return null;
                        }

                        await SetAsync(cacheKey, item);
                    }
                }
            }

            return item;
        }

        public abstract object GetOrDefault(string key);

        public virtual Task<object> GetOrDefaultAsync(string key)
        {
            return Task.FromResult(GetOrDefault(key));
        }

        public abstract void Set(string key, object value, TimeSpan? slidingExpireTime = null);

        public virtual Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null)
        {
            Set(key, value, slidingExpireTime);
            return Task.FromResult(0);
        }

        public abstract void Remove(string key);

        public virtual Task RemoveAsync(string key)
        {
            Remove(key);
            return Task.FromResult(0);
        }

        public abstract void Clear();

        public virtual Task ClearAsync()
        {
            Clear();
            return Task.FromResult(0);
        }

        public virtual void Dispose()
        {

        }
    }
}
View Code

ITypedCache/TypedCacheWrapper: 支持泛型key和value的緩存接口與實現,其內部通過封裝ICache實例和CacheExtension定義的對ICache的擴展方法來是實現泛型版本的Icache.

using System;
using System.Threading.Tasks;

namespace Abp.Runtime.Caching
{
    /// <summary>
    /// Implements <see cref="ITypedCache{TKey,TValue}"/> to wrap a <see cref="ICache"/>.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    public class TypedCacheWrapper<TKey, TValue> : ITypedCache<TKey, TValue>
    {
        public string Name
        {
            get { return InternalCache.Name; }
        }

        public TimeSpan DefaultSlidingExpireTime
        {
            get { return InternalCache.DefaultSlidingExpireTime; }
            set { InternalCache.DefaultSlidingExpireTime = value; }
        }

        public ICache InternalCache { get; private set; }

        /// <summary>
        /// Creates a new <see cref="TypedCacheWrapper{TKey,TValue}"/> object.
        /// </summary>
        /// <param name="internalCache">The actual internal cache</param>
        public TypedCacheWrapper(ICache internalCache)
        {
            InternalCache = internalCache;
        }

        public void Dispose()
        {
            InternalCache.Dispose();
        }

        public void Clear()
        {
            InternalCache.Clear();
        }

        public Task ClearAsync()
        {
            return InternalCache.ClearAsync();
        }

        public TValue Get(TKey key, Func<TKey, TValue> factory)
        {
            return InternalCache.Get(key, factory);
        }

        public Task<TValue> GetAsync(TKey key, Func<TKey, Task<TValue>> factory)
        {
            return InternalCache.GetAsync(key, factory);
        }

        public TValue GetOrDefault(TKey key)
        {
            return InternalCache.GetOrDefault<TKey, TValue>(key);
        }

        public Task<TValue> GetOrDefaultAsync(TKey key)
        {
            return InternalCache.GetOrDefaultAsync<TKey, TValue>(key);
        }

        public void Set(TKey key, TValue value, TimeSpan? slidingExpireTime = null)
        {
            InternalCache.Set(key.ToString(), value, slidingExpireTime);
        }

        public Task SetAsync(TKey key, TValue value, TimeSpan? slidingExpireTime = null)
        {
            return InternalCache.SetAsync(key.ToString(), value, slidingExpireTime);
        }

        public void Remove(TKey key)
        {
            InternalCache.Remove(key.ToString());
        }

        public Task RemoveAsync(TKey key)
        {
            return InternalCache.RemoveAsync(key.ToString());
        }
    }
}
View Code

CacheExtension: 定義了ICache的擴展方法. 最關鍵的是如下兩個支持泛型的方法:GetOrDefault和GetOrDefaultAsync。如下,內部調用ICache實例的相應方法並通過類型轉換。

using System;
using System.Threading.Tasks;

namespace Abp.Runtime.Caching
{
    /// <summary>
    /// Extension methods for <see cref="ICache"/>.
    /// </summary>
    public static class CacheExtensions
    {
        public static object Get(this ICache cache, string key, Func<object> factory)
        {
            return cache.Get(key, k => factory());
        }

        public static Task<object> GetAsync(this ICache cache, string key, Func<Task<object>> factory)
        {
            return cache.GetAsync(key, k => factory());
        }

        public static ITypedCache<TKey, TValue> AsTyped<TKey, TValue>(this ICache cache)
        {
            return new TypedCacheWrapper<TKey, TValue>(cache);
        }
        
        public static TValue Get<TKey, TValue>(this ICache cache, TKey key, Func<TKey, TValue> factory)
        {
            return (TValue)cache.Get(key.ToString(), (k) => (object)factory(key));
        }

        public static TValue Get<TKey, TValue>(this ICache cache, TKey key, Func<TValue> factory)
        {
            return cache.Get(key, (k) => factory());
        }

        public static async Task<TValue> GetAsync<TKey, TValue>(this ICache cache, TKey key, Func<TKey, Task<TValue>> factory)
        {
            var value = await cache.GetAsync(key.ToString(), async (keyAsString) =>
            {
                var v = await factory(key);
                return (object)v;
            });

            return (TValue)value;
        }

        public static Task<TValue> GetAsync<TKey, TValue>(this ICache cache, TKey key, Func<Task<TValue>> factory)
        {
            return cache.GetAsync(key, (k) => factory());
        }

        public static TValue GetOrDefault<TKey, TValue>(this ICache cache, TKey key)
        {
            var value = cache.GetOrDefault(key.ToString());
            if (value == null)
            {
                return default(TValue);
            }

            return (TValue) value;
        }

        public static async Task<TValue> GetOrDefaultAsync<TKey, TValue>(this ICache cache, TKey key)
        {
            var value = await cache.GetOrDefaultAsync(key.ToString());
            if (value == null)
            {
                return default(TValue);
            }

            return (TValue)value;
        }
    }
}
View Code

AbpCacheNames:定義了四個cache的key常量,這幾個cache是供ABP框架使用的

namespace Abp.Runtime.Caching
{
    /// <summary>
    /// Names of standard caches used in ABP.
    /// </summary>
    public static class AbpCacheNames
    {
        /// <summary>
        /// Application settings cache: AbpApplicationSettingsCache.
        /// </summary>
        public const string ApplicationSettings = "AbpApplicationSettingsCache";

        /// <summary>
        /// Tenant settings cache: AbpTenantSettingsCache.
        /// </summary>
        public const string TenantSettings = "AbpTenantSettingsCache";

        /// <summary>
        /// User settings cache: AbpUserSettingsCache.
        /// </summary>
        public const string UserSettings = "AbpUserSettingsCache";

        /// <summary>
        /// Localization scripts cache: AbpLocalizationScripts.
        /// </summary>
        public const string LocalizationScripts = "AbpLocalizationScripts";
    }
}
AbpCacheNames

ICacheConfigurator/CacheConfigurator:封裝了cachename和對該cahce的初始化方法,通過初始化方法可以完成對cache的配置

using System;

namespace Abp.Runtime.Caching.Configuration
{
    internal class CacheConfigurator : ICacheConfigurator
    {
        public string CacheName { get; private set; }

        public Action<ICache> InitAction { get; private set; }

        public CacheConfigurator(Action<ICache> initAction)
        {
            InitAction = initAction;
        }

        public CacheConfigurator(string cacheName, Action<ICache> initAction)
        {
            CacheName = cacheName;
            InitAction = initAction;
        }
    }
}
CacheConfigurator

ICachingConfiguration/CachingConfiguration該接口提供完成cache的配置的方法。具體是通過封裝了一個ICacheConfigurator集合,並調用CacheConfigurator的InitAction來配置cache

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Abp.Configuration.Startup;

namespace Abp.Runtime.Caching.Configuration
{
    internal class CachingConfiguration : ICachingConfiguration
    {
        public IAbpStartupConfiguration AbpConfiguration { get; private set; }

        public IReadOnlyList<ICacheConfigurator> Configurators
        {
            get { return _configurators.ToImmutableList(); }
        }
        private readonly List<ICacheConfigurator> _configurators;

        public CachingConfiguration(IAbpStartupConfiguration abpConfiguration)
        {
            AbpConfiguration = abpConfiguration;

            _configurators = new List<ICacheConfigurator>();
        }

        public void ConfigureAll(Action<ICache> initAction)
        {
            _configurators.Add(new CacheConfigurator(initAction));
        }

        public void Configure(string cacheName, Action<ICache> initAction)
        {
            _configurators.Add(new CacheConfigurator(cacheName, initAction));
        }
    }
}
CachingConfiguration

ICacheManager/CacheManagerBase: 該接口和實現用於生成,配置以及銷毀ICache實例。具體是通過ICachingConfiguration對象來初始化cache, 並通過ConcurrentDictionary<string, ICache>來存放和管理cache.

AbpMemoryCache:通過MemoryCache來實現Icache.

AbpMemoryCacheManager:重寫了CacheManagerBaseCreateCacheImplementation方法,該方法用於創建真實的Icache對象。 具體到AbpMemoryCacheManager就是創建AbpMemoryCache

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM