• 缓存篇~第六回 Microsoft.Practices.EnterpriseLibrary.Caching实现基于方法签名的数据集缓存


    返回目录

    这一讲中主要是说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等监控工具去查看数据库的访问情况!

    返回目录

  • 相关阅读:
    数据库优化方案之分库分表
    聊聊ThreadLocal源码(基于JDK1.8)
    HashMap扩容死循环问题
    Synchronized偏向锁和轻量级锁的升级
    【Java并发编程之深入理解】Synchronized的使用
    ConcurrentHashMap 1.8为什么要使用CAS+Synchronized取代Segment+ReentrantLock
    面试必备技能:HashMap哪些技术点会被经常问到?
    序列键生成器及单例多例模式
    Effective.Java第1-11条
    抽象工厂(AbstractFactory)模式
  • 原文地址:https://www.cnblogs.com/lori/p/4063807.html
Copyright © 2020-2023  润新知