這一講中主要是說EnterpriseLibrary企業級架構里的caching組件,它主要實現了項目緩存功能,它支持四種持久化方式,內存,文件,數據庫和自定義,對于持久化不是今天討論的重要,今天主要說,如何使用AOP的思想再配合Caching組件來實現可更新的,可插拔的,松耦合的,基于數據集(結果集)的緩存方案,之所以叫它方案,確實,在實現上有一定難度,我自己對于微軟的NLayerApp架構里用到的Attribute注入方式也對一定修改,因為NLayerApp里的緩存數據集并不支持方法參數為對象和lambda表達式的情況,而我的這個方案已經解決了上面兩種情況,可以說,完全支持!
我們來看一下Web.config對Caching的配置
緩存配置篇
注冊sections塊
<configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /> <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" /> </configSections>
配置caching塊
<cachingConfiguration defaultCacheManager="ByteartRetailCacheManager"> <cacheManagers> <add name="ByteartRetailCacheManager" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" expirationPollFrequencyInSeconds="600" maximumElementsInCacheBeforeScavenging="1000" numberToRemoveWhenScavenging="10" backingStoreName="NullBackingStore" /> <!-- expirationPollFrequencyInSeconds:過期時間(seconds) maximumElementsInCacheBeforeScavenging:緩沖中的最大元素數量 numberToRemoveWhenScavenging:一次移除的數量 --> </cacheManagers> <backingStores> <add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.505.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="NullBackingStore" /> </backingStores> </cachingConfiguration>
為unity塊添加要進行緩存的方法
<register type="NLayer_IoC_Demo.BLL.IUserService,NLayer_IoC_Demo.BLL" mapTo="NLayer_IoC_Demo.BLL.UserService,NLayer_IoC_Demo.BLL" > <!--接口攔截--> <interceptor type="InterfaceInterceptor" /> <!--緩存注入--> <interceptionBehavior type="Project.UnityCaching.CachingBehavior,Project.UnityCaching"/> </register>
緩存實現篇
1 通過CachingAttribute特性對方法進行標識,并配置緩存方式,get,put,remove,一般在添加,修改操作之后,會對緩存進行remove操作
/// <summary> /// 表示由此特性所描述的方法,能夠獲得來自Microsoft.Practices.EnterpriseLibrary.Caching基礎結構層所提供的緩存功能。 /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple=false, Inherited=false)] public class CachingAttribute : Attribute { #region Ctor /// <summary> /// 初始化一個新的<c>CachingAttribute</c>類型。 /// </summary> /// <param name="method">緩存方式。</param> public CachingAttribute(CachingMethod method) { this.Method = method; } /// <summary> /// 初始化一個新的<c>CachingAttribute</c>類型。 /// </summary> /// <param name="method">緩存方式。</param> /// <param name="correspondingMethodNames">與當前緩存方式相關的方法名稱。注:此參數僅在緩存方式為Remove時起作用。</param> public CachingAttribute(CachingMethod method, params string[] correspondingMethodNames) : this(method) { this.CorrespondingMethodNames = correspondingMethodNames; } #endregion #region Public Properties /// <summary> /// 獲取或設置緩存方式。 /// </summary> public CachingMethod Method { get; set; } /// <summary> /// 獲取或設置一個<see cref="Boolean"/>值,該值表示當緩存方式為Put時,是否強制將值寫入緩存中。 /// </summary> public bool Force { get; set; } /// <summary> /// 獲取或設置與當前緩存方式相關的方法名稱。注:此參數僅在緩存方式為Remove時起作用。 /// </summary> public string[] CorrespondingMethodNames { get; set; } #endregion }
2 CachingMethod標識了緩存的方式
/// <summary> /// 表示用于Caching特性的緩存方式。 /// </summary> public enum CachingMethod { /// <summary> /// 表示需要從緩存中獲取對象。如果緩存中不存在所需的對象,系統則會調用實際的方法獲取對象, /// 然后將獲得的結果添加到緩存中。 /// </summary> Get, /// <summary> /// 表示需要將對象存入緩存。此方式會調用實際方法以獲取對象,然后將獲得的結果添加到緩存中, /// 并直接返回方法的調用結果。 /// </summary> Put, /// <summary> /// 表示需要將對象從緩存中移除。 /// </summary>
3 一個標準的緩存CRUD接口,它默認使用Microsoft.Practices.EnterpriseLibrary.Caching來實現,當前你可以進行利用這個接口來實現多態
/// <summary> /// 表示實現該接口的類型是能夠為應用程序提供緩存機制的類型。 /// </summary> public interface ICacheProvider { #region Methods /// <summary> /// 向緩存中添加一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> void Add(string key, string valKey, object value); /// <summary> /// 向緩存中更新一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> void Put(string key, string valKey, object value); /// <summary> /// 從緩存中讀取對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <returns>被緩存的對象。</returns> object Get(string key, string valKey); /// <summary> /// 從緩存中移除對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> void Remove(string key); /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> bool Exists(string key); /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值和緩存值鍵的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <param name="valKey">緩存值鍵。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> bool Exists(string key, string valKey); #endregion }
4 使用Microsoft.Practices.EnterpriseLibrary.Caching來實現緩存的持久化功能
/// <summary> /// 表示基于Microsoft Patterns & Practices - Enterprise Library Caching Application Block的緩存機制的實現。 /// </summary> public class EntLibCacheProvider : ICacheProvider { #region Private Fields private readonly ICacheManager _cacheManager = CacheFactory.GetCacheManager(); #endregion #region ICacheProvider Members /// <summary> /// 向緩存中添加一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Add(string key, string valKey, object value) { Dictionary<string, object> dict = null; if (_cacheManager.Contains(key)) { dict = (Dictionary<string, object>)_cacheManager[key]; dict[valKey] = value; } else { dict = new Dictionary<string, object>(); dict.Add(valKey, value); } _cacheManager.Add(key, dict); } /// <summary> /// 向緩存中更新一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Put(string key, string valKey, object value) { Add(key, valKey, value); } /// <summary> /// 從緩存中讀取對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <returns>被緩存的對象。</returns> public object Get(string key, string valKey) { if (_cacheManager.Contains(key)) { Dictionary<string, object> dict = (Dictionary<string, object>)_cacheManager[key]; if (dict != null && dict.ContainsKey(valKey)) return dict[valKey]; else return null; } return null; } /// <summary> /// 從緩存中移除對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> public void Remove(string key) { _cacheManager.Remove(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key) { return _cacheManager.Contains(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值和緩存值鍵的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <param name="valKey">緩存值鍵。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key, string valKey) { return _cacheManager.Contains(key) && ((Dictionary<string, object>)_cacheManager[key]).ContainsKey(valKey); } #endregion }
5 一個工廠模塊,來對緩存的持久化方式進行創建,這個一般可以在配置文件中動態去配置的,本類使用簡單的單例模式來進行創建,不考慮多線程情況
/// <summary> /// 緩存持久化工廠類 /// </summary> public sealed class CacheManager : ICacheProvider { #region Private Fields private readonly ICacheProvider _cacheProvider; private static readonly CacheManager _instance = new CacheManager(); #endregion #region Ctor static CacheManager() { } private CacheManager() { _cacheProvider = new EntLibCacheProvider(); } #endregion #region Public Properties /// <summary> /// 獲取<c>CacheManager</c>類型的單件(Singleton)實例。 /// </summary> public static CacheManager Instance { get { return _instance; } } #endregion #region ICacheProvider Members /// <summary> /// 向緩存中添加一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Add(string key, string valKey, object value) { _cacheProvider.Add(key, valKey, value); } /// <summary> /// 向緩存中更新一個對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <param name="value">需要緩存的對象。</param> public void Put(string key, string valKey, object value) { _cacheProvider.Put(key, valKey, value); } /// <summary> /// 從緩存中讀取對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> /// <param name="valKey">緩存值的鍵值,該值通常是由使用緩存機制的方法的參數值所產生。</param> /// <returns>被緩存的對象。</returns> public object Get(string key, string valKey) { return _cacheProvider.Get(key, valKey); } /// <summary> /// 從緩存中移除對象。 /// </summary> /// <param name="key">緩存的鍵值,該值通常是使用緩存機制的方法的名稱。</param> public void Remove(string key) { _cacheProvider.Remove(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key) { return _cacheProvider.Exists(key); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示擁有指定鍵值和緩存值鍵的緩存是否存在。 /// </summary> /// <param name="key">指定的鍵值。</param> /// <param name="valKey">緩存值鍵。</param> /// <returns>如果緩存存在,則返回true,否則返回false。</returns> public bool Exists(string key, string valKey) { return _cacheProvider.Exists(key, valKey); } #endregion }
7 最后貢獻緩存攔截類,這是核心,是提供AOP功能的核心,其中自己添加了對結構體和lambda表達式和類的方法參數的支持,原版應該是陳晴陽寫的,但它不支持結構體和lambda和類,所以,我的版本把它完善了。
/// <summary> /// 表示用于方法緩存功能的攔截行為。 /// </summary> public class CachingBehavior : IInterceptionBehavior { #region Private Methods /// <summary> /// 根據指定的<see cref="CachingAttribute"/>以及<see cref="IMethodInvocation"/>實例, /// 獲取與某一特定參數值相關的鍵名。 /// </summary> /// <param name="cachingAttribute"><see cref="CachingAttribute"/>實例。</param> /// <param name="input"><see cref="IMethodInvocation"/>實例。</param> /// <returns>與某一特定參數值相關的鍵名。</returns> private string GetValueKey(CachingAttribute cachingAttribute, IMethodInvocation input) { switch (cachingAttribute.Method) { // 如果是Remove,則不存在特定值鍵名,所有的以該方法名稱相關的緩存都需要清除 case CachingMethod.Remove: return null; // 如果是Get或者Put,則需要產生一個針對特定參數值的鍵名 case CachingMethod.Get: case CachingMethod.Put: if (input.Arguments != null && input.Arguments.Count > 0) { var sb = new StringBuilder(); for (int i = 0; i < input.Arguments.Count; i++) { if (input.Arguments[i].GetType().BaseType == typeof(LambdaExpression))//lambda處理 { var exp = input.Arguments[i] as LambdaExpression; var arr = ((System.Runtime.CompilerServices.Closure)(((System.Delegate)(Expression.Lambda(exp).Compile().DynamicInvoke())).Target)).Constants; Type t = arr[0].GetType(); string result = ""; foreach (var member in t.GetFields()) { result += member.Name + "_" + t.GetField(member.Name).GetValue(arr[0]) + "_"; } result = result.Remove(result.Length - 1); sb.Append(result.ToString()); } else if (input.Arguments[i].GetType() != typeof(string)//類和結構體處理 && input.Arguments[i].GetType().BaseType.IsClass) { var obj = input.Arguments[i]; Type t = obj.GetType(); string result = ""; foreach (var member in t.GetProperties()) { result += member.Name + "_" + t.GetProperty(member.Name).GetValue(obj) + "_"; } result = result.Remove(result.Length - 1); sb.Append(result.ToString()); } else//簡單值類型處理 { sb.Append(input.Arguments[i].ToString()); } if (i != input.Arguments.Count - 1) sb.Append("_"); } return sb.ToString(); } else return "NULL"; default: throw new InvalidOperationException("無效的緩存方式。"); } } #endregion #region IInterceptionBehavior Members /// <summary> /// 獲取當前行為需要攔截的對象類型接口。 /// </summary> /// <returns>所有需要攔截的對象類型接口。</returns> public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } /// <summary> /// 通過實現此方法來攔截調用并執行所需的攔截行為。 /// </summary> /// <param name="input">調用攔截目標時的輸入信息。</param> /// <param name="getNext">通過行為鏈來獲取下一個攔截行為的委托。</param> /// <returns>從攔截目標獲得的返回信息。</returns> public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { var method = input.MethodBase; var key = method.Name; if (method.IsDefined(typeof(CachingAttribute), false)) { var cachingAttribute = (CachingAttribute)method.GetCustomAttributes(typeof(CachingAttribute), false)[0]; var valKey = GetValueKey(cachingAttribute, input); switch (cachingAttribute.Method) { case CachingMethod.Get: try { if (CacheManager.Instance.Exists(key, valKey)) { var obj = CacheManager.Instance.Get(key, valKey); var arguments = new object[input.Arguments.Count]; input.Arguments.CopyTo(arguments, 0); return new VirtualMethodReturn(input, obj, arguments); } else { var methodReturn = getNext().Invoke(input, getNext); CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); return methodReturn; } } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } case CachingMethod.Put: try { var methodReturn = getNext().Invoke(input, getNext); if (CacheManager.Instance.Exists(key)) { if (cachingAttribute.Force) { CacheManager.Instance.Remove(key); CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); } else CacheManager.Instance.Put(key, valKey, methodReturn.ReturnValue); } else CacheManager.Instance.Add(key, valKey, methodReturn.ReturnValue); return methodReturn; } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } case CachingMethod.Remove: try { var removeKeys = cachingAttribute.CorrespondingMethodNames; foreach (var removeKey in removeKeys) { if (CacheManager.Instance.Exists(removeKey)) CacheManager.Instance.Remove(removeKey); } var methodReturn = getNext().Invoke(input, getNext); return methodReturn; } catch (Exception ex) { return new VirtualMethodReturn(input, ex); } default: break; } } return getNext().Invoke(input, getNext); } /// <summary> /// 獲取一個<see cref="Boolean"/>值,該值表示當前攔截行為被調用時,是否真的需要執行 /// 某些操作。 /// </summary> public bool WillExecute { get { return true; } } #endregion }
緩存調用篇
我們的緩存只能配置在接口的方法中,這主要考慮到unity的注入環節和面向對象的多態特性,本例中,緩存這塊配置在了BLL層中,當然,如果你的架構允許,也可以做在DATA層中,當然DATA層的緩存力度可能太大,我覺得并不太合適,但代碼可能更精簡,所
以,大家要因情況而議,到在哪層都沒問題。
public interface IUserService { [Caching(CachingMethod.Get)] PagedList<WebManageUsers> GetWebManageUsers(PageParameters pp); [Caching(CachingMethod.Get)] PagedList<WebManageUsers> GetWebManageUsers(Expression<Func<WebManageUsers, bool>> predicate, PageParameters pp); [Caching(CachingMethod.Remove, "GetWebManageUsers")] void InsertManageUsers(NLayer_IoC_Demo.Entity.WebManageUsers entity); }
private readonly IUserService _userService = ServiceLocator.Instance.GetService<IUserService>(); public ActionResult Index(string name, int page = 1) { ViewBag.Message = "緩存篇"; if (string.IsNullOrWhiteSpace(name)) return View(_userService.GetWebManageUsers(new PageParameters(page, 3))); else { Expression<Func<WebManageUsers, bool>> predicate = i => i.LoginName.Contains(name); return View(_userService.GetWebManageUsers(predicate, new PageParameters(page, 3))); } }
緩存配置好后,可以使用sql profiler等監控工具去查看數據庫的訪問情況!
文章列表