一、Spring cache 与 ehcache 有什么关系
1. Spring自身并没有实现缓存解决方案,但是对缓存管理功能提供了声明式的支持,能够与多种流行的缓存实现进行集成
2. Spring内置很多缓存管理器
-
SimpleCacheManager
-
NoOpCacheManager
-
ConcurrentMapCacheManager
-
CompositeCacheManager
-
EhCacheCacheManager
-
RedisCacheManager(来自Spring Date Redis项目)
-
GemfireCacheManager(来自Spring Date GemFire项目)
可以看到,在为Spring的缓存抽象选择缓存管理器时,我们有很多可选方案。具体选择哪一个要取决于想要使用的底层缓存供应商。每一个方案都可以为应用提供不同风格的缓存,其中有一些会比其他的更加适用于生产环境。尽管所做出的选择会影响到数据如何缓存,但是Spring声明缓存的方式并没有什么差别。也就是说,无论你用什么缓存管理器,Spring声明缓存的方式是不变的,注解驱动缓存(java配置启动、XML配置启动)和XML配置缓存两种方式。
二、环境搭建
1.Spring配置文件
-
引入ehcache的命名空间
xmlns:cache="http://www.springframework.org/schema/cache"
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
-
添加bean
<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
<cache:annotation-driven cache-manager="ehcacheManager"/>
<!-- cacheManager工厂类,指定ehcache.xml的位置 -->
<bean id="ehcacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:conf/ehcache.xml" />
</bean>
<!-- 声明cacheManager -->
<bean id="ehcacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcacheManagerFactory" />
</bean>
2.ehcache.xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true">
<!--
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-也可以写绝对路径/相对路径
-->
<diskStore path="java.io.tmpdir"/>
<!-- 默认缓存 -->
<defaultCache maxEntriesLocalHeap="10000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- 自定义缓存 -->
<cache name="myCache" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="30" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
<cache name="myCache1" maxEntriesLocalHeap="10000" maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="30" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" transactionalMode="off">
<persistence strategy="localTempSwap"/>
</cache>
</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.spring 注解
3.1@Cacheable
@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。需要注意的是,当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable可以指定三个属性,value、key和condition。
3.1.1 value属性指定Cache的名称
value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache当需要指定多个Cache时其是一个数组。
//Cache是发生在Cache1上的
@Cacheable("Cache1")
public User get(Long id) {
returnnull;
}
//Cache是发生在Cache1和Cache2上的
@Cacheable({"Cache1","Cache2"})
public User get(Long id) {
returnnull;
}
3.1.2 键的生成策略
key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持Spring EL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。
默认策略:
默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
l 如果方法没有参数,则使用0作为key。
l 如果只有一个参数的话则使用该参数作为key。
l 如果参数多余一个的话则使用所有参数的hashCode作为key。
如果我们需要指定自己的默认策略的话,那么我们可以实现自己的KeyGenerator,然后指定我们的Spring Cache使用的KeyGenerator为我们自己定义的KeyGenerator。
1、使用基于注解的配置时是通过cache:annotation-driven指定的。
<cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>
2、使用基于XML配置时是通过cache:advice来指定的。
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>
需要注意的是此时我们所有的Cache使用的Key的默认生成策略都是同一个KeyGenerator。
自定义策略:
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及他们对应的属性,使用方法参数时我们可以直接使用“#参数名”或者“p参数index”。下面时几个使用参数作为key的示例。
@Cacheable(value="userCache",key="#id")
public User get(Long id) {
return null;
}
@Cacheable(value="userCache",key="#p0")
public User get(Long id) {
return null;
}
@Cacheable(value="userCache",key="#user.id")
public User get(User user) {
return null;
}
@Cacheable(value="userCache",key="#p0.id")
public User get(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={"userCache","xxx"},key="caches[1].name")
public User get(User user) {
return null;
}
3.1.3 Condition属性指定发生的条件
当我们不希望缓存一个方法所有的返回结果时,通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过Spring EL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法都会执行一次。如下示例表示只有当user的id为偶数时才进行缓存。
@Cacheable(value="userCache",key="#user.id",condition="#user.id%2==0")
public User get(User user) {
System.out.println("select user from DB.");
returnuserDAO.getAll();
}
3.2 @CachePut
在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。
//每次都会执行方法,并将返回结果存放在指定的缓存中
@CachePut("userCache")
public User get(Long id) {
return null;
}
3.3 @CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面来介绍下属性allEntries和beforeInvocation。
3.3.1 allEntries属性(调用后清除缓存,默认false)
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更为有效。
根据id清除:
/**
* 如果加了allEntries=true 属性,则condition条件无效
* p0:p代表参数,0代表位置,p0代表第一个参数,p1代表第二个参数
* 如果把参数值代入到condition条件中返回的是true,则删除该缓存,反之不删除
* @param id
*/
@CacheEvict(value="myCache",condition = "#p0 == '3'") //根据一定条件清空缓存
public void reload(String id){
}
@CacheEvict(value="userCache",allEntries=true)
public void delete(Long id) {
System.out.println("delete user by id:" + id);
}
3.3.2 beforeInvocation属性(先清空缓存,再进行查询)
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的元素。
根据id清除:
@CacheEvict(value="userCache",beforeInvocation=true)
public void delete(Long id) {
System.out.println("delete user by id:" + id);
}
其实除了使用@CacheEvict清除缓存元素外,当我们使用Ehcache作为实现时,我们也可以配置Ehcache自身的驱除策略,其是通过Ehcache的配置文件来指定的。
3.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 get(Long id) {
return null;
}
以下附代码示例:
1.代码结构
2.
BaseJunitTest.java
package com.maven.test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:conf/spring.xml")
public class BaseJunitTest {
}
MyAccountService.java
package com.maven.ehcache;
import java.util.List;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
@Repository("myAccountService")
public class MyAccountService {
/**
这个注释的意思是,当调用这个方法的时候,会从一个名叫 myCache 的缓存中查询,
如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。
这里的缓存中的 key 就是参数 id,value 就是 Account 对象
在一个参数的情况下,如果指定的key与参数不一致,则会抛出异常
*/
@Cacheable(value="myCache",key="#id")
public Account getAccount_1(String id) {
return DB.getDb1(id);
}
@Cacheable(value="myCache")
public List<Account> getAccount_2() {
return DB.getDb2();
}
@CachePut(value="myCache",key="#account.getId()") //加载缓存,并且会进入方法,key可以不写
public void getAccount_3(Account account){
DB.getDb_3(account);
}
/**
* 如果加了allEntries=true 属性,则condition条件无效
* p0:p代表参数,0代表位置,p0代表第一个参数,p1代表第二个参数
* 如果把参数值代入到condition条件中返回的是true,则删除该缓存,反之不删除
* @param id
*/
@CacheEvict(value="myCache",condition = "#p0 == '3'") //根据一定条件清空缓存
public void reload(String id){
}
}
DB.java(模拟数据库交互)
package com.maven.ehcache;
import java.util.ArrayList;
import java.util.List;
public class DB {
//模拟数据库交互
static Account getDb1(String id){
Account account = new Account();
switch (id) {
case "1":
account.setId("111");
account.setName("111");
account.setScore(111);
break;
case "2":
account.setId("222");
account.setName("222");
account.setScore(222);
break;
default:
account.setId("000");
account.setName("000");
account.setScore(000);
break;
}
System.out.println("数据来源:数据库");
return account;
}
static List<Account> getDb2(){
List<Account> list = new ArrayList<>();
Account account = new Account();
account.setId("111");
account.setName("111");
account.setScore(111);
Account account1 = new Account();
account1.setId("222");
account1.setName("222");
account1.setScore(222);
Account account2 = new Account();
account2.setId("000");
account2.setName("000");
account2.setScore(000);
list.add(account);
list.add(account1);
list.add(account2);
System.out.println("数据来源:数据库");
return list;
}
static void getDb_3(Account account){
System.out.println("保存至数据库");
}
}
Account.java
package com.maven.ehcache;
import java.io.Serializable;
public class Account implements Serializable {
/**
*
*/
private static final long serialVersionUID = 8941837538665634643L;
private String id;
private String name;
private String pwd;
private Integer score;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
EcacheTest.java
package com.maven.test;
import java.util.List;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.maven.ehcache.Account;
import com.maven.ehcache.MyAccountService;
public class EcacheTest extends BaseJunitTest {
@Autowired
private MyAccountService myAccountService;
@Test //单条记录缓存取存
public void test0(){
System.out.println("第一次查询:");
Account account = myAccountService.getAccount_1("1");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
myAccountService.reload("1");
System.out.println("第二次查询");
account = myAccountService.getAccount_1("1");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
System.out.println("第三次查询");
account = myAccountService.getAccount_1("2");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
myAccountService.reload("1");
System.out.println("第四次查询");
account = myAccountService.getAccount_1("2");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
}
//@Test //集合记录缓存取存
public void test1(){
System.out.println("第一次查询:");
List<Account> list = myAccountService.getAccount_2();
System.out.println("第二次查询");
List<Account> list1 = myAccountService.getAccount_2();
for(Account account:list1){
System.out.println(account.getId());
}
}
//@Test
public void test2(){
System.out.println("第一次查询");
Account account = myAccountService.getAccount_1("1");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
account.setId("1111");
account.setName("1111");
account.setScore(1111);
myAccountService.getAccount_3(account);
System.out.println("第二次查询");
account = myAccountService.getAccount_1("1");
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getScore());
}
}