本文举例源代码或素材下载
设计思想
我们经常需要在应用
中缓存Cache
些常用数据供全局使用以提升性能
如果需要缓存Cache
对象类型和数目是固定
我们可能会直接将其声明为
;如果我们需要缓存Cache
对象类型和数目是不定
我们可能会借助
个
Hashtable来实现
但是Hashtable有个缺陷:它没有层次结构
它总是以键/值
形式来存储数据
个Key对应
个Value
如果我们想获取相关联
组数据就会比较困难了
NOTE:如果你从事Asp.Net
开发
提起缓存Cache你可能首先会想到Output Cache、数据源缓存Cache或者是基于
.Web.Caching.Cache
对象缓存Cache
实际上缓存Cache
目
就是把对象(数据)存储在内存中
不用每次需要对象服务
时候都重新创建对象(相对耗时)
将对象声明为
那么对象将在其所属
类被载入AppDo
时
化
这样对象
生命周期和AppDo
同样长
从而起到缓存Cache
目
感兴趣
朋友可以做个测试:在站点下新建
个Default.aspx文件
在后置代码中添加如下代码:
public Test {
public DateTime a = DateTime.Now;
public DateTime b = DateTime.Now;
}
protected void Page_Load(object sender, EventArgs e) {
Test t = Test;
Label1.Text = Test.a. + "<br />"; //Label1为页面上个LabelControl控件
Label1.Text t.b.;
}
结果是只要站点不重启(代码也不修改)
那么a
值是恒定不变
即使将页面关了重新打开也
样
可见a只在Test类加载到AppDo
中进行了
次
化
而b在每次刷新时都会改变
每次请求页面都会在创建Test类型例子时重新对a进行
化
NOTE:声明为静态(
)
个特例是声明为const
这是
const天生就是
但它
局限性是对象
类型必须为诸如
或者
简单类型
除此以外
声明为const
对象将不再是变量
而是
个常量
例如const a = "abc"; 相当于给
类型
串"abc"起了个别名叫a
因此const必须在声明时就赋值
XML文档结构是树形
具有标准
层次结构
XPath用于从Xml文档中选择
个或多个结点
比如 "/BookStore/Book"
选择Book结点下
所有子结点
SAF 中
缓存Cache服务通过
个在内存中动态构造
Xml文档树作为桥梁
将 静态(
)缓存Cache 和 XPath 这两个技术结合了起来
支持使用XPath
语法来获取Hashtable中对象
其中静态缓存Cache进行实际
数据缓存Cache
XPath用于获取数据对象
从
员
角度来看
即是Hashtable
Key支持了XPath
语法
可以将原本“平板式”
Hashtable想象成为
个“树形结构”
它
结点包含了缓存Cache
数据
我们通过标准
XPath到达结点(当然这只是
个假象)并获取数据
通过这种方式就可以使用XPath来
次获取Hashtable中
多个相关数据对象
而实际上是如何实现这
过程
呢?我们
步步来看:
首先在内存中动态构建
个 Xml文档
它只包含
个根结点
可以任意命名
这里将它命名为了Cache
提供
个Xpath路径:获取对象(数据)前首先要存储对象
存对象自然要先提供
个路径(这里称为“路径”
是
它是
个XPath
实际上也就相当于Hashtable中
键Key)
根据上
步提供
路径
以Cache为根结点
逐层深入地创建XmlNode结点
生成
个GUID
在叶结点上添加
个Key属性
为这个Key属性赋值为GUID
在Hashtable中存储对象
其中Hashtable
Key即为上
步生成
GUID
而Value为要存储
对象
使用这种方式
Hashtable
实际
Key
即动态生成
GUID对
员来说是透明
员在存储/获取对象时
只需要提供XPath表达式就可以
下面这幅图介绍说明了它们的间
关系:
这里还需要再介绍说明 3点:
我们使用Hashtable存储对象
可以直接将Hashtable声明为
也可以将Hashtable声明为instance
但是将Hashtable所属
对象声明为
这里应用了Singleton模式
先将对Hashtable
操作封装成
个类
然后在这个类上应用Singleton模式
确保了这个类只有
个(这个类所维护
Hashtable例子自然也只有
个了)
很明显
这个类包含了主要
逻辑
我们将的命名为Cache
使用Hashtable
好处是可以存储任何类型
对象
缺点是丧失了类型安全
有时候我们可能会想使用
个泛型集合类来取代Hashtable
比如Dictionary<T key, T value>
所以这里又引入了Strategy模式
创建了
个ICacheStrategy接口
这个接口包括 3个思路方法
分别用于添加、获取、删除对象
用Xpath获取结点时
可以是基于当前结点
相对路径;也可以是基于根结点
绝对路径
在本文
范例
中
使用
是绝对路径
显然这样更加方便
些
类型接口
我们先看
下类型
组织
然后再看实现
ICacheStrategy用于定义如何添加、获取、删除欲进行缓存Cache
对象
实际上
在接口
实体类中要明确使用何种类型来存储对象
是Dictionary还是Hashtable或者其他
public erface ICacheStrategy {
void AddItem( key, object obj);// 添加对象
object GetItem( key); // 获取对象
void RemoveItem( key); // 删除对象
}
接下来是Cache类
这个类包含了主要
逻辑
包括 动态构建
XML文档、将Xml文档映射到Hashtable 等
public Cache {
void AddItem( xpath, object obj);
object GetItem( xpath);
object GetList( xpath);
void RemoveItem( xpath);
}
仅从接口上看
这个类似乎和ICacheStrategy
没有太大分别
实际上
这个类保存了
个对于ICacheStrategy类型例子
引用
最后
步
实际工作
都委托给了ICacheStrategy去完成
而在此的前各个思路方法
工作主要是由 Xml结点到Hashtable
映射(这里说是Hashtable
是
它是作者提供
个默认实现
当然也可以是其他)
类型实现
我们首先看DefaultCacheStrategy
它实现了ICacheStrategy接口
并使用Hashtable存储对象
public DefaultCacheStrategy : ICacheStrategy {
private Hashtable objectStore;
public DefaultCacheStrategy {
objectStore = Hashtable;
}
public void AddItem( key, object obj) {
objectStore.Add(key, obj);
}
public object GetItem( key) {
objectStore[key];
}
public void RemoveItem( key) {
objectStore.Remove(key);
}
}
接下来我们
步步地看Cache类
实现
下面是Cache类
字段以及构造
(注意为私有)
public Cache {
private XmlElement rootMap; // 动态构建 Xml文档 根结点
private ICacheStrategy cacheStrategy; // 保存对ICacheStrategy引用
public readonly Cache Instance = Cache; // 实现Singleton模式
private XmlDocument doc = XmlDocument; // 构建 Xml文档
// 私有构造用来实现Singleton模式
private Cache {
// 这里应用了Strategy模式
// 改进:可以将使用何种Strategy定义到app.config中然后使用反射来动态创建类型
cacheStrategy = DefaultCacheStrategy;
// 创建文档根结点用于映射 实际数据存储(例如Hashtable) 和 Xml文档
rootMap = doc.CreateElement("Cache");
// 添加根结点
doc.AppendChild(rootMap);
}
// 略...
}
Cache类还包含两个私有思路方法
PreparePath
用于对输入
Xpath进行格式化
使其以构造
中创建
根节点("Cache")作为根结点(这样做是可以使你在添加/获取对象时免去写根结点
麻烦);CreateNode
用于根据XPath逐层深入地创建Xml结点
// 根据 XPath 创建个结点
private XmlNode CreateNode( xpath) {
xpathArray = xpath.Split('/');
nodePath = "";
// 父节点化
XmlNode parentNode = (XmlNode)rootMap;
// 逐层深入 XPath 各层级如果结点不存在则创建
// 比如 /DvdStore/Dvd/NoOneLivesForever
for ( i = 1; i < xpathArray.Length; i) {
XmlNode node = rootMap.SelectSingleNode(nodePath + "/" + xpathArray[i]);
(node null) {
XmlElement Element = rootMap.OwnerDocument.CreateElement(xpathArray[i]); // 创建结点
parentNode.AppendChild(Element);
}
// 创建新路径更新父节点进入下级
nodePath = nodePath + "/" + xpathArray[i];
parentNode = rootMap.SelectSingleNode(nodePath);
}
parentNode;
}
// 构建 XPath使其以 /Cache 为根结点并清除多于"/"
private PrepareXPath( xpath) {
xpathArray = xpath.Split('/');
xpath = "/Cache"; // 这里名称需和构造中创建根结点名称对应
foreach ( s in xpathArray) {
(s != "") {
xpath "/" + s;
}
}
xpath;
}
AddItem
思路方法用于向缓存Cache中添加对象
包括了下面几个步骤:
根据输入
XPath判断到达 叶结点
路径是否已经存在
如果不存在
上面
CreateNode
思路方法
逐层创建结点
生成GUID
在组结点下创建 XmlNode 叶结点
为叶结点添加属性Key
并将值设为GUID
将对象保存至实际
位置
默认实现是
个Hashtable
通过
ICacheStrategy.AddItem
思路方法来完成
并将Hashtable
Key设置为GUID
NOTE: 为了介绍说明方便
这里有
个我对
类结点
命名--“组结点”
假设有XPath路径:/Cache/BookStore/Book/Title
那么/Cache/BookStore/Book即为“组结点”
称其为“组结点”
是
其下可包含多个叶结点
比如 /Cache/BookStore/Book/Author 包含了叶结点 Author;而/Cache/BookStore/Book/Title 中
Title为叶结点
GUID存储在叶结点
属性中
需要注意 组结点 和 叶结点是相对
对于路径 /Cache/BookStore/Book 来说
它
组结点就是“/Cache/BookStore”
而 Book是它
叶结点
下面是AddItem
思路方法
完整代码:
// 添加对象对象实际上还是添加到ICacheStrategy指定存储位置
// 动态创建 Xml 结点仅保存了对象Id(key)用于映射两者间关系
public virtual void AddItem( xpath, object obj) {
// 获取 Xpath例如 /Cache/BookStore/Book/Title
Xpath = PrepareXPath(xpath);
separator = Xpath.LastIndexOf("/");
// 获取组结点层叠顺序 例如 /Cache/BookStore/Book
group = Xpath.Sub(0, separator);
// 获取叶结点名称例如 Title
element = Xpath.Sub(separator + 1);
// 获取组结点
XmlNode groupNode = rootMap.SelectSingleNode(group);
// 如果组结点不存在创建的
(groupNode null) {
lock (this) {
groupNode = CreateNode(group);
}
}
// 创建个唯 key 用来映射 Xml 和对象主键
key = Guid.NewGuid.;
// 创建个新结点
XmlElement objectElement = rootMap.OwnerDocument.CreateElement(element);
// 创建结点属性 key
XmlAttribute objectAttribute = rootMap.OwnerDocument.CreateAttribute("key");
// 设置属性值为 刚才生成 Guid
objectAttribute.Value = key;
// 将属性添加到结点
objectElement.Attributes.Append(objectAttribute);
// 将结点添加到 groupNode 下面(groupNode为Xpath层次部分)
groupNode.AppendChild(objectElement);
// 将 key 和 对象添加到实际存储位置比如Hashtable
cacheStrategy.AddItem(key, obj);
}
RemoveItem
则用于从缓存Cache中删除对象
它也包含了两个步骤:1、先从Xml文档树中删除结点;2、再从实际
存储位置(Hashtable)中删除对象
这里需要注意
是:如果XPath指定
是
个叶结点
那么直接删除该结点;如果XPath指定
是组结点
那么需要删除组结点下
所有结点
代码如下:
// 根据 XPath 删除对象
public virtual void RemoveItem( xpath) {
xpath = PrepareXPath(xpath);
XmlNode result = rootMap.SelectSingleNode(xpath);
key; // 对象Id
// 如果 result 是个组结点(含有子结点)
(result.HasChildNodes) {
// 选择所有包含有key属性结点
XmlNodeList nodeList = result.SelectNodes("descendant::*[@key]");
foreach (XmlNode node in nodeList) {
key = node.Attributes["key"].Value;
// 从 Xml 文档中删除结点
node.ParentNode.RemoveChild(node);
// 从实际存储中删除结点
cacheStrategy.RemoveItem(key);
}
} { // 如果 result 是个叶结点(不含子结点)
key = result.Attributes["key"].Value;
result.ParentNode.RemoveChild(result);
cacheStrategy.RemoveItem(key);
}
}
最后
两个思路方法
GetItem
和GetList
分别用于从缓存Cache中获取单个或者多个对象
值得注意
是当使用GetList
思路方法时
Xpath应该为到达
个组结点
路径
// 根据 XPath 获取对象
// 先根据Xpath获得对象Key然后再根据Key获取实际对象
public virtual object GetItem( xpath) {
object obj = null;
xpath = PrepareXPath(xpath);
XmlNode node = rootMap.SelectSingleNode(xpath);
(node != null) {
// 获取对象Key
key = node.Attributes["key"].Value;
// 获取实际对象
obj = cacheStrategy.GetItem(key);
}
obj;
}
// 获取组对象此时xpath为个组结点
public virtual object GetList( xpath) {
xpath = PrepareXPath(xpath);
XmlNode group = rootMap.SelectSingleNode(xpath);
// 获取该结点下所有子结点(使用[@key]确保子结点定包含key属性)
XmlNodeList results = group.SelectNodes(xpath + "/*[@key]");
ArrayList objects = ArrayList;
key;
foreach (XmlNode result in results) {
key = result.Attributes["key"].Value;
Object obj = cacheStrategy.GetItem(key);
objects.Add(obj);
}
(object)objects.ToArray(typeof(object));
}
至此
SAF
缓存Cache服务
设计和代码实现都完成了
现在我们来看看如何使用它
测试
void Main( args) {
CacheService.Cache cache = CacheService.Cache.Instance;
// 添加对象到缓存Cache中
cache.AddItem("/WebApplication/Users/Xin", "customer xin");
cache.AddItem("/WebApplication/Users/Jimmy", "customer jimmy");
cache.AddItem("/WebApplication/Users/Steve", "customer other");
cache.AddItem("/WebApplication/GlobalData", "1/1/2008");
cache.AddItem("/Version", "v10120080401");
cache.AddItem("/Site", "TraceFact.Net");
// 获取所有User
object objects = cache.GetList("/WebApplication/Users");
foreach (object obj in objects) {
Console.WriteLine("Customer in cache: {0}", obj.);
}
// 删除所有WebApplication下所有子孙结点
cache.RemoveItem("/WebApplication");
// 获取单个对象
time = ()cache.GetItem("/WebApplication/GlobalData");
name = ()cache.GetItem("/WebApplication/Users/Xin");
Console.WriteLine("Time: {0}", time);// 输出为空WebApplication下所有结点已删除
Console.WriteLine("User: {0}", name);// 输出为空, WebApplication下所有结点已删除
// 获取根目录下所有叶结点
objects = cache.GetList("/");
foreach (object obj in objects) {
Console.WriteLine("Object: {0}", obj.);
}
Console.ReadLine;
}
输出
结果为:
Customer in cache: customer xin
Customer in cache: customer jimmy
Customer in cache: customer other
Time:
User:
Object: v10120080401
Object: Trace