一、概述
SpringCache
本身是一個緩存體系的抽象實現,並沒有具體的緩存能力,要使用SpringCache
還需要配合具體的緩存實現來完成。
雖然如此,但是SpringCache是所有Spring支持的緩存結構的基礎
,而且所有的緩存的使用最後都要歸結於SpringCache。
它可以將方法的運行結果
進行緩存;以後再要相同的數據,直接從緩存中獲取,不用調用方法;
二、緩存註解
SpringCache緩存功能的實現是依靠下面的這幾個註解完成的。
- @EnableCaching:開啓緩存功能
- @Cacheable:定義緩存,用於觸發緩存
- @CachePut:定義更新緩存,觸發緩存更新
- @CacheEvict:定義清除緩存,觸發緩存清除
- @Caching:組合定義多種緩存功能
- @CacheConfig:定義公共設置,位於
class
之上
1、@CacheConfig
該註解標註於類
之上,用於配置該類中會用到的一些公共的緩存
相關配置。
@CacheConfig(cacheNames = “pers.liuchengyin.service.blogServiceImpl”)
cachNames
的值pers.liuchengyin.service.blogServiceImpl
就是這個類公共的緩存對象
,就相當於一個名爲pers.liuchengyin.service.blogServiceImpl
的Map
對象,下文簡稱對象
2、@Cacheable
用在查詢的方法
上,方法的返回值將被加入緩存
。該註解標註的方法每次被調用前都會觸發緩存校驗
,校驗指定參數的緩存
是否已存在(已發生過相同參數的調用),若存在,直接返回緩存結果,否則執行方法內容,最後將方法執行結果保存到緩存中。
該註解有這些參數:value
、cacheNames
、key
、condition
、unless
、keyGenerator
、cacheManager
、cacheResolver
①value
、cacheNames
是兩個同等的參數。cacheNames是Spring4新增的,作爲value
的別名,用於指定緩存對象
,不是必須指定的。如果不指定則使用類
上面配置的CacheConfig
配置的cacheNames
。
②key
就是緩存對象存儲在Map
集合中的key
值,不是必需,缺省
按照方法的所有參數
組合作 爲key
值, 可以自己配置,但需要SpEL
表達式。比如
@Cacheable(key = "#p0") // 表示第一個參數作爲緩存的key值
getBlog(Integer id, String name){}
@Cacheable(key = "#id") // 也可以用參數名作爲緩存的key值,這裏的參數是id
getBlog(Integer id, String name){}
@Cacheable(key = "#user.id") // 甚至可以用對象裏的某個屬性作爲key值
getBlog(Integer id, User user){}
③condition
緩存對象的條件,非必需,也需使用SpEL
表達式,只有滿足表達式條件的內容纔會被緩存。比如
@Cacheable(key = "#p0",condition = "#username.lenth < 3")
getBlog(Integer id, String username){} // 只有第二個參數長度小於3的時候才緩存進去
④unless
與condition
相似,但unless
是方法被調用之後才做判斷的,所以可以對結果result
進行判斷是否去緩存。
⑤keyGenerator
用於指定key 生成器
,非必需。
若需要指定一個自定義的key生成器,我們需要去實現 org.springframework.cache.interceptor.KeyGenerator
接口,並使用該參數來指定
⑥cacheManager
用於指定使用哪個緩存管理器
,非必需。只有當有多個緩存管理器時才需要使用。
⑦cacheResolver
用於指定使用那個緩存解析器
,非必需。需通過 org.springframework.cache.interceptor.CacheResolver
接口來實現自己的緩存解析器,並用該參數指定。
3、@CachePut
用於更新緩存
,無論結果
是否已經緩存,都會在方法執行結束插入緩存,相當於更新緩存,一般用於更新方法
和新增方法
之上。其參數與@Cacheable
類似。
4、@CacheEvict
配置於方法上,通常用在刪除
方法上,用來從緩存中移除相應數據。 除了同@Cacheable
一樣的參數之外,它還有下面兩個參數:
①allEntries
,非必需,默認爲false
。當爲true
時,會移除對應緩存對象的所有緩存數據,也就是移除cacheName
或value
指定的對象的緩存數據。
②beforeInvocation
,非必需, 默認爲false
,會在調用方法之後
移除數據。 當爲 true
時,會在調用方法之前
移除數據。
5、@Caching
配置於方法上,用於定義複雜的緩存規則,比如
@Caching(
cacheable={
@Cacheable(key = "#name")
},
put={
@CachePut("#result.id"),
@CachePut("#result.email")
}
)
Blog getByBlogNmae(String name){}
6、小結
一般來說,我們都在Service
實現層的類上面使用@CacheConfig
來指定公共緩存配置,在查詢
的方法上使用@Cacheable
,在新增/修改
的方法上使用@CachePut
,在刪除
的方法上使用@CacheEvict
。
三、原理
SpringBoot中的自動配置類CacheAutoConfiguration
中的緩存配置類有如下幾種:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
其中,SimpleCacheConfiguration
是默認生效的。它的作用就是給容器中註冊一個CacheManager
:ConcurrentMapCacheManager。這個Manager可以獲取和創建ConcurrentMapCache
類型的組件,可以將數據保存到ConcurrenMap
中。
CacheManager
緩存管理器,它是來管理多個Cache
組件的,對於緩存的CRUD操作實際上是在Cache
組件中完成的。前面所說的cacheName/value
就是每一個Cache
組件的名字。key
也就是緩存的數據,也是就是說Cache
組件裏是Key-value鍵值對。這些鍵值對是以數組的形式存儲在它們所對應的Cache
組件裏的。這個Key
和前面所說的keyGenerator
只需選擇一個就行,一個是我們指定key
,一個是根據生成器自動生成。
四、運行流程
①@Cacheable
1、方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定的名字獲取。
CacheManager先獲取相應的緩存,第一次獲取緩存如果沒有Cache組件會自動創建。
2、去Cache中查找緩存的內容
3、沒有查到緩存就調用目標方法
4、將目標方法返回的結果返回並放進緩存中
5、下一次來調用,因爲有這個緩存值,就不會再調用目標方法,直接返回緩存中的值
②@CachePut
1、先調用目標方法 - 這裏面就可能涉及數據庫的數據修改
2、將目標方法的結果緩存起來(也就是覆蓋老的緩存)
該註解所指定的Cache
組件需要與對應@Cacheable
的一致,不然就起不到修改的作用。
五、使用示例
1、引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2、配置ehcache.xml
ehcache.xml是緩存的配置文件,默認在resources下,可以通過yml配置文件修改其讀取位置。
spring:
cache:
ehcache:
config:claspath:config/ehcache_config.xml # config文件夾下的echache_config.xml
這裏我們就使用默認的
<ehcache>
<diskStore path="java.io.tmpdir/cache"/>
<!-- 默認緩存策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!--
自定義緩存策略
name:緩存名稱
maxElementsInMemory:緩存最大個數,0表示無窮大
eternal:緩存對象是否永久有效,如果爲true,忽略timeToIdleSeconds、timeToLiveSeconds
timeToIdleSeconds:允許對象處於空閒狀態的最長時間,以秒爲單位
timeToLiveSeconds:對象允許存在於緩存中的最長時間,以秒爲單位
overflowToDisk:true表示當內存緩存的對象數目達到了maxElementsInMemory界限後,會把溢出的對象寫到硬盤緩存中,對象需實現序列化接口
diskPersistent:是否緩存虛擬機重啓期數據,是否持久化磁盤緩存
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,以秒爲單位
-->
<cache name="student_cache"
maxElementsInMemory="10000"
eternal="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"
/>
</ehcache>
3、創建POJO類
@Data
public class Student implements Serializable {
/** ID */
private Long id;
/** 姓名 */
private String name;
/** 性別 */
private String sex;
/** 班級 */
protected String classGrade;
/** 入學日期 */
private Date admissionDate;
}
4、創建Service類
@Service
@CacheConfig(cacheNames = "student_cache") // 緩存名
public class StudentServiceImpl implements StudentService {
@Override
@Cacheable // 默認以參數組合爲Key
public Student getStudentById(Long id) {
System.out.println("直接獲取學生信息:");
Student student = new Student();
student.setId(1L);
student.setName("柳成蔭");
student.setSex("男");
student.setClassGrade("21班");
student.setAdmissionDate(new Date());
return student;
}
@Override
@CachePut(key = "#student.id") // 指定對象的Id爲Key
public Student updateStudent(Student student) {
System.out.println("修改學生信息:");
student.setName("九月清晨");
return student;
}
@Override
@CacheEvict(key = "#id") // 刪除Id爲Key的緩存
public void deleteStudentById(Long id) {
System.out.println("刪除學生信息:");
}
}
5、測試
爲了方便測試,直接使用SpringBootTest來進行測試,直接調用Service。
@RunWith(SpringRunner.class)
@SpringBootTest
public class DoTest {
@Autowired
private StudentService studentService;
@Test
public void test1(){
// 調用查詢信息
Student student = studentService.getStudentById(1L);
System.out.println(student);
System.out.println("--------------------分割線-----------------------");
// 調用查詢信息
Student student1 = studentService.getStudentById(1L);
System.out.println(student1);
System.out.println("--------------------分割線-----------------------");
// 修改學生信息
Student student2 = studentService.updateStudent(student1);
System.out.println(student2);
System.out.println("--------------------分割線-----------------------");
// 調用查詢信息
Student student3 = studentService.getStudentById(1L);
System.out.println(student3);
System.out.println("--------------------分割線-----------------------");
// 刪除學生信息
studentService.deleteStudentById(1L);
System.out.println("--------------------分割線-----------------------");
// 調用查詢信息
Student student4 = studentService.getStudentById(1L);
System.out.println(student4);
}
}
6、運行結果分析
從運行截圖和Service裏的方法來看:
1、第一次查詢時,發現是調用了方法的
2、第二次查詢時,沒有出現直接獲取學生信息
這一句,說明是從緩存中取的結果
3、接着,調用修改學生信息的方法,name
被修改成九月清晨
4、第三次查詢時,發現name
變成了九月清晨
,依舊是從緩存中獲取的,說明修改了緩存
5、接着,調用刪除學生信息的方法
6、第四次查詢時,出現直接獲取學生信息
這一句,並且name
變爲柳成蔭
,說明刪除緩存了,直接調用方法獲取的結果
六、自定義緩存Key
Spring提供了一個root對象生成Key,如下圖:
除此之外,我們也可也自定義緩存Key的生成器KeyGenerator
,如下
@Component
public class MyKeyGenerator implements KeyGenerator {
/**
* 自定義生成Key
* @param target 當前對象
* @param method 當前請求的方法
* @param params 方法的參數
* @return
*/
@Override
public Object generate(Object target, Method method, Object... params) {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
String args = "";
for (Object param : params) {
if (param instanceof Long){
args += param;
}else if(param instanceof Student){
Student student = (Student) param;
args += student.getName();
}
}
// 這只是個示例,根據自己需求進行返回Key
return className + methodName + args;
}
}
在使用的時候,我們只需要指定生成策略即可:
@Autowired
private MyKeyGenerator myKeyGenerator;
@Cacheable(keyGenerator = "myKeyGenerator")
public void getStudent(Long id, Student student){
System.out.println("....");
}