Spring Cache
Outline:
- Basic Idea
- 启用Spring缓存支持
- 提供缓存管理器
- 应用缓存
ref: Spring In Action
Basic Idea
缓存实际上是一种面向切面的行为。Spring将缓存实现为一个切面。
- 在使用XML声明缓存规则时,这一点非常明显:我们必须要将缓存通知绑定到一个切点上
缓存编程步骤:
- 启用Spring缓存支持(java方式): 提供缓存配置类,加上
@EnableCaching
- 提供缓存管理器:给配置类提供一个CacheManager`
- 应用缓存:给需要缓存的方法加上对应的注解
启用Spring缓存支持
Spring启用缓存支持有两种方式:
注解驱动的缓存:在一个配置类上添加
@EnableCaching
1
2
3
4
5
6
7
8
9
10
11
12
13
14import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
public class CachingConfig {
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager(); //这里还声明了一个ConcurrentMapCacheManager的Bean
}
}- 当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。. 参见.
XML声明的缓存: 用Spring cache命名空间中的
<cache:annotation-driven>
元素来启用注解驱动的缓 存1
2
3
4
5
6
7
8
9
10<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.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
</beans>
本质上,@EnableCaching
和<cache:annotation-driven>
的工作方式是相同的。它们都会创建一个切面(aspect)并 触发Spring缓存注解的切点(pointcut)
配置CacheManager
Spring 内置的缓存管理器:
SimpleCacheManager
NoOpCacheManager
ConcurrentMapCacheManager
CompositeCacheManager
EhCacheCacheManager
Spring Data的缓存管理器:
RedisCacheManager
(来自于Spring Data Redis项目)GemfireCacheManager
EhCache
对比Redis, 跑在同一个进程上, 速度更快
1 | import net.sf.ehcache.CacheManager; //ehcache提供的CacheManager |
cacheManager()
方法通过传入Ehcache的CacheManager
实例, 创建了一个EhCacheCacheManager
实例- Spring和ehcache都定义了
CacheManager
类型,我们需要将 ehcache的CacheManager
注入到Spring的EhCacheCacheManager
- Spring提供了
EhCacheManagerFactoryBean
来生成EhCache的CacheManager
。方法ehcache()
会创建并返回一EhCacheManagerFactoryBean
实例。因为它是一个工厂bean(即实现了Spring的FactoryBean
接口),所以注册在Spring应用上下文中的并不是EhCacheManagerFactoryBean的实例,而是Ehcache的CacheManager的实例, 后者被注入到EhCacheCacheManager
之中
- Spring和ehcache都定义了
EhCache
自身也需要配置(通过XML), 我们在创建EhCacheManagerFactoryBean
的过程中, 通过setConfigLocation()
方法,传入ClassPath-Resource
,来指定EhCache
XML配置文件相对于根类路径(classpath)的位置。
ehCache配置文件
ehCache官方文档
1 | <ehcache> |
Redis
优点:
对比EhCache, 可以实现跨进程的缓存
缓存的条目是键值对,其中key描述了产生value的操作和参数。因此, Redis作为key-value存储,非常适合存储缓存
Spring Data Redis提供了RedisCacheManager
, 它与一个Redis Server协作,并通过RedisTemplate
将缓存条目存储到Redis中
1 |
|
cacheManager()
方法通过传入RedisTemplate
实例, 创建了一个RedisCacheManager
实例- 为了使用
RedisCacheManager
,我们需要RedisTemplate
及RedisConnection
Bean, 这同样用工厂Bean实现
使用多个缓存管理器
可以用Spring的CompositeCacheManager
1 |
|
会从EhCacheCacheManager
检查Ehcache, 然后从RedisCacheManager
检查Redis
应用缓存
这里主要介绍用注解配置缓存
@Cacheable
@Cacheable( cache_name )
方法的结果会被存到指定的缓存中,下次采用相同的参数进行方法调用时,会使用缓存中的结果 例如:
1 |
|
当findOne()被调用时,缓存切面会拦截调用并在缓存中查找之前 以名spittleCache存储的返回值。缓存的key是传递 到findOne()方法中的id参数。如果按照这个key能够找到值的话, 就会返回找到的值,方法不会再被调用。如果没有找到值的话,那么 就会调用这个方法,并将返回值放到缓存之中,为下一次调 用findOne()方法做好准备
可以加到接口的方法上, 这会导致所有实现该接口的类的对应方法都应用该注解
默认key生成: 默认key的生成按照以下规则: - 如果没有参数,则使用0作为key - 如果只有一个参数,使用该参数作为key - 如果又多个参数,使用包含所有参数的hashCode作为key
自定义key的生成: 当目标方法参数有多个时,有些参数并不适合缓存逻辑 比如:
1 | @Cacheable("books") |
其中checkWarehouse,includeUsed并不适合当做缓存的key.针对这种情况,Cacheable 允许指定生成key的关键属性,并且支持支持SpringEL表达式。(推荐方法) 再看一些例子:
1 |
|
缓存的同步 sync: 在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。 例:
1 | @Cacheable(cacheNames="foos", sync="true") |
属性condition: 有时候,一个方法可能不适合一直缓存(例如:可能依赖于给定的参数)。属性condition支持这种功能,通过SpEL 表达式来指定可求值的boolean值,为true才会缓存(在方法执行之前进行评估)。 例:
1 | @Cacheable(cacheNames="book", condition="#name.length < 32") |
此外,还有一个unless 属性可以用来是决定是否添加到缓存。与condition不同的是,unless表达式是在方法调用之后进行评估的。如果返回false,才放入缓存(与condition相反)。 #result指返回值 例:
1 | @Cacheable(cacheNames="book", condition="#name.length < 32", unless="#result.name.length > 5"") |
@CachePut
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
1 | @CachePut(cacheNames="book", key="#isbn") |
注意:应该避免@CachePut 和 @Cacheable同时使用的情况。
@CacheEvict
移除缓存条目
@CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素。
1 | @CacheEvict(cacheNames="books", allEntries=true) |
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
1 | @CacheEvict(cacheNames="books", beforeInvocation=true) |
@CacheConfig
有时候一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig
1 | @CacheConfig("books") |
@CacheConfig是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager 和CacheResolver。 该操作会被覆盖。
用XML配置缓存
就是在XML里面指定缓存规则要应用到哪些方法, 避免在源代码里写@Cacheable()
, 比较晦涩,不好用