【转】Spring Cache简介
声明
本文为转载文章, 原文链接: http://haohaoxuexi.iteye.com/blog/2123030
简介
从3.1开始,Spring
引入了对Cache
的支持。其使用方法和原理都类似于Spring
对事务管理的支持。Spring Cache
是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache
的时候我们要保证我们缓存的方法对于相同的方法参数要有相同的返回结果。
使用Spring Cache
需要我们做两方面的事:
- 声明某些方法使用缓存
- 配置
Spring
对Cache
的支持
和Spring
对事务管理的支持一样,Spring
对Cache
的支持也有基于注解和基于XML
配置两种方式。下面我们先来看看基于注解的方式。
1 基于注解的支持
Spring
为我们提供了几个注解来支持Spring Cache
。其核心主要是@Cacheable
和@CacheEvict
。使用@Cacheable
标记的方法在执行后Spring Cache
将缓存其返回结果,而使用@CacheEvict
标记的方法会在方法执行前或者执行后移除Spring Cache
中的某些元素。下面我们将来详细介绍一下Spring
基于注解对Cache
的支持所提供的几个注解。
1.1 @Cacheable
@Cacheable
可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略,这个稍后会进行说明。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable
可以指定三个属性,value
、key
和condition
。
1.1.1 value属性指定Cache名称
value
属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache
上的,对应Cache
的名称。其可以是一个Cache
也可以是多个Cache
,当需要指定多个Cache
时其是一个数组。
@Cacheable("cache1")//Cache是发生在cache1上的
public User find(Integer id) {
return null;
}
@Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
public User find(Integer id) {
return null;
}
1.1.2 使用key属性自定义key
key
属性是用来指定Spring
缓存方法的返回结果时对应的key
的。该属性支持SpringEL
表达式。当我们没有指定该属性时,Spring
将使用默认策略生成key
。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
除了上述使用方法参数作为key
之外,Spring
还为我们提供了一个root
对象可以用来生成key
。通过该root对象我们可以获取到以下信息。
属性名称 | 描述 | 示例 |
---|---|---|
methodName |
当前方法名 | #root.methodName |
method |
当前方法 | #root.method.name |
target |
当前被调用的对象 | #root.target |
targetClass |
当前被调用的对象的class | #root.targetClass |
args |
当前方法参数组成的数组 | #root.args[0] |
caches |
当前被调用的方法使用的Cache | #root.caches[0].name |
当我们要使用root
对象的属性作为key
时我们也可以将#root
省略,因为Spring
默认使用的就是root
对象的属性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
1.1.3 condition属性指定发生的条件
有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition
属性可以实现这一功能。condition
属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL
表达式来指定的,当为true
时表示进行缓存处理;当为false
时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
1.2 @CachePut
在支持Spring Cache
的环境下,对于使用@Cacheable
标注的方法,Spring
在每次执行前都会检查Cache
中是否存在相同key
的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut
也可以声明一个方法支持缓存功能。与@Cacheable
不同的是使用@CachePut
标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut
也可以标注在类上和方法上。使用@CachePut
时我们可以指定的属性跟@Cacheable
是一样的。
@CachePut("users")//每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
return null;
}
1.3 @CacheEvict
@CacheEvict
是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict
可以指定的属性有value
、key
、condition
、allEntries
和beforeInvocation
。其中value
、key
和condition
的语义与@Cacheable
对应的属性类似。即value
表示清除操作是发生在哪些Cache
上的(对应Cache
的名称);key
表示需要清除的是哪个key
,如未指定则会使用默认策略生成的key
;condition
表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries
和beforeInvocation
。
1.3.1 allEntries属性
allEntries
是boolean
类型,表示是否需要清除缓存中的所有元素。默认为false
,表示不需要。当指定了allEntries
为true
时,Spring Cache
将忽略指定的key
。有的时候我们需要Cache
一下清除所有的元素,这比一个一个清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
1.3.2 beforeInvocation属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation
可以改变触发清除操作的时间,当我们指定该属性值为true
时,Spring
会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.out.println("delete user by id: " + id);
}
其实除了使用@CacheEvict
清除缓存元素外,当我们使用Ehcache
作为实现时,我们也可以配置Ehcache
自身的驱除策略,其是通过Ehcache
的配置文件来指定的。由于Ehcache
不是本文描述的重点,这里就不多赘述了,想了解更多关于Ehcache
的信息,请查看我关于Ehcache的专栏。
1.4 @Caching
@Caching
注解可以让我们在一个方法或者类上同时指定多个Spring Cache
相关的注解。其拥有三个属性:cacheable
、put
和evict
,分别用于指定@Cacheable
、@CachePut
和@CacheEvict
。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
return null;
}
1.5 使用自定义注解
Spring
允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用@Cacheable
进行标注的自定义注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
那么在我们需要缓存的方法上使用@MyCacheable
进行标注也可以达到同样的效果。
@MyCacheable
public User findById(Integer id) {
System.out.println("find user by id: " + id);
User user = new User();
user.setId(id);
user.setName("Name" + id);
return user;
}
2 配置Spring对Cache的支持
2.1 声明对Cache的支持
2.1.1 基于注解
配置Spring
对基于注解的Cache
的支持,首先我们需要在Spring
的配置文件中引入cache
命名空间,其次通过
<cache:annotation-driven />
就可以启用Spring
对基于注解的Cache
的支持。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven/>
</beans>
<cache:annotation-driven/>
有一个cache-manager
属性用来指定当前所使用的CacheManager
对应的bean
的名称,默认是cacheManager
,所以当我们的CacheManager
的id为cacheManager
时我们可以不指定该参数,否则就需要我们指定了。
<cache:annotation-driven/>
还可以指定一个mode
属性,可选值有proxy
和aspectj
。默认是使用proxy
。
此外,<cache:annotation-driven/>
还可以指定一个proxy-target-class
属性,表示是否要代理class
,默认为false
。我们前面提到的@Cacheable
、@cacheEvict
等也可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置proxy-target-class
为true
或者mode
为aspectj
时,是直接基于class
进行操作的,定义在接口上的@Cacheable
等Cache
注解不会被识别到,那对应的Spring Cache
也不会起作用了。
需要注意的是<cache:annotation-driven/>
只会去寻找定义在同一个ApplicationContext
下的@Cacheable
等缓存注解。
2.1.2 基于XML配置
除了使用注解来声明对Cache
的支持外,Spring
还支持使用XML
来声明对Cache
的支持。这主要是通过类似于aop:advice
的cache:advice
来进行的。在cache
命名空间下定义了一个cache:advice
元素用来定义一个对于Cache
的advice
。其需要指定一个cache-manager
属性,默认为cacheManager
。cache:advice
下面可以指定多个cache:caching
元素,其有点类似于使用注解时的@Caching
注解。cache:caching
元素下又可以指定cache:cacheable
、cache:cache-put
和cache:cache-evict
元素,它们类似于使用注解时的@Cacheable
、@CachePut
和@CacheEvict
。下面来看一个示例:
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="users">
<cache:cacheable method="findById" key="#p0"/>
<cache:cacheable method="find" key="#user.id"/>
<cache:cache-evict method="deleteAll" all-entries="true"/>
</cache:caching>
</cache:advice>
上面配置定义了一个名为cacheAdvice
的cache:advice
,其中指定了将缓存findById
方法和find
方法到名为users
的缓存中。这里的方法还可以使用通配符*
,比如find*
表示任何以find
开始的方法。
有了cache:advice
之后,我们还需要引入aop
命名空间,然后通过aop:config
指定定义好的cacheAdvice
要应用在哪些pointcut
上。如:
<aop:config proxy-target-class="false">
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>
上面的配置表示在调用com.xxx.UserService
中任意公共方法时将使用cacheAdvice
对应的cache:advice
来进行Spring Cache
处理。更多关于Spring Aop
的内容不在本文讨论范畴内。
2.2 配置CacheManager
CacheManager
是Spring
定义的一个用来管理Cache
的接口。Spring
自身已经为我们提供了两种CacheManager
的实现,一种是基于Java API
的ConcurrentMap
,另一种是基于第三方Cache
实现——Ehcache
,如果我们需要使用其它类型的缓存时,我们可以自己来实现Spring
的CacheManager
接口或AbstractCacheManager
抽象类。下面分别来看看Spring
已经为我们实现好了的两种CacheManager
的配置示例。
2.2.1 基于ConcurrentMap的配置
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
</set>
</property>
</bean>
上面的配置使用的是一个SimpleCacheManager
,其中包含一个名为xxx
的ConcurrentMapCache
。
2.2.2 基于Ehcache的配置
<!-- Ehcache实现 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcacheManager"/>
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="ehcache-spring.xml"/>
上面的配置使用了一个Spring
提供的EhCacheCacheManager
来生成一个Spring
的CacheManager
,其接收一个Ehcache
的CacheManager
,因为真正用来存入缓存数据的还是Ehcache
。Ehcache
的CacheManager
是通过Spring
提供的EhCacheManagerFactoryBean
来生成的,其可以通过指定ehcache
的配置文件位置来生成一个Ehcache
的CacheManager
。若未指定则将按照Ehcache
的默认规则取classpath
根路径下的ehcache.xml
文件,若该文件也不存在,则获取Ehcache
对应jar
包中的ehcache-failsafe.xml
文件作为配置文件。更多关于Ehcache
的内容这里就不多说了,它不属于本文讨论的内容,欲了解更多关于Ehcache
的内容可以参考我之前发布的Ehcache系列文章,也可以参考官方文档等。
3 键的生成策略
键的生成策略有两种,一种是默认策略,一种是自定义策略。
3.1 默认策略
默认的Key
生成策略是通过KeyGenerator
生成的,其默认策略如下:
-
如果方法没有参数,则使用
0
作为key
。 -
如果只有一个参数的话则使用该参数作为
key
。 -
如果参数多余一个的话则使用所有参数的
hashCode
作为key
。
如果我们需要指定自己的默认策略的话,那么我们可以实现自己的KeyGenerator
,然后指定我们的Spring Cache
使用的KeyGenerator
为我们自己定义的KeyGenerator
。
使用基于注解的配置时是通过cache:annotation-driven
指定的.
<cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
而使用基于XML
配置时是通过cache:advice
来指定的。
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>
需要注意的是此时我们所有的Cache
使用的Key
的默认生成策略都是同一个KeyGenerator
。
3.2 自定义策略
自定义策略是指我们可以通过Spring
的EL
表达式来指定我们的key
。这里的EL
表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用#参数名
或者#p[i]
。下面是几个使用参数作为key
的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
return null;
}
除了上述使用方法参数作为key
之外,Spring
还为我们提供了一个root
对象可以用来生成key
。通过该root
对象我们可以获取到以下信息。
属性名称 | 描述 | 示例 |
---|---|---|
methodName |
当前方法名 | #root.methodName |
method |
当前方法 | #root.method.name |
target |
当前被调用的对象 | #root.target |
targetClass |
当前被调用的对象的class | #root.targetClass |
args |
当前方法参数组成的数组 | #root.args[0] |
caches |
当前被调用的方法使用的Cache | #root.caches[0].name |
当我们要使用root
对象的属性作为key
时我们也可以将#root
省略,因为Spring
默认使用的就是root
对象的属性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
return null;
}
4 Spring单独使用Ehcache
前面介绍的内容是Spring
内置的对Cache
的支持,其实我们也可以通过Spring
自己单独的使用Ehcache
的CacheManager
或Ehcache
对象。通过在Application Context
中配置EhCacheManagerFactoryBean
和EhCacheFactoryBean
,我们就可以把对应的EhCache
的CacheManager
和Ehcache
对象注入到其它的Spring bean
对象中进行使用。
4.1 EhCacheManagerFactoryBean
EhCacheManagerFactoryBean
是Spring
内置的一个可以产生Ehcache
的CacheManager
对象的FactoryBean
。其可以通过属性configLocation
指定用于创建CacheManager
的Ehcache
配置文件的路径,通常是ehcache.xml文件的路径。如果没有指定configLocation
,则将使用默认位置的配置文件创建CacheManager
,这是属于Ehcache
自身的逻辑,即如果在classpath
根路径下存在ehcache.xml
文件,则直接使用该文件作为Ehcache
的配置文件,否则将使用ehcache-xxx.jar
中的ehcache-failsafe.xml
文件作为配置文件来创建Ehcache
的CacheManager
。此外,如果不希望创建的CacheManager
使用默认的名称(在ehcache.xml
文件中定义的,或者是由CacheManager
内部定义的),则可以通过cacheManagerName
属性进行指定。下面是一个配置EhCacheManagerFactoryBean
的示例。
<!-- 定义CacheManager -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定新建的CacheManager的名称 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
4.2 EhCacheFactoryBean
EhCacheFactoryBean
是用来产生Ehcache
的Ehcache
对象的FactoryBean
。定义EhcacheFactoryBean
时有两个很重要的属性我们可以来指定。一个是cacheManager
属性,其可以指定将用来获取或创建Ehcache
的CacheManager
对象,若未指定则将通过CacheManager.create()
获取或创建默认的CacheManager
。另一个重要属性是cacheName
,其表示当前EhCacheFactoryBean
对应的是CacheManager
中的哪一个Ehcache
对象,若未指定默认使用beanName
作为cacheName
。若CacheManager
中不存在对应cacheName
的Ehcache
对象,则将使用CacheManager
创建一个名为cacheName
的Cache
对象。此外我们还可以通过EhCacheFactoryBean
的timeToIdle
、timeToLive
等属性指定要创建的Cache
的对应属性,注意这些属性只对CacheManager
中不存在对应Cache
时新建的Cache
才起作用,对已经存在的Cache
将不起作用,更多属性设置请参考Spring
的API
文档。此外还有几个属性是对不管是已经存在还是新创建的Cache
都起作用的属性:statisticsEnabled
、sampledStatisticsEnabled
、disabled
、blocking
和cacheEventListeners
,其中前四个默认都是false
,最后一个表示为当前Cache
指定CacheEventListener
。下面是一个定义EhCacheFactoryBean
的示例。
<!-- 定义CacheManager -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- 指定配置文件的位置 -->
<property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
<!-- 指定新建的CacheManager的名称 -->
<property name="cacheManagerName" value="cacheManagerName"/>
</bean>
<!-- 定义一个Ehcache -->
<bean id="userCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheName" value="user"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
(注:本文是基于Spring3.1.0所写)