相关阅读:关于缓存的进阶了解:关于缓存你需要知道的
一、启用对缓存的支持
Spring对缓存的支持有两种方式:
- 注解驱动的缓存(推荐使用)
- XML声明的缓存
1、使用注解驱动的缓存配置
使用Java配置的话,只需要在其中的一个配置类上添加@EnableCaching,这样的话就能启动注解驱动的缓存。如:
package spittr.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching // 启用缓存
public class CachingConfig {
@Bean
public CacheManager cacheManager(){ // 声明缓存管理器
return new ConcurrentMapCacheManager();
}
}
2、使用XML声明的缓存配置
如果以XML的方式配置应用的话,那么可以使用Spring cache命名空间中的<cache:annotation-driven>元素来启动注解驱动的缓存。
<?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.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />
</beans>
3、解释
本质上,@EnableCaching和<cache:annotation-driven />的工作方式是相同的。它们都会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。
而且在上面的程序配置中,它们不仅仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的bean。缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成。在Spring中主要内置了7个缓存管理器实现,如下所示:
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager:使用java.util.concurrent.ConcurrentHashMap作为缓存存储。它的缓存存储是基于内存的,所以它的生命周期是与应用关联的,对于生产级别的大型企业级应用程序,这可能并不是理想的选择。
- CompositeCacheManager:用于配置多个缓存管理器,它会迭代这些缓存管理器,以查找之前所缓存的值
- EhCacheCacheManager:使用Ehcache作为缓存存储
- RedisCacheManager:来自于Spring Data Redis项目
- GemfireCacheManager:来自于Spring Data GemFire项目
二、使用Ehcache缓存(使用Java方式配置)
1、在pom.xml中加入所需依赖
<!-- Ehcache依赖包 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>4.1.0.Final</version>
</dependency>
2、ehcache.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false">
<!-- 指定一个文件目录,当EhCache把数据写到硬盘上时,将把数据写到这个文件目录下 -->
<diskStore path="java.io.tmpdir"/>
<!-- 设定缓存的默认数据过期策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
<cache
name="myCache"
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="30"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LFU"
/>
</ehcache>
配置解析:
<diskStore> 当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口)
<diskStore path=""> 用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index
name 缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里)
maxElementsOnDisk 磁盘缓存中最多可以存放的元素数量,0表示无穷大
maxElementsInMemory 内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况
1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中
2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素
eternal 缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds
timeToIdleSeconds 缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性。即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除
timeToLiveSeconds 缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大。即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除
overflowToDisk 内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中),会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data
diskPersistent 是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache,后缀名为index的文件。这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存。要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒
diskSpoolBufferSizeMB 设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB
memoryStoreEvictionPolicy 内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存。共有三种策略,分别为LRU(最近最少使用)、LFU(最常用的)、FIFO(先进先出)
3、Ehcache的配置类
import net.sf.ehcache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
@Configuration
@EnableCaching
public class CachingConfig {
/*
* 配置EhCacheCacheManager
*/
@Bean
public EhCacheCacheManager cacheManager(CacheManager cacheManager){
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(cacheManager);
return ehCacheCacheManager;
}
/*
* 配置EhCacheManagerFactoryBean
*/
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean(){
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean ();
cacheManagerFactoryBean.setConfigLocation (new ClassPathResource("ehcache.xml"));
cacheManagerFactoryBean.setShared (true);
return cacheManagerFactoryBean;
}
}
4、为方法添加注解以支持缓存
如前文所述,Spring的缓存抽象在很大程度上围绕切面构建的。在Spring中启用缓存时,会创建一个切面,它触发一个或更多的Spring的缓存注解。下表列出了Spring所提供的缓存注解。
注:下表的所有注解都能运用在方法或类上。当将其放在单个方法上时,注解所描述的缓存行为只会运用到这个方法上。如果注解放在类级别的话,那么缓存行为就会应用到这个类的所有方法上。
注解 | 描述 |
@Cacheable | 表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则的话,这个方法就会被调用,返回值会放到缓存之中。 |
@CachePut | 表明Spring应该将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用 |
@CacheEvict | 表明Spring应该在缓存中清除一个或多个条目 |
@Caching | 这是一个分组的注解,能够同时应用多个其他的缓存注解 |
关于@Cacheable和@CachePut的异同点:
@Cacheable和@CachePut注解都可以填充缓存,但是它们的工作方式略有差异。
@Cacheable首先在缓存中查找条目,如果找到了匹配的条目,那么就不会对方法进行调用了。如果没有找到匹配的条目,方法会被调用并且返回值要放到缓存之中。(因此,一般用于缓存CRUD操作中的查找操作。)
而@CachePut并不会在缓存中检查匹配的值,目标放方法总是会被调用,并将返回值添加到缓存之中。(因此,一般用于缓存CRUD操作中的Create和Update操作。最后,使用@CacheEvict来缓存Delete操作。)
使用例子如下:(myCache为在ehcache.xml中配置的cache名字)
import com.scb.h2demo.entity.Staff;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import java.util.List;
public interface IStaffService {
List<Staff> getAllList();
Page<Staff> queryAllStaffList(int pageNum,int pageSize);
List<Staff> getByNameIsLike(String name);
@Cacheable("myCache")
Staff findOne(Integer id);
@CachePut(value="myCache", key = "#result.id")
Staff insert(Staff staff);
@CacheEvict("myCache")
void remove(Integer id);
@CacheEvict("myCache")
void deleteAll();
}
@Cacheable和@CachePut有一些属性是共有的,见下表:
属性 | 类型 | 描述 |
---|---|---|
value | String[] | 要使用的缓存名称 |
condition | String | SpEL表达式,如果得到的值是false的话,不会将缓存应用到方法调用上 |
key | String | SpEL表达式,用来计算自定义的缓存key |
unless | String | SpEL表达式,如果得到的值是true的话,返回值不会放到缓存之中 |
5、自定义缓存Key
@Cacheable和@CachePut都有一个名为key属性(缓存实际上是一个Key-Value形式的,所以有时候我们需要更改默认的Key,并用自定义的key来查找我们的Value,即被缓存的方法。默认的缓存Key一般都是将方法的参数作为缓存Key的),这个属性能够替换默认的key,它是通过一个SpEL表达式计算得到的。任意的SpEL表达式都是可行的,但是更常见的场景是所定义的表达式与存储在缓存中的值有关。据此计算得到key。
而在为缓存编写SpEL表达式的时候,Spring暴露了一些很有用的元数据。下表列出了SpEL中可用的缓存元数据。
表达式 | 描述 |
#root.args | 传递给缓存方法的参数,形式为数组 |
#root.caches | 该方法执行时所对应的缓存,形式为数组 |
#root.target | 目标对象 |
#root.targetClass | 目标对象的类,是#root.target.class的简写形式 |
#root.method | 缓存方法 |
#root.methodName | 缓存方法的名字,是#root.method.name的简写形式 |
#result | 方法调用的返回值(不能用在@Cacheable注解上) |
#Argument | 任意的方法参数名(如#argName)或参数索引(如#a0或#p0) |
如:将方法调用的返回值(Staff对象)的 id 作为Key
@CachePut( value = "myCache", key = "#result.id")
Staff save(Staff staff);
6、条件化缓存
通过为方法添加Spring的缓存注解,Spring就会围绕着这个方法创建一个缓存切面。但是,在有些场景下我们可能希望将缓存功能关闭。
@Cacheable和@CachePut提供了两个属性用以实现条件化缓存:unless和condition,这两个属性都接受一个SpEL表达式。如果unless属性的SpEL表达式计算结果为true,那么缓存方法返回的数据就不会放到缓存中。与之类似,如果conditon属性的SpEL表达式计算结果为false,那么对于这个方法缓存就会被禁用掉。
表面上来看,unless和condition属性做的是相同的事情。但是,这里有一点细微的差别。unless属性只能阻止将对象放进缓存,但是这个方法调用的时候,依然会去缓存中进行查找,如果找到了匹配的值,就会返回找到的值。与之不同的是,如果condition的表达式计算结果为false,那么在这个方法调用的过程中,缓存是被禁用的。就是说,不会去缓存进行查找,同时返回值也不会放进缓存中。
7、移除缓存条目
如上所诉,我们可使用@CacheEvict来移除缓存条目,在此需要补充的是,@CacheEvict所具有的属性。
属性 | 类型 | 描述 |
value | String[] | 要使用的缓存名称 |
key | String | SpEL表达式,用来计算自定义的缓存key |
condition | String | SpEL表达式,如果得到的值是false的话,不会将缓存应用到方法调用上 |
allEntries | boolean | 如果为true的话,特定缓存的所有条目都会被移除掉 |
beforeInvocation | boolean | 如果为true的话,在方法调用之前移除条目。如果为false(默认值)的话,在方法成功调用之后再移除条目 |
三、使用XML声明缓存
如果需要在没有源码的bean上应用缓存功能,最好将缓存配置与缓存数据的代码分隔开来。Spring的cache命名空间提供了使用XML声明缓存规则的方法,可以作为面向注解缓存的替代方法。因为缓存是一种面向切面的行为,所以cache命名空间会与Spring的aop命名空间结合起来使用,用来声明缓存所应用的切点在哪里。
要开始配置XML声明的缓存,首先需要创建Spring配置文件,这个文件中要包含cache和aop命名空间:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
</beans>
cache命名空间定义了在Spring XML配置文件中声明缓存的配置元素。下表列出了cache命名空间所提供的所有元素。
元素 | 描述 |
<cache:annotation-driven> | 启动注解驱动的缓存。等同于Java配置中的@EnableCaching |
<cache:advice> | 定义缓存通知(advice)。结合<aop:advisor>,将通知应用到切点上 |
<cache:caching> | 在缓存通知中,定义一组特定的缓存规则 |
<cache:cacheable> | 指明某个方法要进行缓存。等同于@Cacheable注解 |
<cache:cache-put> | 指明某个方法要填充缓存,但不会考虑缓存中是否有匹配的值。等同于@CachePut注解 |
<cache:cache-evict> | 指明某个方法要从缓存中移除一个或多个条目,等同于@CacheEvict注解 |