文章目錄
注:如果您發現任何不正確的內容,或者您想要分享有關本文主題的更多信息,請撰寫評論或聯繫我 [email protected]。
以下環境基於 windows 10 java version “1.8.0_171” 代碼編輯器爲idea
文中代碼可能不太嚴謹,僅供研究測試使用!
背景
在編寫後臺項目過程中,遇到後臺某個頁面會檢索大量數據到頁面,導致頁面反應速度變慢,且這些數據屬於變動不頻繁的(很少有刪、改、增之類的操作),爲了更好的提供服務,以最快的速度展示數據,所以想到了添加緩存。
項目使用的是springBoot,就就怎麼方便怎麼來吧。用spring官方的總比自己寫的能硬、更堅挺、更持久!
Cache Abstraction,自spring3.1開始支持像開啓事務那樣非侵入的添加緩存。spring原話如下
從3.1版開始,Spring框架就支持向現有Spring應用程序透明地添加緩存。與事務支持類似,緩存抽象允許一致地使用各種緩存解決方案,對代碼的影響最小。
從Spring 4.1開始,在JSR-107註釋和更多定製選項的支持下,緩存抽象得到了顯著擴展。
是什麼?爲什麼使用它
這塊解釋的內容有點照抄spring,不過都是爲了儘快學習掌握。
緩衝和緩存是什麼鬼
緩衝
緩衝區就像你在網上看小視頻,比如pornhub之類的,視頻需要加載速度,你看的是4K的。而你又很猴急,
4K,是一種高清顯示技術。主要應用於電視行業、電影行業、手機行業等。作爲電視行業顯示技術的革命性突破,4K已經成爲行業內的常青樹,熱度從2012年開始就一直是有增無減。畫質技術作爲電視的核心要素,與3D、多屏互動等技術相比,畫質技術給人們帶來的不是一時新鮮感,它是從本質上提升電視的表現力,讓用戶能夠感受到最優秀的畫質所帶來的視覺盛宴。
這個時候,如果沒有緩存的話,網站加載一幀你看一幀。這對於看小片快進的你來說很難受,但是有了緩存之後(還有各種技術),網站會直接加載一分鐘(對於你來說是直接看到有十分鐘,其實還是一幀一幀,攢到十幀纔給你看),你就看的很爽了,直接就完事了。可能解釋的不太好。。。看看spring的解釋
術語“緩衝區”和“緩存”往往可以互換使用。但是請注意,它們代表不同的東西。傳統上,緩衝區用作數據在快實體和慢實體之間的中間臨時存儲。由於一方必須等待另一方(這會影響性能),緩衝區允許整個數據塊(而不是小塊)同時移動,從而緩解了這種情況。數據只從緩衝區寫入和讀取一次。此外,至少有一方知道緩衝區是可見的。
另一方面,緩存根據定義是隱藏的,並且任何一方都不知道緩存的發生。它還提高了性能,但是通過讓相同的數據以快速方式多次讀取來提高性能。
抽象
抽象緩存 是基於方法的,會保存方法的入參以及其結果,如果下次有相同的請求過來直接返回已經存儲的結果(在沒有清空緩存的情況下)。
緩存抽象的核心是將緩存應用於Java方法,從而根據緩存中可用的信息減少執行的次數。也就是說,每次調用目標方法時,抽象都會應用緩存行爲,檢查方法是否已經爲給定的參數執行。如果已執行,則返回緩存的結果,而不必執行實際的方法。如果方法尚未執行,則執行該方法,並緩存結果並返回給用戶,以便在下次調用該方法時返回緩存的結果。這樣,對於給定的一組參數,昂貴的方法(無論是CPU綁定的還是io綁定的)只能執行一次,並且結果可以重用,而不必實際再次執行該方法。緩存邏輯被透明地應用,沒有任何對調用程序的干擾。
這種方法只適用於保證爲給定輸入(或參數)返回相同輸出(結果)的方法,無論執行了多少次。
簡單理解了緩存接下來進入正題。
什麼是ehcache
Ehcache是一種基於標準的開源緩存,可提高性能,卸載數據庫並簡化可伸縮性。它是使用最廣泛的基於Java的緩存,因爲它功能強大,經過驗證,功能齊全,並與其他流行的庫和框架集成。Ehcache可以從進程內緩存擴展到使用TB級緩存的混合進程內/進程外部署。
爲什麼使用ehcahe
如果要你自己實現緩存你會如何選擇,創建個自定義註解然後aop監聽,將查詢入參和結果放入ConcurrentHashMap抑或是redis,在對數據的update、insert、delete時刪除緩存。你能想到的這些早有人做出來了,能用已經成熟的東西幹嘛非要造輪子。
至於爲啥用ehcache,我順手行不,用redis還得搭建redis,麻煩。。。。
怎麼樣
spring提供緩存功能,如果還沒有定義類型爲CacheManager的bean 或名爲CacheResolver的CacheResolver的CacheResolver。Spring Boot嘗試檢測以下提供者(按照指定的順序)
Generic
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Simple
-
Generic
-
JCache
-
EhCache 2.x
-
Hazelcast
-
Infinispan
-
Couchbase
-
Redis
-
Caffeine
-
Simple
如果找不到其他提供程序,則配置一個使用ConcurrentHashMap作爲緩存存儲的簡單實現。如果應用程序中沒有緩存庫,這是默認設置 。至於怎麼默認找到這個 SimpleCacheConfiguration 想要了解原理,你首先得搞明白springBoot 是如何自動配置的,現階段知道是默認就行。有興趣可以看看 org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration#cacheManager
我們先使用默認的 cache。 使用idea直接創建springBoot腳手架,並引入pom依賴
- spring-boot-starter-cache
使用spring cache緩存
- 我的pom依賴如下,或者你可以去maven倉庫找最新版本的ehcache。這個demo將會先做一個簡單的演示,然後在進行深入探討(先知道這玩意兒怎麼用,達到什麼效果,在解釋怎麼用,爲什麼這樣用)。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>
- 在 DemoApplication.java 類上加註解@EnableCaching 聲明啓用緩存。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching // 開啓基於註解的緩存
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 新建一個簡單的測試controller TestController.java(與 DemoApplication.java 同包,或者在其包包含範圍內)
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController // @RequestMapping 和 @ResponseBody
public class TestController {
@RequestMapping("/{demoInt}")
public String test(@PathVariable Integer demoInt) { // 接受路徑的參數做乘法運算
System.out.println("我要運算了~~~");
return String.valueOf(demoInt * 666);
}
}
- 啓動項目。瀏覽器訪問 http://127.0.0.1:8080/2 項目可以正常運行,接下來加入緩存。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController
public class TestController {
@RequestMapping("/{demoInt}")
// @Cacheable註解會先查詢是否已經有緩存,有會使用緩存,沒有則會執行方法並緩存。
@Cacheable(value = "operation", // 緩存的名稱,在 spring配置文件中定義,必須指定至少一個
key = "#demoInt") // 緩存的 key,可以爲空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合。
public String test(@PathVariable Integer demoInt) {
System.out.println("我要運算了~~~");
return String.valueOf(demoInt * 666);
}
// 此處的入參一定要實現 java.io.Serializable 。 Integer類 繼承抽象類 Number。 Number實現 Serializable
- 重新啓動項目,第一次訪問,瀏覽器打印
我要運算了~~~
未重啓,刷新頁面第二次訪問,控制檯沒有任何輸出。這就說明緩存成功。
使用ehcache 3 緩存
-
修改pom,添加ehcache座標,這裏我們使用ehcache 3.X ,不使用 ehcache 2.x
net.sf.ehcache(Ehcache 2.x)
org.ehcache(Ehcache 3.x)
我們需要spring-boot-starter-cache和cache-api依賴,以及依賴ehcache作爲緩存提供者。
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
-
修改配置文件application.yml
spring: cache: jcache: # 這裏是 jcache 不是 ecache。我們使用的 ecache 3.x config: classpath:ehcache.xml
-
新建ehcache.xml
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://www.ehcache.org/v3' xsi:schemaLocation=" http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.7.xsd"> <!-- 一個緩存模板。如果應用程序有多個緩存,但緩存的配置基本相同,那麼這將特別有利。 --> <!-- 持久緩存目錄 persistence(持久性標記) 在持久性標記中,我們爲硬盤(磁盤存儲)上基於文件的緩存定義了目錄。這只是文件夾的定義。--> <persistence directory="spring-boot-ehcache/cache" /> <!-- Default cache template --> <cache-template name="default"> <expiry> <ttl unit="seconds">30</ttl> <!-- 在expires標籤中,我們定義了30秒的生存時間(ttl)。生存時間指定緩存條目獨立於訪問可以在緩存中保留多長時間。指定的時間過期後,將從緩存中刪除該值。 --> </expiry> <resources> <!-- 配置緩存的層和容量。 --> <heap>1000</heap> <!-- 對於堆上存儲,我們配置了1,000個緩存條目的容量。這是開始刪除緩存之前的最大條目數。 --> <offheap unit="MB">10</offheap> <!-- 對於堆外存儲,我們配置了10mb的容量。 --> <disk persistent="true" unit="MB">20</disk> <!-- 作爲磁盤緩存,我們配置了20mb 磁盤緩存必須始終具有比堆緩存更高的內存容量,否則應用程序在解析XML文件時在應用程序啓動時拋出異常。 --> </resources> </cache-template> </config>
-
完成以上配置。實際上還不能夠使用緩存。還需要在ehcache.xml 添加
<!-- Cache configuration --> <cache alias="operation" uses-template="default"> <!-- 使用上面配置的模版 --> </cache>
這裏的 operation 是 TestController.test() 上@Cacheable註解的value值。 這裏務必要加上。到這裏基本就可以滿足基本的要求。
如何添加緩存
在指定需要添加緩存的方法上加上註釋,此處加的註釋與上文中SpringBoot使用默認的ConcurrentHashMap 相同
@RequestMapping("/{demoInt}")
// @Cacheable註解會先查詢是否已經有緩存,有會使用緩存,沒有則會執行方法並緩存。
@Cacheable(value = "operation",// 緩存的名稱,在 spring配置文件中定義,必須指定至少一個
key = "#demoInt") // 緩存的 key,可以爲空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合。
public String test(@PathVariable Integer demoInt) {
// 此處的入參一定要實現 java.io.Serializable 。 Integer類 繼承抽象類 Number。 Number實現 Serializable
System.out.println("我要運算了~~~");
return String.valueOf(demoInt * 666);
}
此處的 @Cacheable 的value值一定要在 ehcache.xml 中cache標籤alias聲明
<!-- Cache configuration -->
<cache alias="operation" uses-template="default"> <!-- 使用上面配置的模版 -->
</cache>
如何清除緩存
使用 @CacheEvict 註解
@CachEvict
的作用 主要針對方法配置,能夠根據一定的條件對緩存進行清空 。
屬性 | 解釋 | 示例 |
---|---|---|
allEntries | 是否清空所有緩存內容,缺省爲 false,如果指定爲true,則方法調用後將立即清空所有緩存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法執行前就清空,缺省爲 false,如果指定爲 true,則在方法還沒有執行的時候就清空緩存,缺省情況下,如果方法執行拋出異常,則不會清空緩存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
爲了方便測試,我新建了一個對象,並重新編寫了測試的Controller
import java.io.Serializable;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月12日
*/
public class Person implements Serializable { // 一定要實現 Serializable!!!!
private static final long serialVersionUID = -680651108611576893L;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Author: amarone
* @Created Date: 2019年09月11日
*/
@RestController
public class TestController {
@RequestMapping("/{demoInt}")
// @Cacheable註解會先查詢是否已經有緩存,有會使用緩存,沒有則會執行方法並緩存。
@Cacheable(value = "operation",// 緩存的名稱,在 spring配置文件中定義,必須指定至少一個
key = "#demoInt") // 緩存的 key,可以爲空,如果指定要按照 SpEL 表達式編寫,如果不指定,則缺省按照方法的所有參數進行組合。
public Object test(@PathVariable Integer demoInt) {
// 此處的入參一定要實現 java.io.Serializable 。 Integer類 繼承抽象類 Number。 Number實現 Serializable
Person person = new Person();
person.setAge(demoInt);
person.setName("張三" + demoInt);
System.out.println(person.toString());
return person;
}
/**
* I'm going to kill all the data
* 方法調用後清空所有緩存
*/
@RequestMapping("/all")
@CacheEvict(value = "operation", allEntries = true)
public String cleanAll() {
return "SUCCESS";
}
/**
* kill one data
*/
@RequestMapping("/kill/{demoInt}")
@CacheEvict(value = "operation", key = "#demoInt")
public String cleanOne(@PathVariable Integer demoInt) {
return "SUCCESS";
}
更多支持的註解以及用法請看 spring 官網
參考
@Cacheable 參數
Annotation parameter | Description |
---|---|
value / cacheNames | 要存儲方法執行結果的緩存的名稱 |
key | The key for the cache entries as Spring Expression Language (SpEL). If the parameter is not specified, a key is created for all method parameters by default. |
keyGenerator | 實現密鑰生成器接口的bean的名稱,因此允許創建用戶定義的緩存密鑰。 |
condition | Condition as Spring Expression Language (SpEL) that specifies when a result is to be cached. |
unless | Condition as Spring Expression Language (SpEL) that specifies when a result should not be cached. |