能跑着自动充电电动车:Spring in Action(2nd) Cache

来源:百度文库 编辑:九乡新闻网 时间:2024/04/27 23:46:20
缓存
在很多程序里,读取数据的频率比写入要高得多。比如RoadRantz,访问站点来查看帖子的人比张贴帖子的人要多。虽然帖子列表会随着时间不断增长,但其增长速度比不上被查看的速度。
更进一步说,RoadRantz所展示的数据对于实时性要求并不高。如果用户在访问站点时看到了稍微过时一点的帖子列表,并不会产生太多负面影响,他们会稍后再返回站点来查看更新的帖子列表,这样做并不会有太大问题。
尽管如此,DAO每次收到关于帖子列表的请求时,都会访问数据库来获得最新的数据(经常会得到与上次请求一样的数据)。
数据库操作通常都是程序性能的最大瓶颈。对于负载很大的程序来说,针对高度优化的数据源进行的最简单查询都可能会产生性能问题。
均衡考虑数据变化的频率以及查询数据库所付出的性能代价,总是从数据库获取最新数据似乎并不明智,而对频繁访问(但不频繁更新)的数据进行缓存则显得更加合理。
从表面上看,缓存似乎相当简单:在获取一些信息之后,把它保存到本地(和更便于访问的)位置,从而便于下次需要时使用。但是,手工实现缓存是很麻烦的。以HibernateRantDao的getRantsForDay()方法为例:
public List getRantsForDay(Date day) {
return getHibernateTemplate().find("from " + RANT +" where postedDate = ?", day);
}
这个方法就非常适合缓存。我们不可能让时间倒转,到过去的某一天来添加帖子。只要被查询的不是今天,对其他任意一天进行查询而返回的帖子列表都是一样的,也就没有必要总是对数据库进行操作来返回过去某一天的帖子列表。我们只需查询数据库一次,然后就可以记住结果,以备下次查询时使用。
下面我们来修改getRantsForDay(),使用某种自制形式的缓存:
public List getRantsForDay(Date day) {
List cachedResult =
rantCache.lookup("getRantsForDay", day);
if(cachedResult != null) {
return cachedResult;
}
cachedResult = getHibernateTemplate().find("from " + RANT +
" where postedDate = ?", day);
rantCache.store("getRantsForDay", day, cachedResult);
return cachedResult
}
这个版本的getRantsForDay()很不好用。这个方法的实际作用是查询指定日期的帖子,但其中大量代码都被用于处理缓存了。而且,它还没有直接处理缓存的一些复杂情况,比如缓存过期、刷新或溢出。
幸运的是,Spring程序有一种更优雅的缓存解决方案。Spring Modules项目(http://springmodules.dev.java.net)通过切面提供了缓存,它把通知应用于Bean方法来透明地对其结果进行缓存,而不是明确地指定要被缓存的方法。
如图5.13所示,Spring Modules对于缓存的支持涉及到一个代理,它拦截对Spring管理的Bean的一个或多个方法的调用。当一个被代理的方法被调用时,Spring Modules Cache首先查阅一个缓存来判断这个方法是否已经被使用同样参数调用过,如果是,它会返回缓存里的值,实际的方法并不会被调用;否则,实际方法会被调用,其返回值会被保存到缓存里,以备方法下一次被调用时使用。
在这一小节里,我们将使用Spring Modules Cache为RoadRantz的DAO层添加缓存功能,这样会让程序具有更好的性能,让繁忙的数据库轻松一些。
5.7.1  配置缓存方案
虽然Spring Modules会提供一个代理来拦截方法并把结果保存到缓存,它并没有提供一个实际的缓存解决方案,而是要依赖于第三方的缓存方案。可以使用的方案有多个,包括:
n    EHCache
n    GigaSpaces
n    JBoss Cache
n    JCS
n    OpenSymphony的OSCache
n    Tangosol的Coherence
我们为RoadRantz程序选择EHCache,主要是因为我以前使用它的经验及能够从www.ibibio.org的Maven仓库轻易获得。无论使用哪个缓存方案,对于Spring Modules Cache的配置基本上都是一样的。
首先要做的是新建一个Spring配置文件来声明缓存。虽然可以把Spring Modules Cache配置放到RoadRantz程序加载的任意一个Spring上下文配置文件里,但最好还是把它们分开,所以我们要创建roadrantz-cache.xml来保存缓存的配置。
与Spring上下文配置文件一样,roadrantz-cache.xml也以元素为根。但为了利用Spring Modules对EHCache的支持,我们要让元素能够识别ehcache命名空间:
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ehcache="http://www.springmodules.org/schema/ehcache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/
springmodules-ehcache.xsd">


我们为RoadRantz程序选择的是EHCache,如果想使用其他缓存方案,需要把Spring Modules命名究竟和规划声明修改为相应的内容。表5.6列出了每个命名空间及其URI和规划URI。
表5.6                   Spring Modules所支持的缓存方案的命名空间及规划
命 名 空 间
命名空间URI
规划URI
ehcache
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/springmodules- ehcache.xsd
gigaspaces
http://www.springmodules.org/ schema/gigaspaces
http://www.springmodules.org/schema/cache/springmodules- gigaspaces.xsd
jboss
http://www.springmodules.org/ schema/jboss
http://www.springmodules.org/schema/cache/springmodules- jboss.xsd
jcs
http://www.springmodules.org/ schema/jcs
http://www.springmodules.org/schema/cache/springmodules-jcs.xsd
oscache
http://www.springmodules.org/schema/oscache
http://www.springmodules.org/schema/cache/springmodules- oscache.xsd
tangosol
http://www.springmodules.org/schema/tangosol
http://www.springmodules.org/schema/cache/springmodules- tangosol.xsd
无论选择哪种缓存,都可以使用一些Spring配置元素在Spring里对缓存进行配置。表5.7列出了这些元素。
表5.7                                         Spring Modules的配置元素
配 置 元 素
用    途

以Java 5注解来声明被缓存的方法

以Jakarta通用属性元素数据来声明被缓存的方法

在Spring XML里配置缓存方案

在Spring XML里声明一个代理来声明被缓存的方法
在使用EHCache作为缓存方案时,需要告诉Spring到哪里寻找EHCache配置文件[5],这正是元素的用途所在:

在此对configLocation属性的设置告诉Spring从程序类路径的根位置加载EHCache的配置。
配置EHCache
我们已经配置了ehcache.xml文件,如程序清单5.12所示。
程序清单5.12 在ehcache.xml里配置EHCache。

在这段代码里,我们配置了两个缓存让EHCache进行管理。元素是必须有的,描述了在没有找到其他缓存情况下所使用的缓存。元素定义了另一个缓存,可以在ehcache.xml里出现0次或多次(每次针对定义的一个缓存)。在此,我们只定义了rantzCache作为惟一的非默认缓存。
里指定的属性描述了缓存的行为。表5.8列出在配置EHCache缓存时可以使用的属性。
表5.8                                          EHCache的缓存配置属性
属    性
用 于 指 定
diskExpiryThreadIntervalSeconds
磁盘过期线程运行的频率(以秒为单位),也就是磁盘存留的缓存清理过期项目的频率(默认是120秒)。
diskPersistent
磁盘缓存在VM重新启动时是否保持(默认为false)。
eternal
元素是否永恒。如果是永恒的,就永远不会过期(必须设置)。
maxElementsInMemory
内存能够被缓存的最大元素数量(必须设置)。
memoryStoreEvictionPolicy
当达到maxElementsInMemory时,如何强制进行驱逐。默认使用“最近使用(LRU)”策略,还可以使用“先入先出(FIFO)”和“较少使用(LFU)”策略。(默认是LRU。)
name
缓存的名称。(对于必须设置。)
overflowToDisk
当内存缓存达到maxElementsInMemory时,是否可以溢出到磁盘。(必须设置。)
timeToIdleSeconds
导致元素过期的访问间隔(以秒为单位)。设置为0表示元素可以永远空闲。(默认值是0。)
timeToLiveSeconds
元素在缓存里可以存在的时间(以秒为单位)。设置为0表示元素可以在缓存里永远存在而不过期。(默认值是0。)
对于RoadRantz程序,我们配置了一个默认缓存(这是EHCache要求的),还配置了一个名为rantzCache的缓存作为主缓存。两个缓存都设置为最多可以容纳500个元素(不过期),访问频率最低的元素会被踢出,不允许磁盘溢出[6]。
在Spring程序上下文里配置的EHCache之后,就可以声明哪个Bean和方法应该对结果进行缓存。首先,我们来声明一个代理来缓存RoadRantz DAO层里方法的返回值。
5.7.2  缓存的代理Bean
我们已经知道HibernateRantDao里的getRantsForDay()方法很适合进行缓存。再回到Spring上下文定义,我们要使用元素把一个代理包裹到HibernateRantDao,从而缓存从getRantsForDay()返回的全部内容:



元素声明哪个方法要被拦截、其返回值要保存到哪个缓存。本例中,methodName被设置为getRantsForDay(),要使用的缓存是rantzCache。
我们可以根据需要在里声明多个来描述Bean方法的缓存。我们可以让一个用于所有被缓存的方法,也可以使用通配符为一个元素指定多个方法。比如下面的元素会代理缓存全部名称由get开头的方法:

把数据放到缓存里只完成了一半的工作。在经过一段时间之后,缓存里一定会包含大量数据,其中很多已经没有意义了。最后,这些数据应该被清出缓存,数据缓存周期重新开始。下面我们来看一看如何在方法调用时刷新缓存。
刷新缓存
元素声明的是要向缓存中添加数据的方法,而元素声明了会清空缓存的方法。举例来说,假设我们想在saveRant()方法被调用时清空rantzCache缓存,那么就应该使用如下的元素:

在默认情况下,cacheName属性里指定的缓存会在methodName被调用之后清空,但利用when属性可以指定清空的时机:

把when属性设置为before可以让缓存在saveRant()被调用之前清空。
声明一个被代理的内部Bean
注意的id和refId属性。由生成的代理的id是rantDao,然而这是HibernateRantDao Bean的id,因此,我们需要把这个真正的Bean重命名为rantDaoTarget(由refId属性指定)。(这与传统Spring AOP代理及其目标的命名方式是一样的,详情请见4.2.3小节。)
如果觉得id/refId组合有些奇怪,我们还可以把目标Bean声明为的内部Bean。举例来说,下面就是把HibernateRantDao配置为一个内部Bean的






即使使用了内部Bean,我们仍然需要为每个要代理的Bean声明一个元素,为方法声明一个或多个元素。对于简单程序来说,这样做不会有什么问题,但随着代理缓存Bean和方法的数量不断增加,这将意味着Spring配置里越来越多的XML。
如果对内部Bean的方法仍然感到不快,或是需要代理多个要缓存的Bean,我们可以考虑使用Spring Modules对注解声明缓存的支持。接下来,让我们忘记,看一看Spring Modules如何支持注解驱动的缓存。
5.7.3  注解驱动的缓存
除了前面介绍的基于XML的缓存配置,Spring Modules还支持使用代码级元数据声明缓存。这种支持有两种形式:
Java 5注解:如果目标环境是Java 5平台,这就是很理想的解决方案。
Jakarta公共属性:如果目标环境是Java 5以前的平台,就应该选择它。
对于RoadRantz程序来说,其目标环境是Java 5,所以我们要使用Java 5注解来声明DAO层的缓存。对于缓存,Spring Modules提供了两个注解:
@Cacheable:声明一个方法的返回值应该被缓存。
@CacheFlush:声明一个方法是清空缓存的触发器。
利用@Cacheable注解,我们可以像下面这样把getRantsForDay()声明为要被缓存的:
@Cacheable(modelId="rantzCacheModel")
public List getRantsForDay(Date day) {
return getHibernateTemplate().find("from " + RANT +
" where postedDate = ?", day);
}
modelId属性指定用于缓存方法返回值的模型,稍后我们介绍说明如何定义缓存模型,现在先来看一看如何使用@CacheFlush来指定saveRant()被调用时的缓存清空操作:
@CacheFlush(modelId="rantzFlushModel")
public void saveRant(Rant rant) {
getHibernateTemplate().saveOrUpdate(rant);
}
modelId属性指定的刷新模型会在saveRant()方法被调用时被清空。
既然说到缓存模型和刷新模型,那么它们是从何而来的呢?元素被用于启动Spring Modules对注解的支持,我们会在roadrantzcache.xml文件里像下面这样配置它:



元素里,必须配置至少一个元素,它就定义了一个缓存模型。简单来说,缓存模型基本上就是对ehcache.xml里配置的一个缓存的引用。本例中,我们把rantzCacheModel与名为rantzCache的缓存关联起来,这样一来,任何modelId是rantzCacheModel的@Cacheable都会使用名为rantzCache的缓存。
刷新模型与缓存模型相当类似,只是它引用的是要被刷新的缓存。下面使用元素创建一个名为rantzFlushModel的刷新模型:




设置缓存模型与刷新模型的不同之处在于,刷新模型不仅决定要清空哪个缓存,还决定了何时清空。在默认情况下,缓存是在@CacheFlush注解的方法被调用之后清空的,但我们可以通过指定的when属性来改变:




把when属性设置为before之后,缓存就会@CacheFlush注解的方法被调用之前清空。