@Cacheable參數(shù)詳解

  1. 功能說明
      @Cacheable 注解在方法上剂公,表示該方法的返回結(jié)果是可以緩存的鸯乃。也就是說蠢涝,該方法的返回結(jié)果會放在緩存中玄呛,以便于以后使用相同的參數(shù)調(diào)用該方法時,會返回緩存中的值和二,而不會實際執(zhí)行該方法徘铝。

注意,這里強調(diào)了一點:參數(shù)相同惯吕。這一點應(yīng)該是很容易理解的惕它,因為緩存不關(guān)心方法的執(zhí)行邏輯,它能確定的是:對于同一個方法废登,如果參數(shù)相同淹魄,那么返回結(jié)果也是相同的。但是如果參數(shù)不同堡距,緩存只能假設(shè)結(jié)果是不同的甲锡,所以對于同一個方法,你的程序運行過程中羽戒,使用了多少種參數(shù)組合調(diào)用過該方法缤沦,理論上就會生成多少個緩存的 key(當然,這些組合的參數(shù)指的是與生成 key 相關(guān)的)易稠。下面來了解一下 @Cacheable 的一些參數(shù):

  1. cacheNames & value
      @Cacheable 提供兩個參數(shù)來指定緩存名:value缸废、cacheNames,二者選其一即可驶社。這是 @Cacheable 最簡單的用法示例:
@Override
@Cacheable("menu")
public Menu findById(String id) {
    Menu menu = this.getById(id);
    if (menu != null){
        System.out.println("menu.name = " + menu.getName());
    }
    return menu;
}

在這個例子中企量,findById 方法與一個名為 menu 的緩存關(guān)聯(lián)起來了。調(diào)用該方法時亡电,會檢查 menu 緩存届巩,如果緩存中有結(jié)果,就不會去執(zhí)行方法了份乒。

  1. 關(guān)聯(lián)多個緩存名
    其實姆泻,按照官方文檔零酪,@Cacheable 支持同一個方法關(guān)聯(lián)多個緩存。這種情況下拇勃,當執(zhí)行方法之前四苇,這些關(guān)聯(lián)的每一個緩存都會被檢查,而且只要至少其中一個緩存命中了方咆,那么這個緩存中的值就會被返回月腋。示例:
@Override
    @Cacheable({"menu", "menuById"})
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

---------
@GetMapping("/findById/{id}")
public Menu findById(@PathVariable("id")String id){
    Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
    Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
    return menu0;
}

為了直觀起見,直接將 id 參數(shù)寫到代碼里“曷福現(xiàn)在榆骚,我們來測試一下,看一下結(jié)果:


1.png
  1. key & keyGenerator
      一個緩存名對應(yīng)一個被注解的方法煌集,但是一個方法可能傳入不同的參數(shù)妓肢,那么結(jié)果也就會不同,這應(yīng)該如何區(qū)分呢苫纤?這就需要用到 key 碉钠。在 spring 中,key 的生成有兩種方式:顯式指定和使用 keyGenerator 自動生成卷拘。

4.1. KeyGenerator 自動生成
    當我們在聲明 @Cacheable 時不指定 key 參數(shù)喊废,則該緩存名下的所有 key 會使用 KeyGenerator 根據(jù)參數(shù) 自動生成。spring 有一個默認的 SimpleKeyGenerator 栗弟,在 spring boot 自動化配置中污筷,這個會被默認注入。生成規(guī)則如下:

a. 如果該緩存方法沒有參數(shù)乍赫,返回 SimpleKey.EMPTY 瓣蛀;

b. 如果該緩存方法有一個參數(shù),返回該參數(shù)的實例 雷厂;

c. 如果該緩存方法有多個參數(shù)惋增,返回一個包含所有參數(shù)的 SimpleKey ;

默認的 key 生成器要求參數(shù)具有有效的 hashCode() 和 equals() 方法實現(xiàn)罗侯。另外,keyGenerator 也支持自定義溪猿, 并通過 keyGenerator 來指定钩杰。關(guān)于 KeyGenerator 這里不做詳細介紹,有興趣的話可以去看看源碼诊县,其實就是使用 hashCode 進行加乘運算讲弄。跟 String 和 ArrayList 的 hash 計算類似。

4.2. 顯式指定 key
    相較于使用 KeyGenerator 生成依痊,spring 官方更推薦顯式指定 key 的方式避除,即指定 @Cacheable 的 key 參數(shù)怎披。

即便是顯式指定,但是 key 的值還是需要根據(jù)參數(shù)的不同來生成瓶摆,那么如何實現(xiàn)動態(tài)拼接呢凉逛?SpEL(Spring Expression Language,Spring 表達式語言) 能做到這一點群井。下面是一些使用 SpEL 生成 key 的例子状飞。

@Override
    @Cacheable(value = {"menuById"}, key = "#id")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

    @Override
    @Cacheable(value = {"menuById"}, key = "'id-' + #menu.id")
    public Menu findById(Menu menu) {
        return menu;
    }

    @Override
    @Cacheable(value = {"menuById"}, key = "'hash' + #menu.hashCode()")
    public Menu findByHash(Menu menu) {
        return menu;
    }

結(jié)果:


2.png

顯示指定的好處在于,直觀明了书斜,看到代碼就能想象生成的 key 是什么樣诬辈。而且 SpEL 也很強大。關(guān)于 SpEL 的詳細用法荐吉,這里不詳述焙糟,可以參考官方文檔:

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions

  注意:官方說 key 和 keyGenerator 參數(shù)是互斥的,同時指定兩個會導(dǎo)致異常样屠。

  1. cacheManager & cacheResolver
      CacheManager穿撮,緩存管理器是用來管理(檢索)一類緩存的。通常來講瞧哟,緩存管理器是與緩存組件類型相關(guān)聯(lián)的混巧。我們知道,spring 緩存抽象的目的是為使用不同緩存組件類型提供統(tǒng)一的訪問接口勤揩,以向開發(fā)者屏蔽各種緩存組件的差異性咧党。那么 CacheManager 就是承擔了這種屏蔽的功能。spring 為其支持的每一種緩存的組件類型提供了一個默認的 manager陨亡,如:RedisCacheManager 管理 redis 相關(guān)的緩存的檢索傍衡、EhCacheManager 管理 ehCache 相關(guān)的緩等。

CacheResolver负蠕,緩存解析器是用來管理緩存管理器的蛙埂,CacheResolver 保持一個 cacheManager 的引用,并通過它來檢索緩存遮糖。CacheResolver 與 CacheManager 的關(guān)系有點類似于 KeyGenerator 跟 key绣的。spring 默認提供了一個 SimpleCacheResolver,開發(fā)者可以自定義并通過 @Bean 來注入自定義的解析器欲账,以實現(xiàn)更靈活的檢索屡江。

大多數(shù)情況下,我們的系統(tǒng)只會配置一種緩存赛不,所以我們并不需要顯式指定 cacheManager 或者 cacheResolver粤蝎。但是 spring 允許我們的系統(tǒng)同時配置多種緩存組件溜在,這種情況下,我們需要指定民褂。指定的方式是使用 @Cacheable 的 cacheManager 或者 cacheResolver 參數(shù)。

注意:按照官方文檔,cacheManager 和 cacheResolver 是互斥參數(shù),同時指定兩個可能會導(dǎo)致異常。

  1. sync
      是否同步桩蓉,true/false。在一個多線程的環(huán)境中抓艳,某些操作可能被相同的參數(shù)并發(fā)地調(diào)用触机,這樣同一個 value 值可能被多次計算(或多次訪問 db),這樣就達不到緩存的目的玷或。針對這些可能高并發(fā)的操作儡首,我們可以使用 sync 參數(shù)來告訴底層的緩存提供者將緩存的入口鎖住,這樣就只能有一個線程計算操作的結(jié)果值偏友,而其它線程需要等待蔬胯,這樣就避免了 n-1 次數(shù)據(jù)庫訪問。

sync = true 可以有效的避免緩存擊穿的問題位他。

  1. condition
      調(diào)用前判斷氛濒,緩存的條件。有時候鹅髓,我們可能并不想對一個方法的所有調(diào)用情況進行緩存舞竿,我們可能想要根據(jù)調(diào)用方法時候的某些參數(shù)值,來確定是否需要將結(jié)果進行緩存或者從緩存中取結(jié)果窿冯。比如當我根據(jù)年齡查詢用戶的時候骗奖,我只想要緩存年齡大于 35 的查詢結(jié)果。那么 condition 能實現(xiàn)這種效果醒串。

condition 接收一個結(jié)果為 true 或 false 的表達式执桌,表達式同樣支持 SpEL 。如果表達式結(jié)果為 true芜赌,則調(diào)用方法時會執(zhí)行正常的緩存邏輯(查緩存-有就返回-沒有就執(zhí)行方法-方法返回不空就添加緩存)仰挣;否則,調(diào)用方法時就好像該方法沒有聲明緩存一樣(即無論傳入了什么參數(shù)或者緩存中有些什么值缠沈,都會執(zhí)行方法膘壶,并且結(jié)果不放入緩存)。下面舉個例子:

我們看一下數(shù)據(jù)庫洲愤,以這兩條數(shù)據(jù)為例:


3.png

我們首先定義一個帶條件的緩存方法:

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "#conditionValue > 1")
    public Menu findById(String id, Integer conditionValue) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

然后分兩種情況調(diào)用(為了直觀可見颓芭,直接將 id 寫在代碼中):

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a", 0);
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75", 2);
        return menu0;
    }
4.png

5.png

可以看到,兩次請求都執(zhí)行方法(因為原來緩存中都沒有數(shù)據(jù))禽篱,但是只有“微服務(wù)測試2”緩存了畜伐。這說明馍惹,只有滿足 condition 條件的調(diào)用躺率,結(jié)果才會被緩存玛界。接下來我們再請求一遍,看下結(jié)果和打拥恐ā:


6.png

7.png

可以看到慎框,“微服務(wù)測試2”由于已經(jīng)有了緩存,所以沒有再執(zhí)行方法體后添。而“微服務(wù)測試0”又一次執(zhí)行了笨枯。

  1. unless
      執(zhí)行后判斷,不緩存的條件遇西。unless 接收一個結(jié)果為 true 或 false 的表達式馅精,表達式支持 SpEL。當結(jié)果為 true 時粱檀,不緩存洲敢。舉個例子:

我們先清除 redis 中的數(shù)據(jù)。然后看看 mysql 中的數(shù)據(jù):


8.png

然后編寫一個緩存方法(在該方法中茄蚯,result代表方法的返回值压彭。關(guān)于):

@Override
    @Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

然后調(diào)用該方法:

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
        return menu0;
    }

看看緩存結(jié)果和打印:


9.png
10.png

可以看到渗常,兩次都執(zhí)行了方法體(其實壮不,unless 條件就是在方法執(zhí)行完畢后調(diào)用,所以它不會影響方法的執(zhí)行)皱碘,但是結(jié)果只有 menu.type = 'page' 的緩存了询一,說明 unless 參數(shù)生效了。

  1. condition VS unless 尸执?
      既然 condition 和 unless 都能決定是否進行緩存家凯,那么同時指定這兩個參數(shù)并且結(jié)果相沖突的時候,會怎么樣呢如失?我們來試一試绊诲。

首先清除 redis 數(shù)據(jù),然后在緩存方法上加上 condition="true"褪贵,如

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "true", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

其它代碼不變掂之,我們來看一下緩存結(jié)果和打印:


11.png
12.png

可以看到脆丁,雖然兩次調(diào)用都執(zhí)行了世舰,但是,type='folder' 的還是被排除了槽卫。說明這種情況下跟压,unless 比 condition 優(yōu)先級要高。接下來我們把 condition="false"歼培,再來試試震蒋,結(jié)果:


13.png
14.png

 可以看到茸塞,兩次調(diào)用的結(jié)果都沒有緩存。說明在這種情況下查剖,condition 比 unless 的優(yōu)先級高钾虐。總結(jié)起來就是:

condition 不指定相當于 true笋庄,unless 不指定相當于 false

當 condition = false效扫,一定不會緩存;

當 condition = true直砂,且 unless = true菌仁,不緩存;

當 condition = true静暂,且 unless = false掘托,緩存;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末籍嘹,一起剝皮案震驚了整個濱河市闪盔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辱士,老刑警劉巖泪掀,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颂碘,居然都是意外死亡异赫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門头岔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塔拳,“玉大人,你說我怎么就攤上這事峡竣】恳郑” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵适掰,是天一觀的道長颂碧。 經(jīng)常有香客問我,道長类浪,這世上最難降的妖魔是什么载城? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮费就,結(jié)果婚禮上诉瓦,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好睬澡,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布呼寸。 她就那樣靜靜地躺著,像睡著了一般猴贰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上河狐,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天米绕,我揣著相機與錄音,去河邊找鬼馋艺。 笑死栅干,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的捐祠。 我是一名探鬼主播碱鳞,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼踱蛀!你這毒婦竟也來了窿给?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤率拒,失蹤者是張志新(化名)和其女友劉穎崩泡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猬膨,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡角撞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勃痴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒所。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沛申,靈堂內(nèi)的尸體忽然破棺而出劣领,到底是詐尸還是另有隱情,我是刑警寧澤铁材,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布剖踊,位于F島的核電站,受9級特大地震影響衫贬,放射性物質(zhì)發(fā)生泄漏德澈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一固惯、第九天 我趴在偏房一處隱蔽的房頂上張望梆造。 院中可真熱鬧,春花似錦、人聲如沸镇辉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忽肛。三九已至村砂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屹逛,已是汗流浹背础废。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罕模,地道東北人评腺。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像淑掌,于是被迫代替她去往敵國和親蒿讥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容