一、緩存有什么用?
緩存應該是我們每一個系統都應該考慮使用的,這樣可以加速系統的訪問,提升系統的性能。比如我們經常需要訪問的高頻數據,將此類數據放在緩存中,可以大幅度提升系統的響應速度。原因就是一般來說我們的數據都是存在數據庫中,但是高頻的訪問數據庫不僅會對數據庫造成壓力,一定程度上還會影響響應速度,影響用戶體驗。如果引入了緩存,不僅能提升訪問性能的同時降低數據庫的壓力。
二、JSR107規范
JSR是Java Specification Requests的縮寫,意思是Java 規范提案。是指向JCP(Java Community Process)提出新增一個標准化技術規范的正式請求。任何人都可以提交JSR,以向Java平台增添新的API和服務。JSR已成為Java界的一個重要標准。
規范:
JCache規范定義了一種對Java對象臨時在內存中進行緩存的方法,包括對象的創建、共享訪問、假脫機(spooling)、失效、各JVM的一致性等,可被用於緩存JSP內最經常讀取的數據,如產品目錄和價格列表。利用JCACHE,多數查詢的反應時間會因為有緩存的數據而加快(內部測試表明反應時間大約快15倍)。
Java Caching定義了5個核心接口,分別是CachingProvider, CacheManager, Cache, Entry 和 Expiry。其中CacheProvider就是緩存提供器,其中包含了多個緩存管理器(CacheManager),緩存管理器之下才是我們需要用到的緩存(Cache),緩存的形式就是鍵值對,即entry對象(Entry),每一個entry對象會對應一個Expiry,即可以對每一條緩存數據設置有效期。
上面都是抄過來的廢話,看下面,實際操作一下。
三、Spring緩存抽象
Spring 3.1開始定義了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口來統一不同的緩存技術;並支持使用JCache(JSR-107)注解簡化我們開發。SpringBoot對緩存整合提供了很好的支持,支持不同的緩存規范(所謂的規范就是相當於面向接口編程,不同的緩存有不同的實現)。
其中Cache接口為緩存的組件規范定義,包含緩存的各種操作集合;
Cache接口下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache , ConcurrentMapCache等;下面我們來看看,SpringBoot中默認的一些緩存配置與實現。默認的是SimpleCache。
四、SpringBoot中的緩存-注解開發
新建SpringBoot web項目,加入依賴配置:SpringBoot版本為2.2.6.RELEASE 庫存文章了,版本是去年的。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然后我們逐個介紹一下緩存開發中用到的一些注解。
- @EnableCaching
- @Cacheable
- @CachePut
- @CacheEvict
- @Caching
- @CacheConfig
1.
@EnableCaching
這個注解應該是最先了解的,把它放在啟動類上面,表示開啟基於注解的緩存,然后你用那些緩存相關的注解就能被Spring容器掃描到。這個注解不能缺,不然項目會報錯。
@EnableCaching
@SpringBootApplication
public class DemoCacheApplication {
public static void main(String[] args) {
SpringApplication.run(DemoCacheApplication.class, args);
}
}
2.
@Cacheable
這個注解有很多的屬性,可以定義一些緩存的規則。主要是用於方法之上,將方法的結果作為緩存的值,默認情況下將方法的參數作為緩存的鍵(key),也就是key-value的形式存儲數據。首先介紹一下這個注解的運行機制:首先在方法運行之前,先根據注解配置的value或者是cacheNames到cacheManage中去查詢緩存組件即Cache,當然如果第一次沒有這個緩存就會自動創建。獲取到緩存組件之后就用我們設置的key屬性,或者是keyGenerator策略生成的key去查詢鍵對應的緩存值,對應上面的entry對象。如果查到了值就返回給請求客戶端,如果沒有查到這個值就調用方法,然后將方法返回的結果緩存起來。
這里我們來說一說這個注解里面的一些屬性的用法:
- value/cacheNames:這兩個屬性是一樣的,都是指定一個緩存組件的名字,當然這個可以指定多個,是一個數組。
- key/keyGenerator:這兩個有一定的區別,但是用的時候只需要用其中一個就行了。key是我們可以直接指定這個緩存結果對應的key是什么,到時候直接去Cache里面查就行了,keyGenerator就是可以自定義一個key的生成策略,然后生成key,兩者從結果上來看都是生成了key,所以兩者只需要用其中一個就OK了。
- condition:在符合這個條件下才進行緩存
- unless:當這個條件為假的時候才進行緩存,與condition相反
當然還有cacheManager(緩存管理器)以及cacheResolver(緩存解析器),這兩個都可以自己定義。兩個都差不多,二者用其中一個就OK。
下面我們來看看SpEl寫法,因為我們的key或者是condition,或者是unless屬性都會用到這個寫法。
來自官方的spel 語法
Name | Location | Description | Example |
---|---|---|---|
methodName |
Root object | 被調用方法的名稱 | #root.methodName |
method |
Root object | 被調用的方法 | #root.method.name |
target |
Root object | 被調用的目標對象 | #root.target |
targetClass |
Root object | 被調用目標的類 | #root.targetClass |
args |
Root object | 用於調用目標的參數(作為數組) | #root.args[0] |
caches |
Root object | 運行當前方法的緩存集合 | #root.caches[0].name |
Argument name | Evaluation context | 任何方法參數的名稱。如果名稱不可用(可能是由於沒有調試信息),則參數名稱也可在代表參數索引的#a<#arg> where下#arg (從 開始0 )。 |
#iban or #a0 (you can also use #p0 or #p<#arg> notation as an alias). |
result |
Evaluation context | 方法調用的結果(要緩存的值)。僅在unless 表達式、cache put 表達式(用於計算key )或cache evict 表達式(何時beforeInvocation 是false )中可用。對於支持的包裝器(例如 Optional ),#result 指的是實際對象,而不是包裝器。 |
#result |
具體使用如下:
@GetMapping("/cacheAble")
@Cacheable(value = "cache",key = "#root.args[0]",condition = "#id>20")
public Student cacheAble(@RequestParam Integer id) {
Student student = (Student) hashMap.get(id);
System.out.println("第一次調用了方法,添加緩存");
return student;
}
上面就相當於是將第一個方法參數id作為緩存的key ,返回值Student 作為值。緩存的名稱就是cache,設置在參數id大於20的時候才進行緩存,unless 則是相反的。
3.@CachePut
這個注解是用來更新緩存的,一般情況下都是用在更新數據的接口上,示例如下:
@PostMapping("/putCache")
@CachePut(value = "cache",key = "#result.id")
public Student putCache(@RequestBody Student student) {
hashMap.replace(student.getId(),student);
System.out.println("調用了方法,也更新了緩存");
return student;
}
4.@CacheEvict
刪除緩存的注解,可以設置刪除全部緩存還是刪除部分緩存數據。如下
@CacheEvict(value = "cache",key = "#id",beforeInvocation = true,allEntries = false)
@GetMapping("/cacheEvict")
public void cacheEvict(@RequestParam Integer id) {
hashMap.remove(id);
System.out.println("緩存刪除");
}
beforeInvocation
設置為true ,表示在方法執行之前進行緩存刪除,默認為false,``allEntries`設置為true ,表示刪除這個緩存名稱下面的所有緩存。
5.@Caching
借助官方的話說,有時,需要指定多個相同類型的注解(例如@CacheEvict
或 @CachePut
)——例如,因為不同緩存之間的條件或鍵表達式不同。@Caching
允許在同一方法上使用多個嵌套的 @Cacheable
、@CachePut
和@CacheEvict
注釋。實際上就是,各個緩存注解可以配合使用,操作不同的緩存空間。
@Caching(
cacheable = {
@Cacheable(value = "cache",key = "#id"),
@Cacheable(value = "cache1",key = "#id")
},
evict = {@CacheEvict(value = "cache2",key = "#id")}
)
@GetMapping("/caching")
public Student caching(@RequestParam Integer id) {
Student student = (Student)hashMap.get(id);
System.out.println("新增緩存了,也刪除了緩存,但是操作的cache不一樣");
return student;
}
代碼實現的就是將數據緩存到cache,cache1,再將cache2的緩存刪除掉。
6.
@CacheConfig
這是一個類級別的緩存配置注解,像緩存名稱,緩存處理器(cacheResolver),緩存管理器(cacheManager),這些配置可以直接配置到類上面,這樣方法對應的就能少些一些重復代碼。
//注解的四個屬性
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
示例: 在類上面,加上注解,配置好對應的緩存名稱,key生成策略等,keyGenerator,cacheManager,cacheResolver這三個東西留着下篇文章將redis緩存的時候講,主要是比較多。
@CacheConfig(cacheNames = "chache")
public class CacheController {}
確實,我們的緩存還能從cacheManager中獲取,這很好理解,cacheManager是管理緩存的,當然也能獲取緩存了。
@GetMapping("/get")
public Student getCache(@RequestParam Integer id){
Cache cache = cacheManager.getCache("cache");
Cache.ValueWrapper student = cache.get(id);
Object o = student.get();
return (Student) o;
}
由於SpringBoot的良好擴展性,CacheManager,CacheResolver,
KeyGenerator都可以自己配置,具體的下一篇文章會介紹。