在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。在Microsoft提供的经典示例项 目.Net PetShop 4.0中,也提供了对缓存的支持,本文是作者在学习此项目时的一些心得体会,有一些地方还不十分清楚,希望能够抛砖引玉。
在.Net PetShop 4.0中,非常成功地使用了工厂模式以及接口(interface)、静态类(Static class)、抽象类(abstract class)等成员。在使用缓存时,也是通过web.config配置进行设置,在使用时非常灵活。下面从底向上具体分析.Net PetShop 4.0缓存方面的技术。
首先看一下该项目中与缓存直接相关的命名空间:
PetShop.ICacheDependencyPetShop.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和它的继承类Category、Item和Product。抽象类TableDependency的构造函数为: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数据库表之间的联系。AggregateCacheDependency和SqlCacheDependency都从 CacheDependency继承而来,但在.NET 2.0中还未提供Oracle等其它数据库对应的类。
下面是web.config文件中与缓存相关的设置:
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
三 CacheDependency工厂
继 承了抽象类TableDependency的Product、Category和Item类均需要在调用时创建各自的对象。由于它们的父类 TableDependency实现了接口IPetShopCacheDependency,因而它们也间接实现了 IPetShopCacheDependency接口,这为实现工厂模式提供了前提。
在PetShop 4.0中,依然利用了配置文件和反射技术来实现工厂模式。命名空间PetShop.CacheDependencyFactory中,类DependencyAccess即为创建IPetShopCacheDependency对象的工厂类:
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所示:
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
虽然DependencyAccess类创建了实现了IPetShopCacheDependency接口的类Category、Product、 Item,然而我们之所以引入IPetShopCacheDependency接口,其目的就在于获得创建了依赖项的 AggregateCacheDependency类型的对象。
四、PetShop.Web命名空间
在PetShop.Web 的App_Code中,有四个静态类与缓存直接相关,分别是CategoryDataProxy、ItemDataProxy、 ProductDataProxy和WebUtility。其中前三个分别调用DependencyFacade对应的方法。例如GetCategoryName()方法:
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页面:
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数据较多时,页面的显示就会非常缓慢。