在之前一篇随笔《在.NET项目中使用PostSharp,实现AOP面向切面编程处理》介绍了PostSharp框架的使用,使用PostSharp能给我带来很多便利和优势,减少代码冗余,提高可读性,并且可以更加优雅的实现常规的日志、异常、缓存、事务等业务场景的处理。本篇主要介绍使用MemoryCache实现缓存的处理。

1、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辅助类的处理,用来方便实现缓存的数据操作。它的辅助类主要代码如下所示。

    /// <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>进行调用接口了,相当于需要对它们进行进一步的函数封装处理才能达到目的。

案例中可以设置失效缓存时间,并且失效后,自动通过Func<T> cachePopulate的函数重新获取缓存内容,在实际情况下,也是非常智能的一种处理方式。

2、结合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缓存管理类来实现缓存的处理,非常方便和高效了。

作者有几篇相关的文章,也顺便顶一下,确实不错:

在.NET项目中使用PostSharp,实现AOP面向切面编程处理

在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理

在.NET项目中使用PostSharp,使用CacheManager实现多种缓存框架的处理

在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理(转)的更多相关文章

  1. 在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理

    在之前一篇随笔<在.NET项目中使用PostSharp,实现AOP面向切面编程处理>介绍了PostSharp框架的使用,试用PostSharp能给我带来很多便利和优势,减少代码冗余,提高可 ...

  2. 在.NET项目中使用PostSharp,使用CacheManager实现多种缓存框架的处理

    在前面几篇随笔中,介绍了PostSharp的使用,以及整合MemoryCache,<在.NET项目中使用PostSharp,实现AOP面向切面编程处理>.<在.NET项目中使用Pos ...

  3. 在.NET项目中使用PostSharp,实现AOP面向切面编程处理

    PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一 ...

  4. .NET项目中使用PostSharp

    PostSharp是一种Aspect Oriented Programming 面向切面(或面向方面)的组件框架,适用在.NET开发中,本篇主要介绍Postsharp在.NET开发中的相关知识,以及一 ...

  5. 实战派 | Java项目中玩转Redis6.0客户端缓存!

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. 在前面的文章中,我们介绍了Redis6.0中的新特性客户端缓存client-side caching,通过tel ...

  6. spring3.0结合Redis在项目中的运用

    推荐一个程序员的论坛网站:http://ourcoders.com/home/ 以下内容使用到的技术有:Redis缓存.SpringMVC.Maven.项目中使用了redis缓存,目的是在业务场景中, ...

  7. redis缓存在项目中的使用

    关于redis为什么能作为缓存这个问题我们就不说了,直接来说一下redis缓存到底如何在项目中使用吧: 1.redis缓存如何在项目中配置? 1.1redis缓存单机版和集群版配置?(redis的客户 ...

  8. android 项目学习随笔十八(三级缓存)

    xUtils的BitmapUtils模块用的就是三级缓存,在项目中尽量还是应用BitmapUtils 三级缓存(机制) import com.itheima.zhsh66.R; import andr ...

  9. 采用EntLib5.0(Unity+Interception+Caching)实现项目中可用的Caching机制

    看了园子里很多介绍Caching的文章,多数都只介绍基本机制,对于Cache更新和依赖部分,更是只简单的实现ICacheItemRefreshAction接口,这在实际项目中是远远不够的.实际项目中, ...

随机推荐

  1. .NET DLL 保护措施详解(非混淆加密加壳)核心思路的实现

    最近有很多朋友通过BLOG找到我询问我的相关细节,其实相关的实现细节我早已把源码上传到51aspx上面了,地址是http://www.51aspx.com/code/codename/56949 也有 ...

  2. Java反射机制示例

    链接: http://www.cnblogs.com/rollenholt/archive/2011/09/02/2163758.html package com.stono.reftest; imp ...

  3. 游戏开发之UE4添加角色到场景中

    接着上次继续学习,现在我们已经有了一个场景并且运行了,我们需要添加一个角色到场景中.要这样做,我们必须从UE4的GameFramework类继承它. 一. 创建一个从Character类继承的类 从基 ...

  4. SDWebImage源码解读之分类

    第十一篇 前言 我们知道SDWebImageManager是用来管理图片下载的,但我们平时的开发更多的是使用UIImageView和UIButton这两个控件显示图片. 按照正常的想法,我们只需要在他 ...

  5. 制作一个功能丰富的Android天气App

    简易天气是一个基于和风天气数据采用MD设计的Android天气App.目前的版本采用传统的MVC模式构建.通过丰富多彩的页面为用户提供日常所需的天气资讯. 项目说明 项目放在github上面 地址是: ...

  6. spring-dwr注解整合

    注解配置 1.web.xml 只需将DwrServlet换为DwrSpringServlet(包名不同) 2.dwr类 3.applicationContext.xml 4.annotationCon ...

  7. MyBatis 源码分析——动态代理

    MyBatis框架是如何去执行SQL语句?相信不只是你们,笔者也想要知道是如何进行的.相信有上一章的引导大家都知道SqlSession接口的作用.当然默认情况下还是使用DefaultSqlSessio ...

  8. Picasso 修改缓存路径

    Picasso 是 Square 公司开源的一个非常友好的图片加载框架,使用范围也比较广泛.具体的使用这里就不做介绍了,文章主要讲讲如何修改图片的缓存路径.Picasso默认的缓存路径位于data/d ...

  9. SQL SERVER运维日记--收缩数据库

    一个小故事 某天,小王正在和HR妹妹闲聊,正HAPPY时,,突然收到系统告警消息,数据库磁盘被剩余空间500M,OMG,不行,磁盘快满了,要是业务要停了,,那就小王只能删库到跑路了,,, 先检查下,有 ...

  10. GitHub 上下载代码运行报错 :'The sandbox is not sync with the Podfile.lock\'

    问题描述: github下载的Demo,很多时候使用到CocoaPods,有的时候因为依赖关系或者版本问题不能编译运行.出现例如The sandbox is not sync with the Pod ...