Orchard 官方网站:http://www.orchardproject.net/
Orchard 中文网站:http://www.orchardch.com/
官网上并没有找到代码的具体说明,中文网站上的资源也不多。主要参考了以下网络文档:
http://www.cnblogs.com/ants/p/3736538.html
http://www.cnblogs.com/n-pei/archive/2011/05/01/2033911.html
废话不多说,直接开始。
首先,我脑中先勾勒出一个标准的缓存管理模块:
1、使用 ConcurrentDictionary 这个线程安全的字典存储缓存的 Key 和 Result,并用 Static 包装,单例提供;
2、CacheManager :缓存管理的接口模块,提供 Add、Remove、Update 方法,分别对应缓存的增加、删除和更新;
我曾经很多次使用这种简单的缓存管理方式,使用过程中发现一些问题:
1、Key 是唯一的,所以我在添加缓存的时候要小心维护,如果 Key 重复了则无法插入缓存字典;
2、我必须自己维护数据的失效,如果一个人开发还好,如果多人开发的话,除了编写很完善的文档,没有什么好的办法;
3、操作起来很麻烦,要写很多代码........... 数据的更改要Update,新增要 Add,Remove倒是很少用,但经常忘记写.......毕竟和很多逻辑裹挟在一起......
接下来看一下 Orchard 的实现方式,如果能解决我的问题最好..........
我看代码的习惯是从用法开始,正好我下载的 Orchard v1.8 有非常不错的 Test,于是从这儿开始:
Orchard.Framework.Tests \ Caching \ CacheTests.cs
首先看这段代码,这段代码向缓存管理器(_cacheManager)中增加了一条缓存记录。
如果F12查看 ICacheManager 的话,可以发现缓存管理中只用到了两个方法:
Get 方法返回一个缓存结果, GetCache 返回一个缓存存储器。而且,进一步查询 DefaultCacheManager(实现 ICacheManager 接口)类,发现 Get 方法调用了 GetCache 方法,并继续调用 GetCache 方法返回的 ICache 对象的 Get 方法,获取缓存。
继续挖,下面是 Cache类的 Get 方法:
_entries 是一个线程安全的字典,这里使用了它的一个方法,AddOrUpdate,这就解决了缓存的添加和更新。
这里有个让我头疼的参数 Func<AcquireContext<TKey>,TResult> acquire 这个参数从 DefaultCacheManager.Get 一直传递到这儿,还没有发生作用,到底干嘛的?
继续挖, Cache.AddEntry 和 Cache.UpdateEntry,再挖到 CreateEntry ,终于发现了acquire 开始使用了。
acquire是一个匿名委托,常表现为 Lamda表达式,输入 AcquireContext<TKey>作为参数,输出 TResult。在这个测试中, 对应的参数是 ctx=>"testResult",显然,这只是传入一个字符串作为 TResult。
使用委托是一个高明的办法,但是我对委托的掌握不是很好,所以............ 再看看有多少种用法。
回到 DefaultCacheManager ,对 Get 方法 Find Usages:
Sample-1:
这里的 Lamda 表达式使用了 AcquireContext .Monitor 属性(又是一个委托),一路追踪代码。上一张图中的CreateEntry 中创建了一个新的名为 context 的 AcquireContext,并将 Monitor 属性赋值为 Entry.AddToken ,将context 带入到这个 Lamba,就将 _signal.When(CacheSettingsPort.CacheKey) 返回的 Token(实现IVolatileToken)通过 Entry.AddToken 委托,添加到了 entry .Tokens (以为维护 Token 的列表)。然后执行下面的 return ......
呃......... 确实和备注说的一样,这样的话,第一运行上面代码的时候,会从 return ....... 后面的语句获取 TResult,并存入缓存。第二次开始就直接从缓存读取了。很简单,很聪明,但是很........纠结。
Sample-2:极端的用法
这里用到了Ruby脚本......... 强大得不忍直视..........
至此,我所担心的第3个问题迎刃而解, Orchard 的缓存机制非常强大、非常灵活,编写的代码量也非常小 ........但是,在编写这些代码之前需要考虑的时间似乎会更长.....习惯了就好。
到现在为止,我们追踪了缓存创建、更新的全步骤,再回头看看这些缓存是如何组织的。
回到 DefaultCacheManager,这个类的构造函数需要一个 Component 类型作为参数,干嘛要 Component 的类型呢?
上文提到了 DefaultCacheHolder 这个单例类,我们知道所有的 Cache 都是存储在这个类里的,翻翻它的 Key ,一切明朗起来了:
原来,DefaultCacheHolder 中的 Key 是一个三元组,由 Component类型、Key类型、Result类型组成。这就解释了 DefaultCacheManager 为什么要用 Component 类型来初始化。使用这个三元组的 Key 的好处是,可以为每个 Component 创建对应 Cache,而且用 Key 类型和 Result 类型进行区分,这样就防止了 Key 重复的情况 ...............
虽然不能说聪明,但是很实用,我担心的第1个问题也解决了。
现在源代码里还有一个 DefaultCacheContextAccessor 还没有分析,首先看到的是 [ThreadStatic],这个类的字段 _threadInstance 在每个线程唯一,不共享,还是静态的......。然后看看使用方法,虽然在 DefaultCacheHolder 中作为构造参数传入,但是真正发挥作用是在 Cache 类中 ,作为 Cahe类的构造参数传入,并在 Cache 类中标识当前操作的 IAcquireContext。
我的理解是 DefaultCacheHolder 维护着一个字典,Key上面说了是个三元组,Value就是Cache,而且是单例的。
在访问 DefaultCacheHolder 的Cache的时候,每个Cache对应一个线程和该线程上的 DefaultCacheContextAccessor访问器,所有对 Cache 的请求都通过这个访问器排队提交。这样就提升了缓存管理的性能。---- 这个理解不知道对不对,还需要跑跑代码看看。
最后,要找找缓存失效的机制。我找到了 Signals。它有一个Tigger方法,可以维护Token.IsCurrent = false,再配合看看 Cache.UpdateEntry方法,基本上可以断定 Signals.Trgger方法的作用就是使缓存失效。
现在大部分的问题都解决了,可以总结一下了:
Orchard 的缓存模块确实比较强大,不仅可以缓存常规的数据,还可以缓存委托、脚本、代码片段......这实际上已经超出了常见缓存模块的功能。
当然,强大的同时,也带来了结构复杂的问题。在这里说明一下,Orchard 的 Caching 模块与其它模块耦合程度很低,可以直接拉出来放到你的项目里使用。
很久以来一直在微软的平台上工作,看过很多微软的代码,但是觉得最近一些年,微软的代码越来越难懂了......... 是自己老了?还是微软的编码风格变华丽了?