在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。在Microsoft提供的经典示例项 目.Net PetShop 4.0中,也提供了对缓存的支持,本文是作者在学习此项目时的一些心得体会,有一些地方还不十分清楚,希望能够抛砖引玉。

    在.Net PetShop 4.0中,非常成功地使用了工厂模式以及接口(interface)、静态类(Static class)、抽象类(abstract class)等成员。在使用缓存时,也是通过web.config配置进行设置,在使用时非常灵活。下面从底向上具体分析.Net PetShop 4.0缓存方面的技术。

    首先看一下该项目中与缓存直接相关的命名空间:

PetShop.ICacheDependency
PetShop.TableCacheDependency
PetShop.CacheDependencyFactory
PetShop.Web

一、PetShop.ICacheDependency命名空间

   最低层应该是接口的定义了,在PetShop.ICacheDependency命名空间中只定义了一个接口 IPetShopCacheDependency,该接口只有一个方法 GetDependency,没有任何参数,返回AggregateCacheDependency类型。 AggregateCacheDependency是在.NET Framework 2.0 版中是新增的类,组合 ASP.NET 应用程序的 Cache 对象中存储的项和 CacheDependency 对象的数组之间的多个依赖项(MSDN中原话)。

二、PetShop.TableCacheDependency命名空间

   PetShop.TableCacheDependency命名空间中,提供两种类:抽象类TableDependency和它的继承类CategoryItemProduct。抽象类TableDependency的构造函数为:

 1 protected TableDependency(string configKey) {
 2 
 3     string dbName = ConfigurationManager.AppSettings["CacheDatabaseName"];
 4     string tableConfig = ConfigurationManager.AppSettings[configKey];
 5     string[] tables = tableConfig.Split(configurationSeparator);
 6 
 7     foreach (string tableName in tables)
 8         dependency.Add(new SqlCacheDependency(dbName, tableName));
 9 }
10 

   传递了一个参数configKey,根据该参数从web.config文件中获取表名列表,同时在web.config中获取数据库名称。将表名列表中的所有数据表添加到AggregateCacheDependency类型的dependency变量中。在此外使用了.NET Framework 2.0 版中是新增的另一个与缓存有关的SqlCacheDependency类。这个类用于建立ASP.NET应用程序的Cache对象中存储的项和特定SQL Server数据库表之间的联系。AggregateCacheDependencySqlCacheDependency都从 CacheDependency继承而来,但在.NET 2.0中还未提供Oracle等其它数据库对应的类。

   下面是web.config文件中与缓存相关的设置:
1 <!-- Cache dependency options. Possible values: PetShop.TableCacheDependency for SQL Server and keep empty for ORACLE -->
2 <add key="CacheDependencyAssembly" value="PetShop.TableCacheDependency"/>
3 <!-- CacheDatabaseName should match the name under caching section, when using TableCacheDependency -->
4 <add key="CacheDatabaseName" value="MSPetShop4"/>
5 <!-- *TableDependency lists table dependency for each instance separated by comma -->
6 <add key="CategoryTableDependency" value="Category"/>
7 <add key="ProductTableDependency" value="Product,Category"/>
8 <add key="ItemTableDependency" value="Product,Category,Item"/>
9 
   每个继承类都只有一个构造函数,通过设置基类的configKey参数变成了三个不同的类。Product类的构造函数为:

public Product() : base("ProductTableDependency") { }

三 CacheDependency工厂

    继 承了抽象类TableDependency的Product、Category和Item类均需要在调用时创建各自的对象。由于它们的父类 TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了 IPetShopCacheDependency接口,这为实现工厂模式提供了前提。

   在PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:

 1 public static class DependencyAccess
 2 {       
 3     public static IPetShopCacheDependency CreateCategoryDependency()
 4     {
 5         return LoadInstance("Category");
 6     }
 7     public static IPetShopCacheDependency CreateProductDependency()
 8     {
 9         return LoadInstance("Product");
10     }
11     public static IPetShopCacheDependency CreateItemDependency()
12     {
13         return LoadInstance("Item");
14     }
15     private static IPetShopCacheDependency LoadInstance(string className)
16     {
17         string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
18         string fullyQualifiedClass = path + "." + className;
19         return (IPetShopCacheDependency)Assembly.Load(path).CreateInstance(fullyQualifiedClass);
20     }
21 }

    在这个方法中通过配置文件中的设置和传进来的参数className,返回相对应的程序集和类。DependencyAccess类里面的其它三个方法,只是调用这个方法,传入不同的参数而已。

整个工厂模式的实现如图4-3所示:


   DependencyFacade 类提供的三个方法正好与DependencyAccess类的三个方法相对应,分别获取CategoryItemProduct AggregateCacheDependency。在DependencyFacade类中还读取了web.config中的 CacheDependencyAssembly设置,从而决定是调用DependencyAccess对应的方法,还是直接返回null

 1 public static class DependencyFacade
 2 {
 3     private static readonly string path = ConfigurationManager.AppSettings["CacheDependencyAssembly"];
 4     public static AggregateCacheDependency GetCategoryDependency()
 5     {
 6         if (!string.IsNullOrEmpty(path))
 7             return DependencyAccess.CreateCategoryDependency().GetDependency();
 8         else
 9             return null;
10     }
11     public static AggregateCacheDependency GetProductDependency()
12     {
13         if (!string.IsNullOrEmpty(path))
14             return DependencyAccess.CreateProductDependency().GetDependency();
15         else
16             return null;
17         }
18     public static AggregateCacheDependency GetItemDependency()
19     {
20         if (!string.IsNullOrEmpty(path))
21             return DependencyAccess.CreateItemDependency().GetDependency();
22         else
23             return null;
24     }
25 }
26 
     DependencyFacade类封装了获取AggregateCacheDependency类型对象的逻辑,如此一来,调用者可以调用相关方法获得创建相关依赖项的AggregateCacheDependency类型对象:
1 AggregateCacheDependency dependency = DependencyFacade.GetCategoryDependency();
   比起直接调用DependencyAccess类的GetDependency()方法而言,除了方法更简单之外,同时它还对CacheDependencyAssembly配置节进行了判断,如果其值为空,则返回null对象。

  虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类Category、Product、 Item,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的 AggregateCacheDependency类型的对象。

四、PetShop.Web命名空间
   在PetShop.Web App_Code中,有四个静态类与缓存直接相关,分别是CategoryDataProxyItemDataProxy ProductDataProxyWebUtility。其中前三个分别调用DependencyFacade对应的方法。例如GetCategoryName()方法:
 1 public static string GetCategoryName(string categoryId)
 2 {
 3      Category category = new Category();
 4      if (!enableCaching)
 5             return category.GetCategory(categoryId).Name;
 6      string cacheKey = string.Format(CATEGORY_NAME_KEY, categoryId);
 7      // 检查缓存中是否存在该数据项;
 8      string data = (string)HttpRuntime.Cache[cacheKey];
 9      if (data == null)
10      {
11            // 通过web.config的配置获取duration值;
12            int cacheDuration = int.Parse(ConfigurationManager.AppSettings["CategoryCacheDuration"]);
13            // 如果缓存中不存在该数据项,则通过业务逻辑层访问数据库获取;
14            data = category.GetCategory(categoryId).Name;
15            // 通过Facade类创建AggregateCacheDependency对象;
16            AggregateCacheDependency cd = DependencyFacade.GetCategoryDependency();
17            // 将数据项以及AggregateCacheDependency 对象存储到缓存中;
18            HttpRuntime.Cache.Add(cacheKey, data, cd, DateTime.Now.AddHours(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
19       }
20       return data;
21 }
22 

   GetCategoryName ()方法首先会检查缓存中是否已经存在CategoryName数据项,如果已经存在,就通过缓存直接获取数据;否则将通过业务逻辑层调用数据访问层访问 数据库获得CategoryName,在获得了CategoryName后,会将新获取的数据连同DependencyFacade类创建的 AggregateCacheDependency对象添加到缓存中。
     WebUtility静态类被表示层的许多页面所调用,例如Product页面:


1 public partial class Products : System.Web.UI.Page
2 {
3     protected void Page_Load(object sender, EventArgs e)
4     {
5         Page.Title = WebUtility.GetCategoryName(Request.QueryString["categoryId"]);
6     }
7 }
  
     显示页面
title的逻辑是放在Page_Load事件方法中,因而每次打开该页面都要执行获取CategoryName的方法。如果没有采用缓存机制,当Category数据较多时,页面的显示就会非常缓慢。
   显示页面title的逻辑是放在Page_Load事件方法中,因而每次打开该页面都要执行获取CategoryName的方法。如果没有采用缓存机制,当Category数据较多时,页面的显示就会非常缓慢。


posted on 2007-08-24 14:23  nerozhang  阅读(892)  评论(1编辑  收藏  举报