一、什么是緩存 Cache
Cache 一詞最早來自于CPU設(shè)計
當(dāng)CPU要讀取一個數(shù)據(jù)時,首先從CPU緩存中查找,找到就立即讀取并送給CPU處理;沒有找到,就從速率相對較慢的內(nèi)存中讀取并送給CPU處理,同時把這個數(shù)據(jù)所在的數(shù)據(jù)塊調(diào)入緩存中,可以使得以后對整塊數(shù)據(jù)的讀取都從緩存中進行,不必再調(diào)用內(nèi)存。正是這樣的讀取機制使CPU讀取緩存的命中率非常高(大多數(shù)CPU可達90%左右),也就是說CPU下一次要讀取的數(shù)據(jù)90%都在CPU緩存中,只有大約10%需要從內(nèi)存讀取。這大大節(jié)省了CPU直接讀取內(nèi)存的時間,也使CPU讀取數(shù)據(jù)時基本無需等待。總的來說,CPU讀取數(shù)據(jù)的順序是先緩存后內(nèi)存。
再到后來,出先了硬盤緩存,然后到應(yīng)用緩存,瀏覽器緩存,Web緩存,等等!
緩存為王!!
Spring Cache
Spring Cache是Spring針對Spring應(yīng)用,給出的一整套應(yīng)用緩存解決方案。
Spring Cache本身并不提供緩存實現(xiàn),而是通過統(tǒng)一的接口和代碼規(guī)范,配置、注解等使你可以在Spring應(yīng)用中使用各種Cache,而不用太關(guān)心Cache的細節(jié)。通過Spring Cache ,你可以方便的使用
各種緩存實現(xiàn),包括ConcurrentMap,Ehcache 2.x,JCache,Redis等。
Spring中Cache的定義
Sping 中關(guān)于緩存的定義,包括在接口 org.springframework.cache.Cache 中,
它主要提供了如下方法
1
2
3
4
5
6
|
// 根據(jù)指定key獲取值 <T> T get(Object key, Class<T> type) // 將指定的值,根據(jù)相應(yīng)的key,保存到緩存中 void put(Object key, Object value); // 根據(jù)鍵,回收指定的值 void evict(Object key) |
從定義中不難看著,Cache 事實上就是一個key-value的結(jié)構(gòu),我們通過個指定的key來操作相應(yīng)的value
Cache Manager
Cache是key-value的集合,但我們在項目中,可能會存在各種業(yè)務(wù)主題的不同的Cache,比如用戶的cache,部門的Cache等,這些cache在邏輯上是分開的。為了區(qū)分這些Cache,提供了org.springframework.cache.CacheManager用來管理各種Cache.該接口只包含兩個方法
1
2
3
4
|
// 根據(jù)名字獲取對應(yīng)主題的緩存 Cache getCache(String name); // 獲取所有主題的緩存 Collection<String> getCacheNames(); |
在該接口中,不允許對Cache進行增加、刪除操作,這些操作應(yīng)該在各種CacheManager實現(xiàn)的內(nèi)部完成,而不應(yīng)該公開出來。
基于注解的緩存
對數(shù)據(jù)的緩存操作,理論上和業(yè)務(wù)本身的相關(guān)性不大,我們應(yīng)當(dāng)把對Cache的讀寫操作從主要代碼邏輯中分離出來。Spring分離的方式就是基于注解的(當(dāng)然像JSR-107等也是基于注解的)。
Spring 提供了一系列的注解,包括@Cacheable,@CachePut,@CacheEvict等一系列注解來簡化我們對緩存的操做,這些注解都位于org.springframework.cache.annotation包下。
二、例子
一個簡單的Spring Boot使用Spring Cache的例子
我們一步一步,來構(gòu)建一個基于Spring Boot Cache的例子
新建一個Spring Boot 項目,引入如下依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> |
其中,spring-boot-starter-cache是cache關(guān)鍵依賴。
修改Application類,加入啟用緩存的注解@EnableCaching
1
2
3
4
5
6
7
|
@SpringBootApplication @EnableCaching public class CacheSimpleApplication { public static void main(String[] args) { SpringApplication.run(CacheSimpleApplication. class , args); } } |
@EnableCache注解啟動了Spring的緩存機制,它會使應(yīng)用檢測所有緩存相關(guān)的注解并開始工作,同時還會創(chuàng)建一個CacheManager的bean,可以被我們的應(yīng)用注入使用。
新建一個RestController類
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
@RestController @RequestMapping ( "/" ) public class CacheController { @Autowired private CacheTestService cacheTestService; /** * 根據(jù)ID獲取信息 * * @param id * @return */ @GetMapping ( "{id}" ) public String test( @PathVariable ( "id" ) String id) { return cacheTestService.get(id); } /** * 刪除某個ID的信息 * * @param id * @return */ @DeleteMapping ( "{id}" ) public String delete( @PathVariable ( "id" ) String id) { return cacheTestService.delete(id); } /** * 保存某個ID的信息 * * @param id * @return */ @PostMapping public String save( @RequestParam ( "id" ) String id, @RequestParam ( "value" ) String value) { return cacheTestService.save(id, value); } /** * 跟新某個ID的信息 * * @param id * @return */ @PutMapping ( "{id}" ) public String update( @PathVariable ( "id" ) String id, @RequestParam ( "value" ) String value) { return cacheTestService.update(id, value); } } |
該類調(diào)用某個Service來實現(xiàn)實際的增刪改查操作。
Service 實現(xiàn)
接下來,我們要實現(xiàn)我們的Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
@Service public class SimpleCacheTestServiceImpl implements CacheTestService { private static final Logger logger = LoggerFactory.getLogger(SimpleCacheTestServiceImpl. class ); private final Map<String, String> enties = new HashMap<>(); public SimpleCacheTestServiceImpl() { enties.put( "1" , "this no 1" ); } @Autowired private CacheManager cacheManager; @Override @Cacheable (cacheNames = "test" ) public String get(String id) { // 記錄數(shù)據(jù)產(chǎn)生的時間,用于測試對比 long time = new Date().getTime(); // 打印使用到的cacheManager logger.info( "The cacheManager is" + cacheManager); // 當(dāng)數(shù)據(jù)不是從cache里面獲取時,打印日志 logger.info( "Get value by id=" + id + ", The time is " + time); return "Get value by id=" + id + ",the value is" + enties.get(id); } @Override public String delete(String id) { return enties.remove(id); } @Override public String save(String id, String value) { logger.info( "save value " + value + " with key " + id); enties.put(id, value); return value; } @Override public String update(String id, String value) { return enties.put(id, value); } } |
緩存
首先在get方法上加上 @Cacheable 注解,運行代碼測試。
我們使用postman做測試,測試地址為 http://localhost:8080/1 ,瀏覽器響應(yīng)Get value by id=1,the value isthis no 1,服務(wù)器控制臺打印兩行日志
1
2
|
Get value by id= 1 ,the value isthis no 1 Get value by id= 1 , The time is 1516004770216 |
但我們再次刷新瀏覽器地址時,瀏覽器正常返回,但控制臺已經(jīng)不再打印了,原因是第二次調(diào)用時,Spring 已經(jīng)不再執(zhí)行方法,而是直接獲取緩存的值。 Spring Cache將函數(shù)的返回值以函數(shù)參數(shù)為key,緩存在了名為test的緩存中 。
這里我們使用了 @Cacheable 注解,注解中的cacheNames指定了這里讀取的是哪個Cache。這里會在cacheName="test"的cache中去查找key是id的緩存對象。
刪除緩存中的數(shù)據(jù)
在上面的程序中,如果我們通過delete請求刪除指定值,發(fā)送delete請求到 http://localhost:8080/1 ,這個時候,值已經(jīng)從map中刪除了,但我們get 請求到 http://localhost:8080/1 的時候,仍然可以拿到值,這是因為我們在刪除數(shù)據(jù)的時候,沒有刪除緩存中的數(shù)據(jù),而在前面的get方法中,方法的運行結(jié)果仍然被保存著,Spring不會去重新讀取,而是直接讀取緩存。這個時候,我們在方法前面加上注解
1
2
3
4
5
|
@Override @CacheEvict (cacheNames = "test" ) public String delete(String id) { return enties.remove(id); } |
先后測試,首先調(diào)用get請求,會正確顯示返回值為Get value by id=1,the value is 1
然后調(diào)用delete請求。將數(shù)據(jù)從cache和map中刪除,再次調(diào)用get請求,這時返回Get value by id=1,the value is null,代表該值確實從緩存中刪除了。
這里我們使用了 @CacheEvict 注解,cacheNames指定了刪除哪個cache中的數(shù)據(jù), 默認會使用方法的參數(shù)作為刪除的key
更新緩存
當(dāng)程序到這里時,如果我們運行post請求,請求體為 id=1&value=new1,這時控制臺打印save value new value1 with key 1,代碼會將值保存到map中,但我們運行g(shù)et請求時,會發(fā)現(xiàn)返回值仍然是之前的狀態(tài)。這是我們可以使用
1
2
3
4
5
6
|
@Override @CachePut (cacheNames = "test" , key = "#id" ) public String save(String id, String value) { logger.info( "save value " + value + " with key " + id); return enties.put(id, value); } |
重新執(zhí)行代碼,我們先發(fā)送delete請求,從map和和cache中刪除數(shù)據(jù)。然后再發(fā)送post請求,寫入數(shù)據(jù)到map中。最后再發(fā)送get請求,會發(fā)現(xiàn)現(xiàn)在可以正確的取到值了,控制臺也沒有打印從map中獲取數(shù)據(jù)的日志。
這里用到了 @CachePut 注解,這個注解的作用是 將方法的返回值按照給定的key,寫入到cacheNames指定的cache中去 。
同樣,我們需要再put方法上加上 @CachePut 注解,讓修改也能刷新緩存中的數(shù)據(jù)。
到這里,一個簡單的包含增刪改查的緩存應(yīng)用就完成了。
三、劃重點
幾個注解
- @EnableCaching 啟用緩存配置
- @Cacheable 指定某個方法的返回值是可以緩存的。在注解屬性中指定緩存規(guī)則。
- @Cacheput 將方法的返回值緩存到指定的key中
- @CacheEvict 刪除指定的緩存數(shù)據(jù)
注意
@Cacheable和@Cacheput都會將方法的執(zhí)行結(jié)果按指定的key放到緩存中,@Cacheable在執(zhí)行時,會先檢測緩存中是否有數(shù)據(jù)存在,如果有,直接從緩存中讀取。如果沒有,執(zhí)行方法,將返回值放入緩存,而@Cacheput會先執(zhí)行方法,然后再將執(zhí)行結(jié)果寫入緩存。 使用@Cacheput的方法一定會執(zhí)行
完整的示例代碼在 https://github.com/ldwqh0/cache-test
總結(jié)
以上所述是小編給大家介紹的Spring Boot 中使用cache緩存的方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對服務(wù)器之家網(wǎng)站的支持!
原文鏈接:https://www.jianshu.com/p/fc076b6c2a13?