在访问量非常大,但更新较少的网站中使用缓存,可以大大提高程序运行的效率,给网络用户一个良好的体验效果。在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 数据较多时,页面的显示就会非常缓慢。