启用对缓存的支持
在Java的Config配置类上:
@Configuration
@EnableCaching //启用缓存
public class CachingConfig{
@Bean
public CacheManager cacheManager(){ //声明缓存管理器
return new ConcurrentMapCacheManager();
}
}
@EnableCaching和XML中的<cache:annotationn-driven />的工作方式相同,它们会创建一个切面并触发Sping缓存注解的切点,然后进行缓存操作。
配置时还声明了一个CacheManager缓存管理器。这个缓存管理器使用java.util.concurrent.ConcurrentHashMap作为其缓存存储。但它的缓存存储是基于内存的。
我们还可以根据自己的需要配置其它的缓存管理器。
配置缓存管理器
Spring 3.1内置了五个缓存管理器实现:
- SimpeCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
Spring Data又提供了两个缓存管理器:
- RedisCacheManager(来自于Spring Data Redis)
- GemfireCacheManager(来自于Spring Data GemFire)
使用EhCache缓存
在Java的Config配置类中:
@Configuration
@EnableCaching //启用缓存
public class CachingConfig{
@Bean //这里是net.sf.ehcache.CacheManager
public EhCacheManager cacheManager(CacheManager cm){ //配置EhCacheCacheManager
return new EhCacheCacheManager(cm); //需要注入一个CacheManager,下面声明了这个bean
//返回的是 org.springframework.cache.ehcache.EhCacheCacheManager
}
@Bean
public EhCacheManagerFactoryBean ehcache(){ //配置EhCacheManagerFactoryBean
EhCacheManagerFactoryBean ecmfBean = new EhCacheManagerFactoryBean();
ecmfBean.setConfiguration(new ClassPathResource("xxxx/ehcache.xml"));
return ecmfBean;
//实际上它是CacheManager的实例,而不是EhCacheManagerFactoryBean的实例
}
}
第一个方法,cacheManager会创建一个EhCacheCacheManager的实例。因为Spring和EhCache都定义了CacheManager类型,是EhCache的CacheManager被注入到了Spring的EhCacheCacheManager(Spring中CacheManager的实现)中。
第二个方法,因为第一步需要一个EhCache的CacheManager进行注入,所以必须要声明一个CacheManager的bean,也就是第二个方法所得到的这个bean。Spring提供了EhCacheManagerFactoryBean(它实现了FactoryBean接口)来生成EhCache的CacheManager。
除了配置类,我们还需要一个EhCache的XML配置文件ehcache.xml:
<ehcache>
<cache name="spittleCache"
maxBytesLocalHeap="50m"
timeToLiveSeconds="100" /> <!-- 最大堆存储为50m,生命周期100s -->
</ehcache>
使用Redis缓存
Spring Data Redis提供的RedisCacheManager也是CacheManager的一个实现。
RedisCacheManager会与一个Redis服务器协作,通过RedisTemplate将缓存储存到Redis中。
所以我们需要一个RedisTemplate的bean,和一个RedisConnectionFactory(连接工厂)实现类的bean。
Java的Config配置类:
@Configuration
@EnableCaching
public class CachingConfig{
@Bean //Redis缓存管理器Bean
public CacheManager cacheManager(RedisTemplate redisTemplate){
return new RedisCacheManager(redisTemplate);
}
@Bean //Redis连接工厂bean,也可以选择其它类型的连接工厂
public JedisConnectionFactory redisConnectionFactory(){
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.afterPropertiesSet(); //
return jedisConnectionFactory;
}
@Bean //RedisTemplate bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisCF){
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(redisCF);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
使用多个缓存管理器
也可以使用CompositeCacheManager迭代已经添加的缓存管理器,来查找缓存值。
例如,将Redis示例中CachingConfig类和EhCache示例中CachingConfig类中的Bean合到一个Config类中,并将cacheManager修改成:
@Bean
public CacheManager cacheManager(net.cf.ehcache.CacheManager cm, //EhCache
javax.cache.CacheManager jcm){ //JCache
CompositeCacheManager cacheManager = new CompositeCacheManager();
List<CacheManager> managers = new ArrayList<>();
managers.add(new JCacheCacheManager(jcm));
managers.add(new EhCacheCacheManager(cm));
managers.add(new RedisCacheManager(redisTemplate())); //注入redisTemplate bean,该方法在类中已声明
cacheManager.setCacheManagers(managers);
return cacheManager;
}
查找缓存时,会按照JCache、EhCache、RedisCache的顺序来查找。
为方法添加注解以支持缓存
Spring的缓存是围绕着切面来构建的,启用缓存时会创建一个切面,它触发Spring的缓存注解。
Spring提供了四个注解来声明缓存规则,它们可以用在方法或类上,如果放在类级别,缓存行为会应用这个类所以的方法。
注解 | 描述 |
---|---|
@Cacheable | 在调用方法之前,首先应该在缓存中查找方法的返回值。如果找到就返回该值,否则再调用该方法,返回值会放到缓存中 |
@CachePut | 将方法的返回值放到缓存中。并且在方法的调用前不会检查缓存,方法始终都被调用 |
@CacheEvict | 在缓存中清除一个或多个条目 |
@Caching | 这是一个分组的注解,能够同时应用多个其它的缓存注解 |
@Cacheable和@CachePut注解都可以填充缓存,所以有4个共有属性:
属性 | 类型 | 描述 |
---|---|---|
value | String[] | 要使用的缓存名称 |
condition | String | SpEL表达式,如果得到的值是false,不会将缓存应用到方法调用上 |
key | String | SpELl表达式,用来计算自定义的缓存key |
unless | String | SpEL表达式,如果得到true,返回值不会放入缓存中 |
在查询操作时,只需使用value属性就可以完成缓存。例如:SpittleRepository的findOne()方法:
@Cacheable("spittleCache") //使用spittleCache缓存
//通常情况下,@Cacheable注解应该放在接口方法声明类上,而不是实现类上
public Spittle findOne(long id){
try{
return "进行查询操作,得到一个Spittle对象返回";
} catch (Exception e){
return null;
}
}
findOne()方法被调用时,缓存切面会拦截调用,并在缓存中查找以名SpittleCache存储的返回值。缓存的key是传入的id参数。
将值放入缓存
@CachePut注解通常使用在插入记录时。例如,一个新的Spittle对象调取save()方法保存后,很可能马上会请求查看这个记录。
@CachePut("SpittleCache")
Spittle save(Spittle spittle);//必须有返回值缓存才能生效
当save方法被调用时,会先执行方法,然后将返回值放入缓存。但是不能将参数spittle作为key,我们需要另外指明它的key为spittleId。
通过@CachePut或@Cacheable注解的key属性,可以计算声明缓存的key。
Spring提供的SpEL表达式有:
表达式 | 描述 |
---|---|
#root.args | 传递给方法的参数,形式为数组 |
#root.caches | 该方法执行时对应的缓存,形式为数组 |
#root.target | 目标对象 |
#root.targetClass | 目标对象的类 |
#root.method | 缓存方法 |
#root.methodName | 缓存方法的名字 |
#result | 方法调用的返回值(不能用在注解的condition属性上) |
#Argument | 任意的方法参数名(如#argName)或参数索引(#a0或#p0) |
对于save方法,#result可以获得返回的对象,可以补充为:
@CachePut(value="spittleCache", key="#result.id")
Spittle save(Spittle spittle);
条件化缓存
@Cacheable和@CachePut注解提供了两个属性来实现条件化缓存:unless和condition。
- unless:SpEL表达式属性为true,数据不进行缓存。
- condition: SpELl表达式属性为false,数据不进行缓存。
unless属性在方法调用的时候会先在缓存中查找,而condition生效时,缓存禁用,不进行查找也不存储。
例如:对于message包含"NoCache"字段的Spittle对象不存入缓存:
@Cacheable(value="spittleCache", unless="#result.message.contains('NoCache')");
Spittle findOne(long id);
对于id小于10的Spittle对象禁用缓存,不从缓存中查找也不存入缓存:
@Cacheable(value="spittleCache",
unless="#result.message.contains('NoCache')",
condition="#id >= 10")
Spittle findOne(long id);
condition目的是决定要不要禁用缓存,所以它不能等到已经返回值了才确定是否禁用缓存。
移除缓存条目
@CacheEvict注解会从缓存中移除条目,常见应用场景是删除了某一条数据:
@CacheEvict("spittleCache")
void remove(long spittleId);//CacheEvict注解可以应用在返回值为void方法上
remove()方法被调用时,会删除key为spittleId的缓存记录。
@CacheEvict注解的可用属性如下:
属性 | 类型 | 描述 |
---|---|---|
value | String[] | 要使用的缓存名 |
key | String | SpELl表达式,用来计算缓存的key |
condition | String | SpELl表达式,得到false就不会应用缓存 |
allEntries | boolean | 如果为true,缓存的所以条目都被移除 |
beforeInvocation | boolean | 如果为true,在方法调用之前移除条目;如果为false(默认),在方法成功调用之后移除条目 |
使用XML声明缓存
XML文件中要包含cache和aop的命名空间。
cache命名空间提供的标签:
标签 | 描述 |
---|---|
<cache:annotation-driven> | 启用注解驱动的缓存。等同于@EnableCaching |
<cache:advice> | 定义缓存通知。结合<aop:advisor>,将通知应用到切点上 |
<cache:caching> | 在缓存通知中,定义一组特定的缓存规则 |
<cache:cacheable> | 指明某个方法要进行缓存。等同于@Cacheable注解 |
<cache:cache-put> | 指明某个方法要填充缓存。等同于@CachePut |
<cache:cache-evict> | 指明某个方法要从缓存中移除一个或多个条目,等同于@CacheEvict |
示例:
<aop:config>
<aop:advisor advice-ref="cacheAdvice"
pointcut="execution(* xxxx.SpittleRepository.*(..))" /> <!-- 将缓存通知绑定到切点上-->
</aop:config>
<cache:advice id="cacheAdvice" cache-manager="cacheManager"> <!-- 指定缓存管理器-->
<cache:caching>
<cache:cacheable cache="spittleCache" method="findRecent" /> <!-- 配置方法支持缓存-->
<cache:cacheable cache="spittleCache" method="findOne" />
<cache:cacheable cache="spittleCache" method="findBySpittleId" />
<cache:cache-put cache="spittleCache" method="save" key="#result.id" /> <!-- 配置方法填充缓存-->
<cache:cache-evict cache="spittleCache" method="remove" /> <!-- 配置方法移除缓存-->
</cache:caching>
</cache:advice>
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
<aop:advisor>引用ID为cacheAdvice的通知,它将这个通知与一个切点匹配,建立了一个完整的切面。它需要指定一个缓存管理器,默认值是cacheManager,所以下面有一个cacheManager的bean。
通知利用<cache:advice>元素声明,在<cache:advice>下面可以包含任意数量的<cache:caching>。
<cache:caching>有四个可以供子标签共享的属性:
- cache:指明要使用的缓存名称
- condition:SpEL表达式,false禁用缓存。
- key:SpEL表达式,用来得到缓存的key
- method:要缓存的方法名