一. 揭开迷雾
1. 程序集准备
a. 需要给项目添加 System.Web 程序集。
b. 需要给使用的地方添加两个引用。
2. 程序集探究
在对应的类中输入关键字 Cache,选中点击F12,查看Cache程序集,如下图所示:
我们一起来分析从上往下来分析一下该程序集。
(1). 两个字段,根据类型能看出来,一个是具体的时间点,另一个时间段,我们猜想,他们可能分别对应缓存中的绝对过期时间和相对过期时间。(其实事实也是如此)
(2). 构造函数
注意:通常我们实例化该类型的缓存,一般不直接通过构造函数实例化,我们通常使用 HttpRuntime.Cache 。我们一起来看看HttpRuntime的程序集,其中有个Cache属性,即可以通过它来获取Cache对象的实例。
(3). 顾名思义通俗易懂的属性和方法。
A : Count 属性,获取缓存个数。
B : Get(string key) 方法,根据缓存的键名来获取缓存的值。
C : Remove(string key) 方法,根据缓存的键名来删除缓存的值。
(4). 深究 public IDictionaryEnumerator GetEnumerator();
查看IDictionaryEnumerator的程序集,发现他是一个字典类型的集合,有键有值。
那么问题来了,怎么遍历该字典类型的集合呢?怎么获取对应的键和值呢?F12查看 IEnumerator程序集,发现里面有一个MoveNext()方法,可以推进到集合的下一个元素。
(5). Add和Insert方法,二者都为增加缓存的方法,而且二者最多参数的那个重载参数类型完全相同。不同的是Insert有多种重载方式,而Add只有一种重载。 二者核心差异:当缓存已经存在,用Add添加会报错,但用Insert添加,会直接覆盖更新。
下面以Add为例,一起来分析这七个参数。
A:key: 代表缓存的键名。
B:value:代表缓存的值,可以是任意类型。
C:dependencies:表示缓存的依赖项,可以依赖文件、文件夹、多个文件、以及依赖数据库,如果不需要依赖时,传入参数null。
D:absoluteExpiration:代表绝对过期时间,是个DateTime类型的时间点。如果要启用绝对过期时间,那么slidingExpriation必须为:Cache.NoSlidingExpiration。
该参数表现形式可以是:如:DateTime.Now.AddDays(1); DateTime.Parse("2016-5-28 20:32:00") 。
E:slidingExpiration:代表相对过期时间,是个TimeSpan类型的时间段。 如果要启用相对过期时间,那么absoluteExpiration必须为:Cache.NoAbsoluteExpiration。
该参数的表现形式可以是:: 如:new TimeSpan(0,0, 0, 2) 天,小时,分钟,秒 。
F:priority:代表缓存销毁时的移除顺序,优先级低的先销毁。没有特殊要求通常使用: CacheItemPriority.Normal 即可。
G:onRemoveCallBack:是一个无返回值具有三个参数的委托。public delegate void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason);
当缓存过期时,调用该委托。委托的三个参数分别代表的含义为:缓存的键、缓存的值、缓存销毁的原因。
二. 缓存封装
1. 封装思路
首先我们要清楚,封装的目的是什么? 说白了,方便自己、方便别人的灵活调用;同时便于以后的代码维护,能应对需求的变更。
通常调用的方式有两种:1. 将方法写成static静态方法,直接通过 类名.方法名 来进行调用。(该种方式偏离了面向对象的体系)
2. 将该类实例化,然后用实例化的对象调用该类中的封装方法。
3. 这里暂时不考虑 IOC的方式创建对象。
我们试想,会有什么需求变化呢? 有一天,PM要求,所有使用System.Web.Caching.Cache缓存的地方,都要改成 MemoryCache,如果我们事先没有应对措施,我的天!数不胜数的方法中均是传入的 Cache实例,不支持MemoryCache的实例,排着改?保证你崩溃!!!!!
上述场景,在实际开发中非常常见,解决该问题,我们通常使用DIP原则进行封装(依赖倒置原则,核心面向接口编程)。
所以我们定义一个缓存接口ICache,约束了缓存的定义的规范,然后我们再建立一个 RuntimeCacheHelp 类,对ICache进行了实现,同时也定义自己特有的方法。
2. 话不多说,直接上代码。
接口:
1 /// <summary> 2 /// 定义缓存接口 3 /// </summary> 4 public interface ICache 5 { 6 //1.缓存的个数只能获取,不能设置 7 int Count { get; } 8 //2. 删除特定键的缓存 9 void Remove(string key); 10 //3.移除全部缓存 11 void RemoveAll(); 12 //4.根据键值获取特定类型的缓存 13 T Get<T>(string key); 14 //5.判断缓存是否存在 15 bool Contains(string key); 16 //6. 获取缓存值的类型 17 Type GetCacheType(string key); 18 //7. 增加或更新缓存(存在则更新,不存在则添加,采用绝对时间的模式) 19 /// <param name="cacheTime">绝对过期时间,默认1天</param> 20 void AddOrUpdate(string key, object value,int cacheTime=1); 21 22 }
缓存帮助类:
1 /// <summary> 2 /// HttpRuntime缓存 3 /// 需要引入:System.Web程序集 4 /// 支持缓存依赖 5 /// </summary> 6 public class RuntimeCacheHelp : ICache 7 { 8 //一.实例化的两种方式 9 10 #region 1.封装成属性 11 protected Cache cache 12 { 13 get 14 { 15 return HttpRuntime.Cache; 16 } 17 } 18 #endregion 19 20 #region 2.利用构造函数 21 22 //public Cache cache2 { get; set; } 23 //public RuntimeCacheHelp() 24 //{ 25 // cache2 = HttpRuntime.Cache; 26 //} 27 #endregion 28 29 //二. 实现接口中的方法 30 31 #region 1.获取缓存的个数 32 public int Count 33 { 34 get { return cache.Count; } 35 } 36 #endregion 37 38 #region 2.删除特定键的缓存 39 /// <summary> 40 /// 删除特定键的缓存 41 /// </summary> 42 /// <param name="key">缓存的键名</param> 43 public void Remove(string key) 44 { 45 cache.Remove(key); 46 } 47 #endregion 48 49 #region 3.移除全部缓存 50 /// <summary> 51 /// 移除全部缓存 52 /// </summary> 53 public void RemoveAll() 54 { 55 IDictionaryEnumerator CacheEnum = cache.GetEnumerator(); 56 while (CacheEnum.MoveNext()) 57 { 58 cache.Remove(CacheEnum.Key.ToString()); 59 } 60 } 61 62 #endregion 63 64 #region 4.根据键值获取特定类型的缓存 65 /// <summary> 66 /// 根据键值获取特定类型的缓存 67 /// </summary> 68 /// <typeparam name="T">泛型T</typeparam> 69 /// <param name="key">键名</param> 70 /// <returns></returns> 71 public T Get<T>(string key) 72 { 73 //程序集中 this属性的应用 74 if (cache[key] != null) 75 { 76 return (T)cache[key]; 77 } 78 return default(T); 79 } 80 #endregion 81 82 #region 5.判断缓存是否存在 83 /// <summary> 84 /// 判断缓存是否存在 85 /// </summary> 86 /// <param name="key">键值</param> 87 /// <returns>true代表存在;false代表不存在</returns> 88 public bool Contains(string key) 89 { 90 return cache.Get(key) == null ? false : true; 91 } 92 93 #endregion 94 95 #region 6.获取缓存值的类型(子类创建) 96 /// <summary> 97 /// 获取缓存值的类型 98 /// </summary> 99 /// <param name="key">键名</param> 100 /// <returns>键名对应的缓存值的类型</returns> 101 public Type GetCacheType(string key) 102 { 103 return cache[key].GetType(); 104 } 105 #endregion 106 107 #region 7.增加或更新缓存(存在则更新,不存在则添加,采用绝对时间的模式) 108 /// <summary> 109 /// 增加或更新缓存 110 /// </summary> 111 /// <param name="key"></param> 112 /// <param name="value"></param> 113 /// <param name="cacheTime">绝对过期时间,默认为1天</param> 114 public void AddOrUpdate(string key, object value, int cacheTime = 1) 115 { 116 var absoluteTime = DateTime.Now + TimeSpan.FromDays(cacheTime); 117 cache.Insert(key, value, null, absoluteTime, Cache.NoSlidingExpiration); 118 } 119 120 #endregion 121 122 //三. 子类创建的新方法 123 124 125 #region 1.获取全部缓存(子类创建) 126 public IDictionaryEnumerator GetAllCache() 127 { 128 IDictionaryEnumerator cacheList = cache.GetEnumerator(); 129 //获取键值的方法 130 //while (cacheList.MoveNext()) 131 //{ 132 // var key = cacheList.Key.ToString(); 133 // var value = cacheList.Value; 134 //} 135 return cacheList; 136 } 137 #endregion 138 139 #region 2.Add模式增加缓存(子类创建) 140 /// <summary> 141 /// Add模式增加缓存(如果该缓存已经存在,再次添加会报错;Insert模式会覆盖上次) 142 /// </summary> 143 /// <param name="key">键</param> 144 /// <param name="value">值</param> 145 /// <param name="absoluteExpiration">绝对过期时间的参数:如:DateTime.Now.AddDays(1); DateTime.Parse("2016-5-28 20:32:00");</param> 146 /// <param name="slidingExpiration">相对过期时间的参数: 如:new TimeSpan(0,0, 0, 2) 天,小时,分钟,秒</param> 147 /// <param name="isAbsolute">true代表使用绝对过期时间,填写absoluteExpiration参数,slidingExpiration忽略不需要填写 148 /// false代表使用绝对过期时间,填写slidingExpiration 参数, absoluteExpiration忽略不需要填写 149 /// </param> 150 /// <param name="dependencies">缓存依赖:可以依赖文件、文件夹、数据库表</param> 151 /// <param name="priority">缓存销毁时的优先级,没有特别要求,使用该封装的默认即可</param> 152 /// <param name="onRemoveCallback">缓存失效后的回调:含有三个参数的委托,三个参数分别为(key,value,reason) 即(缓存的键、值、失效原因)</param> 153 public void AddCache(string key, object value, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool isAbsolute = true, CacheDependency dependencies = null, 154 CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback onRemoveCallback = null) 155 { 156 if (isAbsolute) 157 { 158 //绝对过期 159 cache.Add(key, value, dependencies, absoluteExpiration, Cache.NoSlidingExpiration, priority, onRemoveCallback); 160 } 161 else 162 { 163 //相对过期 164 cache.Add(key, value, dependencies, Cache.NoAbsoluteExpiration, slidingExpiration, priority, onRemoveCallback); 165 } 166 } 167 #endregion 168 169 #region 3.Insert模式增加缓存(子类创建) 170 /// <summary> 171 /// Insert模式增加缓存(如果该缓存已经存在,会覆盖上次,Add模式则是报错) 172 /// </summary> 173 /// <param name="key">键</param> 174 /// <param name="value">值</param> 175 /// <param name="absoluteExpiration">绝对过期时间的参数:如:DateTime.Now.AddDays(1); DateTime.Parse("2016-5-28 20:32:00");</param> 176 /// <param name="slidingExpiration">相对过期时间的参数: 如:new TimeSpan(0,0, 0, 2) 天,小时,分钟,秒</param> 177 /// <param name="isAbsolute">true代表使用绝对过期时间,填写absoluteExpiration参数,slidingExpiration忽略不需要填写 178 /// false代表使用绝对过期时间,填写slidingExpiration 参数, absoluteExpiration忽略不需要填写 179 /// </param> 180 /// <param name="dependencies">缓存依赖:可以依赖文件、文件夹、数据库表</param> 181 /// <param name="priority">缓存销毁时的优先级,没有特别要求,使用该封装的默认即可</param> 182 /// <param name="onRemoveCallback">缓存失效后的回调:含有三个参数的委托,三个参数分别为(key,value,reason) 即(缓存的键、值、失效原因)</param> 183 public void InsertCache(string key, object value, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool isAbsolute = true, CacheDependency dependencies = null, 184 CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback onRemoveCallback = null) 185 { 186 if (isAbsolute) 187 { 188 //绝对过期 189 cache.Insert(key, value, dependencies, absoluteExpiration, Cache.NoSlidingExpiration, priority, onRemoveCallback); 190 } 191 else 192 { 193 //相对过期 194 cache.Insert(key, value, dependencies, Cache.NoAbsoluteExpiration, slidingExpiration, priority, onRemoveCallback); 195 } 196 } 197 #endregion
3. 如何调用?调用过程中会存在哪些问题?我们再后续章节中进行介绍。