asp.net SAF 中缓存服务的实现

复制代码 代码如下:

  protected void Page_Load(object sender, EventArgs e)

  {

  webinfo info = new webinfo();

  Response.Write("有static的执行结果:" + webinfo.a + "<br />");

  Response.Write("没有static的执行结果:" + info.b);

  }

  public class webinfo

  {

  public static DateTime a = DateTime.Now;

  public DateTime b = DateTime.Now;

  }

  下面内容多摘自该文章:结果是只要站点不重启(代码也不修改),那么a的值是恒定不变的,即使将页面关了重新打开也一样;而b则是刷新就变化。如果你从事Asp.Net的开发,提起缓存你可能首先会想到Output Cache、数据源缓存或者是基于System.Web.Caching.Cache的对象缓存。实际上缓存的目的就是把对象(数据)存储在内存中,不用每次需要对象服务的时候都重新创建对象(相对耗时)。将对象声明为static,那么对象将在其所属的类被载入AppDomain时初始化,这样对象的生命周期与AppDomain同样长,从而起到缓存的目的。

  设计思想

         我们经常需要在应用程序中缓存一些常用数据供全局使用以提升性能。如果需要缓存的对象类型和数目是固定的,我们可能会直接将其声明为static;如果我们需要缓存的对象类型和数目是不定的,我们可能会借助一个static Hashtable来实现。但是Hashtable有个缺陷:它没有层次结构,它总是以键/值的形式来存储数据,一个Key对应一个Value,如果我们想获取相关联的一组数据就会比较困难了。

  XML文档结构是树形的,具有标准的层次结构。XPath用于从Xml文档中选择一个或多个结点。比如 "/BookStore/Book",选择Book结点下的所有子结点。

  SAF 中的缓存服务通过一个在内存中动态构造的Xml文档树作为桥梁,将静态(static)缓存和XPath 这两个技术结合了起来,支持使用XPath的语法来获取Hashtable中对象。其中静态缓存进行实际的数据缓存,XPath用于获取数据对象。从程序员的角度来看,即是Hashtable的Key支持了XPath的语法,可以将原本“平板式”的Hashtable想象成为一个“树形结构”,它的结点包含了缓存的数据,我们通过标准的XPath到达结点(当然这只是一个假象)并获取数据。通过这种方式就可以使用XPath来一次获取Hashtable中的多个相关数据对象。 简单说,SAF缓存服务是为了实现一个有层次(树形)的缓存结构,从而实现对缓存更加灵活的操作。

  而实际上是怎么实现这一过程的呢?我们一步步来看:

  1、首先在内存中动态构建一个 Xml文档,它只包含一个根结点,可以任意命名,这里将它命名为了Cache。

  2、提供一个Xpath路径:获取对象(数据)前首先要存储对象,存对象自然要先提供一个路径(这里称为“路径”,是因为它是一个XPath,实际上也就相当于Hashtable中的键Key)。

  3、根据上一步提供的路径,以Cache为根结点,逐层深入地创建XmlNode结点。

  4、生成一个GUID,在叶结点上添加一个Key属性,为这个Key属性赋值为GUID。

  5、在Hashtable中存储对象,其中Hashtable的Key即为上一步生成的GUID,而Value为要存储的对象。

  使用这种方式,Hashtable的实际的Key,即动态生成的GUID对程序员来说是透明的,程序员在存储/获取对象时,只需要提供XPath表达式就可以。下面这幅图说明了它们之间的关系:

asp.net SAF 中缓存服务的实现

  这里还需要再说明三点:

  1、我们使用Hashtable存储对象,可以直接将Hashtable声明为static的,也可以将Hashtable声明为instance的,但是将Hashtable所属的对象声明为static的。这里应用了Singleton模式,先将对Hashtable的操作封装成一个类,然后在这个类上应用Singleton模式,确保了这个类只有一个(这个类所维护的Hashtable实例自然也只有一个了)。很明显,这个类包含了主要的逻辑,我们将之命名为Cache。

  2、使用Hashtable的好处是可以存储任何类型的对象,缺点是丧失了类型安全。有时候我们可能会想使用一个泛型集合类来取代Hashtable,比如Dictionary<T key, T value>。所以这里又引入了Strategy模式,创建了一个ICacheStrategy接口,这个接口包括三个方法,分别用于添加、获取、删除对象。

  3、用Xpath获取结点时,可以是基于当前结点的相对路径;也可以是基于根结点的绝对路径。在本文的范例程序中,使用的是绝对路径,显然这样更加方便一些。

  类型接口

  我们先看一下类型的组织,然后再看实现。

  ICacheStrategy用于定义如何添加、获取、删除欲进行缓存的对象。实际上,在接口的实体类中要明确使用何种类型来存储对象,是Dictionary还是Hashtable或者其他。

  C#

  

复制代码 代码如下:

  public interface ICacheStrategy {

  void AddItem(string key, object obj);// 添加对象

  object GetItem(string key);      // 获取对象

  void RemoveItem(string key); // 删除对象

  }

  接下来是Cache类,这个类包含了主要的逻辑,包括 动态构建的XML文档、将Xml文档映射到Hashtable 等。

  

复制代码 代码如下:

  public class Cache {

  void AddItem(string xpath, object obj);

  object GetItem(string xpath);

  object[] GetList(string xpath);

  void RemoveItem(string xpath);

  }

  仅从接口上看,这个类似乎和ICacheStrategy的没有太大分别,实际上,这个类保存了一个对于ICacheStrategy类型实例的引用,最后一步的实际工作,都委托给了ICacheStrategy去完成。而在此之前各个方法的工作主要是由 Xml结点到Hashtable的映射(这里说是Hashtable,是因为它是作者提供的一个默认实现,当然也可以是其他)。

  类型实现

  我们首先看DefaultCacheStrategy,它实现了ICacheStrategy接口,并使用Hashtable存储对象。

  

复制代码 代码如下:

  public class DefaultCacheStrategy : ICacheStrategy {

  private Hashtable objectStore;

  public DefaultCacheStrategy() {

  objectStore = new Hashtable();

  }

  public void AddItem(string key, object obj) {

  objectStore.Add(key, obj);

  }

  public object GetItem(string key) {

  return objectStore[key];

  }

  public void RemoveItem(string key) {

  objectStore.Remove(key);

  }

  }

  接下来我们一步步地看Cache类的实现,下面是Cache类的字段以及构造函数(注意为私有)。 Java复制代码

  

复制代码 代码如下:

  public class Cache {

  private XmlElement rootMap;             // 动态构建的 Xml文档 的根结点

  private ICacheStrategy cacheStrategy;   // 保存对ICacheStrategy的引用

  public static readonly Cache Instance = new Cache();  // 实现Singleton模式

  private XmlDocument doc = new XmlDocument();   // 构建 Xml文档

  // 私有构造函数,用来实现Singleton模式

  private Cache() {

  // 这里应用了Strategy模式。

  // 改进:可以将使用何种Strategy定义到app.config中,然后使用反射来动态创建类型

  cacheStrategy = new DefaultCacheStrategy();

  // 创建文档根结点,用于映射 实际的数据存储(例如Hashtable) 和 Xml文档

  rootMap = doc.CreateElement("Cache");

  // 添加根结点

  doc.AppendChild(rootMap);

  }

  // 略...

  }

  Cache类还包含两个私有方法。PreparePath()用于对输入的Xpath进行格式化,使其以构造函数中创建的根节点("Cache")作为根结点(这样做是可以使你在添加/获取对象时免去写根结点的麻烦);CreateNode() 用于根据XPath逐层深入地创建Xml结点。 C#复制代码

  // 根据 XPath 创建一个结点

  private XmlNode CreateNode(string xpath) {

  string[] xpathArray = xpath.Split('/');

  string nodePath = "";

  // 父节点初始化

  XmlNode parentNode = (XmlNode)rootMap;

  // 逐层深入 XPath 各层级,如果结点不存在则创建

  // 比如 /DvdStore/Dvd/NoOneLivesForever

  for (int i = 1; i < xpathArray.Length; i++) {

  XmlNode node = rootMap.SelectSingleNode(nodePath + "/" + xpathArray[i]);

  if (node == null) {

  XmlElement newElement = rootMap.OwnerDocument.CreateElement(xpathArray[i]);   // 创建结点

  parentNode.AppendChild(newElement);

  }

  // 创建新路径,更新父节点,进入下一级

  nodePath = nodePath + "/" + xpathArray[i];

  parentNode = rootMap.SelectSingleNode(nodePath);

  }

  return parentNode;

  }

  // 构建 XPath,使其以 /Cache 为根结点,并清除多于的"/"字符

  private string PrepareXPath(string xpath) {

  string[] xpathArray = xpath.Split('/');

  xpath = "/Cache";     // 这里的名称需与构造函数中创建的根结点名称对应

  foreach (string s in xpathArray) {

  if (s != "") {

  xpath += "/" + s;

  }

  }

  return xpath;

  }

  AddItem()方法用于向缓存中添加对象,包括了下面几个步骤:

  1、根据输入的XPath判断到达 叶结点 的路径是否已经存在,如果不存在,调用上面的CreateNode()方法,逐层创建结点。

  2、生成GUID,在组结点下创建 XmlNode 叶结点,为叶结点添加属性Key,并将值设为GUID。

  3、将对象保存至实际的位置,默认实现是一个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(string xpath, object obj) {

  // 获取 Xpath,例如 /Cache/BookStore/Book/Title

  string newXpath = PrepareXPath(xpath);

  int separator = newXpath.LastIndexOf("/");

  // 获取组结点的层叠顺序 ,例如 /Cache/BookStore/Book

  string group = newXpath.Substring(0, separator);

  // 获取叶结点名称,例如 Title

  string element = newXpath.Substring(separator + 1);

  // 获取组结点

  XmlNode groupNode = rootMap.SelectSingleNode(group);

  // 如果组结点不存在,创建之

  if (groupNode == null) {

  lock (this) {

  groupNode = CreateNode(group);

  }

  }

  // 创建一个唯一的 key ,用来映射 Xml 和对象的主键

  string key = Guid.NewGuid().ToString();

  // 创建一个新结点

  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()则用于从缓存中删除对象,它也包含了两个步骤:1、先从Xml文档树中删除结点;2、再从实际的存储位置(Hashtable)中删除对象。这里需要注意的是:如果XPath指定的是一个叶结点,那么直接删除该结点;如果XPath指定的是组结点,那么需要删除组结点下的所有结点。代码如下:

  C#复制代码

  // 根据 XPath 删除对象

  public virtual void RemoveItem(string xpath) {

  xpath = PrepareXPath(xpath);

  XmlNode result = rootMap.SelectSingleNode(xpath);

  string key;           // 对象的Id

  // 如果 result 是一个组结点(含有子结点)

  if (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);

  }

  } else {      // 如果 result 是一个叶结点(不含子结点)

  key = result.Attributes["key"].Value;

  result.ParentNode.RemoveChild(result);

  cacheStrategy.RemoveItem(key);

  }

  }

  最后的两个方法,GetItem()和GetList()分别用于从缓存中获取单个或者多个对象。值得注意的是当使用GetList()方法时,Xpath应该为到达一个组结点的路径。

  

复制代码 代码如下:

  // 根据 XPath 获取对象

  // 先根据Xpath获得对象的Key,然后再根据Key获取实际对象

  public virtual object GetItem(string xpath) {

  object obj = null;

  xpath = PrepareXPath(xpath);

  XmlNode node = rootMap.SelectSingleNode(xpath);

  if (node != null) {

  // 获取对象的Key

  string key = node.Attributes["key"].Value;

  // 获取实际对象

  obj = cacheStrategy.GetItem(key);

  }

  return obj;

  }

  // 获取一组对象,此时xpath为一个组结点

  public virtual object[] GetList(string xpath) {

  xpath = PrepareXPath(xpath);

  XmlNode group = rootMap.SelectSingleNode(xpath);

  // 获取该结点下的所有子结点(使用[@key]确保子结点一定包含key属性)

  XmlNodeList results = group.SelectNodes(xpath + "/*[@key]");

  ArrayList objects = new ArrayList();

  string key;

  foreach (XmlNode result in results) {

  key = result.Attributes["key"].Value;

  Object obj = cacheStrategy.GetItem(key);

  objects.Add(obj);

  }

  return (object[])objects.ToArray(typeof(object));

  }

  至此,SAF 的缓存服务的设计和代码实现都完成了,现在我们来看看如何使用它。

  程序测试

  

复制代码 代码如下:

  static void Main(string[] args) {

  CacheService.Cache cache = CacheService.Cache.Instance;

  // 添加对象到缓存中

  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.ToString());

  }

  // 删除所有WebApplication下所有子孙结点

  cache.RemoveItem("/WebApplication");

  // 获取单个对象

  string time = (string)cache.GetItem("/WebApplication/GlobalData");

  string name = (string)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.ToString());

  }

  Console.ReadLine();

  }

  输出的结果为:

  Customer in cache: customer xin

  Customer in cache: customer jimmy

  Customer in cache: customer other

  Time:

  User:

  Object: v10120080401

  Object: Trace

  全部代码:

  ICacheStrategy.cs

  

复制代码 代码如下:

  using System;

  using System.Collections.Generic;

  using System.Text;

  using System.Collections;

  namespace CacheService {

  // 定义如何添加、获取、删除欲进行缓存的对象

  public interface ICacheStrategy {

  // 添加对象

  void AddItem(string key, object obj);

  // 获取对象

  object GetItem(string key);

  // 删除对象

  void RemoveItem(string key);

  }

  public class DefaultCacheStrategy : ICacheStrategy {

  private Hashtable objectStore;

  public DefaultCacheStrategy() {

  objectStore = new Hashtable();

  }

  public void AddItem(string key, object obj) {

  objectStore.Add(key, obj);

  }

  public object GetItem(string key) {

  return objectStore[key];

  }

  public void RemoveItem(string key) {

  objectStore.Remove(key);

  }

  }

  }

  Cache.cs

  

复制代码 代码如下:

  using System;

  using System.Collections.Generic;

  using System.Text;

  using System.Xml;

  using System.Collections;

  namespace CacheService {

  // 使用树形结构来存储对象,有别于Hashtable的平板式结构

  // 通过 XPath 来进行对象获取

  public class Cache {

  private XmlElement rootMap;             //  动态构建的 Xml文档 的根结点

  private ICacheStrategy cacheStrategy;

  public static readonly Cache Instance = new Cache();    // 实现Singleton模式

  private XmlDocument doc = new XmlDocument();    // 构建 Xml文档

  // 私有构造函数,用来实现Singleton模式

  private Cache() {

  // 这里应用了Strategy模式。

  // 改进:可以将使用何种Strategy定义到app.config中,然后使用反射来动态创建类型

  cacheStrategy = new DefaultCacheStrategy();

  // 创建文档根结点,用于映射 实际的数据存储(例如Hashtable) 和 Xml文档

  rootMap = doc.CreateElement("Cache");

  // 添加根结点

  doc.AppendChild(rootMap);

  }

  // 根据 XPath 获取对象

  // 先根据Xpath获得对象的Key,然后再根据Key获取实际对象

  public virtual object GetItem(string xpath) {

  object obj = null;

  xpath = PrepareXPath(xpath);

  XmlNode node = rootMap.SelectSingleNode(xpath);

  if (node != null) {

  // 获取对象的Key

  string key = node.Attributes["key"].Value;

  // 获取实际对象

  obj = cacheStrategy.GetItem(key);

  }

  return obj;

  }

  // 获取一组对象,此时xpath为一个组结点

  public virtual object[] GetList(string xpath) {

  xpath = PrepareXPath(xpath);

  XmlNode group = rootMap.SelectSingleNode(xpath);

  // 获取该结点下的所有子结点(使用[@key]确保子结点一定包含key属性)

  XmlNodeList results = group.SelectNodes(xpath + "/*[@key]");

  ArrayList objects = new ArrayList();

  string key;

  foreach (XmlNode result in results) {

  key = result.Attributes["key"].Value;

  Object obj = cacheStrategy.GetItem(key);

  objects.Add(obj);

  }

  return (object[])objects.ToArray(typeof(object));

  }

  // 添加对象,对象实际上还是添加到ICacheStrategy指定的存储位置,

  // 动态创建的 Xml 结点仅保存了对象的Id(key),用于映射两者间的关系

  public virtual void AddItem(string xpath, object obj) {

  // 获取 Xpath,例如 /Cache/BookStore/Book/Title

  string newXpath = PrepareXPath(xpath);

  int separator = newXpath.LastIndexOf("/");

  // 获取组结点的层叠顺序 ,例如 /Cache/BookStore/Book

  string group = newXpath.Substring(0, separator);

  // 获取叶结点名称,例如 Title

  string element = newXpath.Substring(separator + 1);

  // 获取组结点

  XmlNode groupNode = rootMap.SelectSingleNode(group);

  // 如果组结点不存在,创建之

  if (groupNode == null) {

  lock (this) {

  groupNode = CreateNode(group);

  }

  }

  // 创建一个唯一的 key ,用来映射 Xml 和对象的主键

  string key = Guid.NewGuid().ToString();

  // 创建一个新结点

  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);

  }

  // 根据 XPath 删除对象

  public virtual void RemoveItem(string xpath) {

  xpath = PrepareXPath(xpath);

  XmlNode result = rootMap.SelectSingleNode(xpath);

  string key;         // 对象的Id

  // 如果 result 是一个组结点(含有子结点)

  if (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);

  }

  } else {        // 如果 result 是一个叶结点(不含子结点)

  key = result.Attributes["key"].Value;

  result.ParentNode.RemoveChild(result);

  cacheStrategy.RemoveItem(key);

  }

  }

  // 根据 XPath 创建一个结点

  private XmlNode CreateNode(string xpath) {

  string[] xpathArray = xpath.Split('/');

  string nodePath = "";

  // 父节点初始化

  XmlNode parentNode = (XmlNode)rootMap;

  // 逐层深入 XPath 各层级,如果结点不存在则创建

  // 比如 /DvdStore/Dvd/NoOneLivesForever

  for (int i = 1; i < xpathArray.Length; i++) {

  XmlNode node = rootMap.SelectSingleNode(nodePath + "/" + xpathArray[i]);

  if (node == null) {

  XmlElement newElement = rootMap.OwnerDocument.CreateElement(xpathArray[i]); // 创建结点

  parentNode.AppendChild(newElement);

  }

  // 创建新路径,更新父节点,进入下一级

  nodePath = nodePath + "/" + xpathArray[i];

  parentNode = rootMap.SelectSingleNode(nodePath);

  }

  return parentNode;

  }

  // 构建 XPath,使其以 /Cache 为根结点,并清除多于的"/"字符

  private string PrepareXPath(string xpath) {

  string[] xpathArray = xpath.Split('/');

  xpath = "/Cache";       // 这里的名称需与构造函数中创建的根结点名称对应

  foreach (string s in xpathArray) {

  if (s != "") {

  xpath += "/" + s;

  }

  }

  return xpath;

  }

  }

  }