SpringBoot使用Cache集成Redis做緩存的保姆級(jí)教程
1. 項(xiàng)目背景
Spring Cache是Spring框架提供的一個(gè)緩存抽象層,它簡化了緩存的使用和管理。Spring Cache默認(rèn)使用服務(wù)器內(nèi)存,并無法控制緩存時(shí)長,查找緩存中的數(shù)據(jù)比較麻煩。
因此Spring Cache支持將緩存數(shù)據(jù)集成到各種緩存中間件中。本文已常用的Redis作為緩存中間件作為示例,詳細(xì)講解項(xiàng)目中如何使用Cache提高系統(tǒng)性能。
2. Spring Cache介紹
Spring Cache是Spring框架提供的一種緩存解決方案,基于AOP原理,實(shí)現(xiàn)了基于注解的緩存功能,只需要簡單地加一個(gè)注解就能實(shí)現(xiàn)緩存功能,對(duì)業(yè)務(wù)代碼的侵入性很小。
使用Spring Cache的方法很簡單,只需要在方法上添加注解即可實(shí)現(xiàn)將方法返回?cái)?shù)據(jù)存入緩存,以及清理緩存等注解的使用。
2.1 主要特點(diǎn)
- 統(tǒng)一的緩存抽象:Spring Cache為應(yīng)用提供了一種統(tǒng)一的緩存抽象,可以輕松集成各種緩存提供者(如Ehcache、Redis、Caffeine等),使用統(tǒng)一的API。
- 注解驅(qū)動(dòng):Spring Cache通過簡單的注解配置,如
@Cacheable
、@CachePut
、@CacheEvict
等,可以快速實(shí)現(xiàn)緩存功能,而無需處理底層緩存邏輯。 - 靈活性和擴(kuò)展性:Spring Cache允許根據(jù)業(yè)務(wù)需求自定義緩存策略,如緩存的失效時(shí)間、緩存的淘汰策略等。同時(shí),它也提供了
CacheManager
接口和Cache
接口,可以實(shí)現(xiàn)降低對(duì)各種緩存框架的耦合。
2.2 常用注解
@EnableCaching
- 作用:開啟Spring的緩存注解支持。
- 使用場景:在配置類上添加此注解,以啟用Spring Cache的注解處理功能。
- 注意:此注解本身并不提供緩存實(shí)現(xiàn),而是允許你使用
@Cacheable
、@CachePut
、@CacheEvict
等注解來定義緩存行為。
@Cacheable
- 作用:在方法執(zhí)行前檢查緩存,如果緩存中存在數(shù)據(jù)則直接返回,否則執(zhí)行方法并將結(jié)果緩存。
- value:指定緩存的名稱(或名稱數(shù)組)。緩存名稱與
CacheManager
中配置的緩存對(duì)應(yīng)。 - key:用于生成緩存鍵的表達(dá)式(可選)。如果不指定,則默認(rèn)使用方法的參數(shù)值作為鍵。
- condition:條件表達(dá)式(可選),用于決定是否執(zhí)行緩存操作。
- unless:否定條件表達(dá)式(可選),用于在方法執(zhí)行后決定是否緩存返回值。
@Cacheable注解配置參數(shù)說明
value/cacheNames:
- 用于指定緩存的名稱(或名稱數(shù)組),緩存名稱作為緩存key的前綴。這是緩存的標(biāo)識(shí)符,用于在
CacheManager
中查找對(duì)應(yīng)的緩存。 value
和cacheNames
是互斥的,即只能指定其中一個(gè)。
- 用于指定緩存的名稱(或名稱數(shù)組),緩存名稱作為緩存key的前綴。這是緩存的標(biāo)識(shí)符,用于在
key:
- 用于生成緩存鍵的表達(dá)式。這個(gè)鍵用于在緩存中唯一標(biāo)識(shí)存儲(chǔ)的值。
- 如果不指定
key
,則默認(rèn)使用方法的參數(shù)值(經(jīng)過某種轉(zhuǎn)換)作為鍵。 - 可以使用Spring Expression Language(SpEL)來編寫
key
表達(dá)式,以實(shí)現(xiàn)動(dòng)態(tài)鍵的生成。
keyGenerator:
- 指定一個(gè)自定義的鍵生成器(實(shí)現(xiàn) org.springframework.cache.interceptor.KeyGenerator 接口的類),用于生成緩存的鍵。與 key 屬性互斥,二者只能選其一。
- 如果同時(shí)指定了
key
和keyGenerator
,則會(huì)引發(fā)異常,因?yàn)樗鼈兪腔コ獾摹?/li> - 開發(fā)者可以編寫自己的
KeyGenerator
實(shí)現(xiàn),并將其注冊(cè)到Spring容器中,然后在@Cacheable
注解中引用。
cacheManager:
CacheManager
表示緩存管理器,通過緩存管理器可以設(shè)置緩存過期時(shí)間。- 用于指定要使用的
CacheManager
。這是一個(gè)可選參數(shù),通常不需要顯式指定,因?yàn)镾pring會(huì)默認(rèn)使用配置的CacheManager。
- 如果系統(tǒng)中配置了多個(gè)
CacheManager
,則需要通過此參數(shù)指定使用哪一個(gè)。
cacheResolver:
- 緩存解析器,用于解析緩存名稱并返回相應(yīng)的
Cache
對(duì)象。這也是一個(gè)可選參數(shù)。 - 類似于
cacheManager
,如果系統(tǒng)中配置了多個(gè)緩存解析邏輯,可以通過此參數(shù)指定使用哪一個(gè)。
- 緩存解析器,用于解析緩存名稱并返回相應(yīng)的
condition:
- 條件表達(dá)式,用于決定是否執(zhí)行緩存操作。這是一個(gè)可選參數(shù)。
- 條件表達(dá)式使用SpEL編寫,如果表達(dá)式返回
true
,則執(zhí)行緩存操作;否則不執(zhí)行。
unless:
- 否定條件表達(dá)式,用于在方法執(zhí)行后決定是否緩存返回值。這也是一個(gè)可選參數(shù)。
- 與
condition
類似,unless
也使用SpEL編寫,但它是在方法執(zhí)行后才進(jìn)行評(píng)估的。 - 如果
unless
表達(dá)式返回true
,則不緩存返回值;否則緩存。
sync:
- 是否使用異步模式進(jìn)行緩存操作。這是一個(gè)可選參數(shù),通常不需要顯式指定。
- 在多線程環(huán)境中,如果多個(gè)線程同時(shí)請(qǐng)求相同的數(shù)據(jù)并觸發(fā)緩存操作,使用異步模式可以避免線程阻塞和重復(fù)計(jì)算。
@Cacheable
注解的這些參數(shù)是互斥或相互關(guān)聯(lián)的,例如value
和cacheNames
不能同時(shí)指定,key
和keyGenerator
也不能同時(shí)指定。此外,cacheManager
和cacheResolver
也是互斥的,因?yàn)樗鼈兌加糜谥付ň彺娴慕馕龊凸芾矸绞健?/p>
對(duì)于前兩個(gè)注解的應(yīng)用:
@Cacheable(cacheNames = "cache:cacheByKey", key = "#id") public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了cacheByKey方法" + id); return id; }
看注釋掉的那行,取緩存名稱為cache:cacheByKey,參數(shù)id的值作為key,最終緩存key為:緩存名稱+“::”+key,例如:上述代碼id為123,最終的key為:cache:cacheByKey::123
SpEL(Spring Expression Language)是一種在 Spring 框架中用于處理字符串表達(dá)式的強(qiáng)大工具,它可以實(shí)現(xiàn)獲取對(duì)象的屬性,調(diào)用對(duì)象的方法操作。
- 單個(gè)緩存名稱:
@Cacheable(value = "myCache")
表示使用名為myCache
的緩存。 - 多個(gè)緩存名稱:
@Cacheable(value = {"cache1", "cache2"})
表示方法的結(jié)果將同時(shí)緩存到cache1
和cache2
中。 - 與
@CacheConfig
結(jié)合使用:如果類上使用了@CacheConfig
注解,并且指定了cacheNames
屬性,那么類中的方法在使用@Cacheable
時(shí)可以省略value
屬性,直接使用類級(jí)別的緩存配置。
@CacheEvict
- 作用:從緩存中刪除數(shù)據(jù)。
- value:指定要?jiǎng)h除的緩存的名稱(或名稱數(shù)組)。
- key:用于指定要?jiǎng)h除的緩存鍵(可選)。如果不指定,則默認(rèn)使用方法的參數(shù)值作為鍵。
- allEntries:布爾值,指定是否刪除緩存中的所有條目(而不是僅刪除與指定鍵匹配的條目)。
- beforeInvocation:布爾值,指定是否在方法執(zhí)行之前刪除緩存(默認(rèn)為
false
,即在方法執(zhí)行之后刪除)。
@CachePut
- 作用:更新緩存中的數(shù)據(jù),無論方法是否成功執(zhí)行,都會(huì)將結(jié)果放入緩存。
- value、key、condition、unless:與
@Cacheable
中的這些屬性相同。
@Caching
- 作用:允許在同一個(gè)方法上組合使用多個(gè)緩存注解(如
@Cacheable
、@CachePut
、@CacheEvict
)。 - 屬性:包含一個(gè)或多個(gè)緩存注解。
@CacheConfig
- 作用:為類級(jí)別提供緩存相關(guān)的默認(rèn)配置。
- cacheNames:指定該類中所有方法使用的默認(rèn)緩存名稱(或名稱數(shù)組)。
- keyGenerator:指定自定義的鍵生成器(可選)。
- cacheManager:指定要使用的
CacheManager
(可選)。
3. 示例代碼
項(xiàng)目依賴于Redis配置,這里就不多贅述了。
緩存管理器配置:
定義了兩個(gè)緩存管理器,默認(rèn)cacheManager
(使用@Primary
標(biāo)注),一個(gè)緩存返回值為null的管理器cacheNullManager
,詳情看下面代碼。
package com.maple.redis.config; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.CacheErrorHandler; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.lang.reflect.Method; import java.time.Duration; /** * @author 笑小楓 * @date 2025/1/7 */ @Slf4j @Configuration @EnableCaching public class CacheConfig extends CachingConfigurerSupport { /** * 默認(rèn)緩存管理器 * 只有CacheManger才能掃描到cacheable注解 * spring提供了緩存支持Cache接口,實(shí)現(xiàn)了很多個(gè)緩存類,其中包括RedisCache。但是我們需要對(duì)其進(jìn)行配置,這里就是配置RedisCache */ @Bean @Primary public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return RedisCacheManager.RedisCacheManagerBuilder //Redis鏈接工廠 .fromConnectionFactory(redisConnectionFactory) //緩存配置 通用配置 默認(rèn)存儲(chǔ)一小時(shí) .cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1))) //配置同步修改或刪除 put/evict .transactionAware() //對(duì)于不同的cacheName我們可以設(shè)置不同的過期時(shí)間 .withCacheConfiguration("cache2:cacheByUser", getCacheConfigurationWithTtl(Duration.ofHours(2))) .build(); } /** * 創(chuàng)建并返回一個(gè)CacheManager Bean,用于管理Redis緩存。 * 主要返回結(jié)果為null時(shí)使用,會(huì)緩存null值,緩存時(shí)間為10分鐘,防止緩存穿透。 * 使用時(shí)通過 cacheManager = "cacheNullManager" 指定使用該緩存管理器。 */ @Bean public CacheManager cacheNullManager(RedisConnectionFactory redisConnectionFactory) { return RedisCacheManager.RedisCacheManagerBuilder //Redis鏈接工廠 .fromConnectionFactory(redisConnectionFactory) //緩存配置 通用配置 默認(rèn)存儲(chǔ)一小時(shí) .cacheDefaults(RedisCacheConfiguration .defaultCacheConfig() // 設(shè)置key為String .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 設(shè)置value 為自動(dòng)轉(zhuǎn)Json的Object .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())) .entryTtl(Duration.ofMinutes(10))) //配置同步修改或刪除 put/evict .transactionAware() .build(); } /** * 緩存的基本配置對(duì)象 */ private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) { return RedisCacheConfiguration .defaultCacheConfig() //設(shè)置key value的序列化方式 // 設(shè)置key為String .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 設(shè)置value 為自動(dòng)轉(zhuǎn)Json的Object .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json())) // 不緩存null .disableCachingNullValues() // 設(shè)置緩存的過期時(shí)間 .entryTtl(duration); } /** * 緩存的異常處理 */ @Bean @Override public CacheErrorHandler errorHandler() { // 異常處理,當(dāng)Redis發(fā)生異常時(shí),打印日志,但是程序正常走 log.info("初始化 -> [{}]", "Redis CacheErrorHandler"); return new CacheErrorHandler() { @Override public void handleCacheGetError(RuntimeException e, Cache cache, Object key) { log.error("Redis occur handleCacheGetError:key -> [{}]", key, e); } @Override public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) { log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e); } @Override public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) { log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e); } @Override public void handleCacheClearError(RuntimeException e, Cache cache) { log.error("Redis occur handleCacheClearError:", e); } }; } @Override @Bean("myKeyGenerator") public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuffer sb = new StringBuffer(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } }
使用案例:
User
對(duì)象就id
、name
兩個(gè)字段,大家隨意~
package com.maple.redis.controller; import com.maple.redis.bean.User; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; import org.springframework.web.bind.annotation.*; /** * @author 笑小楓 * @date 2025/1/7 */ @Slf4j @RestController @RequestMapping("/cache") public class TestCacheController { /** * 獲取簡單緩存數(shù)據(jù)。 * * <p>通過@Cacheable注解,該方法的結(jié)果會(huì)被緩存到名為"cache:simpleCache"的緩存中。 * 如果在緩存中找到相同請(qǐng)求的結(jié)果,將直接返回緩存的值,避免重復(fù)執(zhí)行方法體中的邏輯。 * * <p>方法內(nèi)部,使用Thread.sleep(5000)模擬了一個(gè)耗時(shí)操作, */ @GetMapping("/simpleCache") @Cacheable(cacheNames = "cache:simpleCache") public String simpleCache() throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了simpleCache方法"); return "test"; } /** * 如果緩存中存在對(duì)應(yīng)的ID,則直接從緩存中獲取結(jié)果,避免重復(fù)執(zhí)行耗時(shí)操作。 * 如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。 * 方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。 */ @GetMapping("/{id}") @Cacheable(cacheNames = "cache:cacheByKey", key = "#id") public Integer cacheByKey(@PathVariable("id") Integer id) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了cacheByKey方法" + id); return id; } /** * <p>該方法使用@Caching注解集成了多個(gè)緩存策略:</p> * <ul> * <li> * 當(dāng)方法返回值為null時(shí)(即緩存穿透情況),使用名為"cacheNullManager"的CacheManager進(jìn)行緩存處理, * 緩存名稱為"cache2:cacheByKey",緩存鍵為傳入的用戶ID,并設(shè)置緩存過期時(shí)間為10分鐘。 * 這通過@Cacheable注解的cacheManager屬性指定緩存管理器,unless屬性設(shè)置緩存條件(當(dāng)結(jié)果為null時(shí)緩存)。 * </li> * <li> * 當(dāng)方法返回值不為null時(shí),使用默認(rèn)的CacheManager進(jìn)行緩存處理, * 緩存名稱和鍵的設(shè)置與上述相同,但此時(shí)緩存管理器為默認(rèn)配置。 * 這通過另一個(gè)@Cacheable注解實(shí)現(xiàn),其unless屬性設(shè)置為當(dāng)結(jié)果為null時(shí)不緩存。 * </li> * </ul> * * <p>在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。</p> */ @Caching( cacheable = { //result為null時(shí),屬于緩存穿透情況,使用cacheNullManager緩存管理器進(jìn)行緩存,并且設(shè)置過期時(shí)間為10分鐘。 @Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result != null", cacheManager = "cacheNullManager"), //result不為null時(shí),使用默認(rèn)緩存管理器進(jìn)行緩存。 @Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result == null") } ) @GetMapping("/cacheMore/{id}") public User cacheMore(@PathVariable("id") Integer id) throws InterruptedException { Thread.sleep(5000); if (id > 100) { return null; } else { return new User(id, "zhangsan"); } } @PostMapping("/cacheByUser") @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id") public User cacheByUser(@RequestBody User user) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了cacheByUser方法" + user.getId()); return user; } @PostMapping("/cacheByIdAndName") @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id") public User cacheByIdAndName(@RequestBody User user) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了cacheByUser方法" + user.getId()); return user; } /** * 根據(jù)用戶ID大于100的條件進(jìn)行緩存處理。 * * @param user 用戶對(duì)象,包含用戶ID等信息。 * @return 返回傳入的用戶對(duì)象。 * @throws InterruptedException 如果線程被中斷,則拋出此異常。 * * 通過@Cacheable注解實(shí)現(xiàn)了緩存功能,當(dāng)請(qǐng)求的用戶ID大于100時(shí),會(huì)觸發(fā)緩存機(jī)制。 * 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶對(duì)象的ID。 * 如果緩存中已存在對(duì)應(yīng)的用戶數(shù)據(jù),則直接從緩存中獲取并返回,避免重復(fù)執(zhí)行耗時(shí)操作。 * 如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。 * 在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。 */ @PostMapping("/cacheByUserIdGt100") @Cacheable(cacheNames = "cache2:cacheByUser", key = "#user.id", condition = "#user.id > 100") public User cacheByUserIdGt100(@RequestBody User user) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了cacheByUser方法" + user.getId()); return user; } /** * 更新用戶信息。 * <p> * 使用@CachePut注解將更新后的用戶信息存入緩存中。 * 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的User對(duì)象的ID。 * 如果緩存中已存在對(duì)應(yīng)的用戶數(shù)據(jù),則更新緩存中的值;如果不存在,則創(chuàng)建新的緩存條目。 * 在方法執(zhí)行過程中,通過Thread.sleep模擬了一個(gè)耗時(shí)操作。 */ @PostMapping("/updateUser") @CachePut(cacheNames = "cache2:cacheByUser", key = "#user.id") public User updateUser(@RequestBody User user) throws InterruptedException { Thread.sleep(5000); log.info("執(zhí)行了saveUser方法" + user.getId()); return user; } /** * 刪除指定ID的用戶,并從緩存中移除對(duì)應(yīng)的數(shù)據(jù)。 * <p> * 使用@CacheEvict注解用于從緩存中移除指定ID的用戶數(shù)據(jù)。 * 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶ID。 * 在執(zhí)行刪除操作前,方法通過Thread.sleep模擬了一個(gè)耗時(shí)操作。 */ @DeleteMapping("/{id}") @CacheEvict(cacheNames = "cache2:cacheByUser", key = "#id") public void deleteUser(@PathVariable("id") Integer id) throws InterruptedException { Thread.sleep(10000); log.info("執(zhí)行了deleteUser方法" + id); } }
模擬了多種緩存使用的方式
updateUser
使用@CachePut
對(duì)數(shù)據(jù)進(jìn)行緩存或更新。deleteUser
使用@CacheEvict
刪除緩存。cacheMore
根據(jù)條件選擇不同的緩存管理器進(jìn)行緩存數(shù)據(jù)。
簡單附幾張測試截圖吧
第一次查詢,沒有緩存截圖:
后續(xù)查詢走緩存的截圖
redis緩存數(shù)據(jù)格式:
redis緩存數(shù)據(jù)詳情:
4. SpEL在Spring Cache中的應(yīng)用
4.1 SpEL概述
SpEL是Spring框架提供的一種功能強(qiáng)大的表達(dá)式語言,它能夠在運(yùn)行時(shí)查詢和操作對(duì)象圖。SpEL的語法簡潔,支持方法調(diào)用、字符串模板、集合操作、邏輯運(yùn)算等復(fù)雜功能,使得在Spring配置和代碼中能夠更輕松地處理復(fù)雜的邏輯和數(shù)據(jù)結(jié)構(gòu)。
4.2 SpEL應(yīng)用
動(dòng)態(tài)生成緩存鍵
- 在Spring Cache中,緩存鍵(Key)用于在緩存中唯一標(biāo)識(shí)數(shù)據(jù)。通過使用SpEL表達(dá)式,可以根據(jù)方法參數(shù)、返回值等動(dòng)態(tài)生成緩存鍵。
- 例如,在@Cacheable注解中,可以使用key屬性配合SpEL表達(dá)式來指定緩存鍵的生成規(guī)則。
條件緩存
- Spring Cache允許通過condition屬性來指定緩存的條件。當(dāng)條件滿足時(shí),才會(huì)執(zhí)行緩存操作(如緩存數(shù)據(jù)或移除緩存)。
除非條件
- unless屬性與condition屬性類似,但它用于指定不執(zhí)行緩存操作的條件。
- 當(dāng)unless條件滿足時(shí),即使方法被調(diào)用,其結(jié)果也不會(huì)被緩存。
- unless屬性同樣支持SpEL表達(dá)式。
4.3 SpEL表達(dá)式在Spring Cache中的常用變量
#參數(shù)名:
- 表示方法參數(shù)??梢酝ㄟ^參數(shù)名來引用方法參數(shù)的值。
- 例如,#param1表示第一個(gè)參數(shù)的值。
#result:
- 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result來引用方法的返回值。
#root:
- 表示緩存表達(dá)式根對(duì)象(CacheExpressionRootObject)。它提供了對(duì)緩存操作上下文的訪問。
- 通過#root,可以獲取到緩存的詳細(xì)信息,如緩存名稱、方法參數(shù)等。
注意:
condition
屬性在Spring Cache中用于在方法執(zhí)行前判斷是否執(zhí)行緩存操作,并且不能引用方法的返回值;而unless
屬性則用于在方法執(zhí)行后根據(jù)返回值或其他條件來決定是否緩存數(shù)據(jù)。
5. 工作原理
Spring Cache是基于AOP原理,對(duì)添加注解@Cacheable的類生成代理對(duì)象,在方法執(zhí)行前查看是否有緩存對(duì)應(yīng)的數(shù)據(jù),如果有直接返回?cái)?shù)據(jù),如果沒有調(diào)用源方法獲取數(shù)據(jù)返回,并緩存起來,下邊跟蹤Spring Cache的切面類CacheAspectSupport.java中的private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)方法。
@Nullable private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) { if (contexts.isSynchronized()) { CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next(); if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { return this.invokeOperation(invoker); } Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); Cache cache = (Cache)context.getCaches().iterator().next(); try { return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache)); } catch (Cache.ValueRetrievalException var10) { Cache.ValueRetrievalException ex = var10; ReflectionUtils.rethrowRuntimeException(ex.getCause()); } } this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class)); List<CachePutRequest> cachePutRequests = new ArrayList(); if (cacheHit == null) { this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } Object cacheValue; Object returnValue; if (cacheHit != null && !this.hasCachePut(contexts)) {//如果緩存有,則從緩存取 cacheValue = cacheHit.get(); returnValue = this.wrapCacheValue(method, cacheValue); } else {//緩存沒有,執(zhí)行原始方法 returnValue = this.invokeOperation(invoker); cacheValue = this.unwrapReturnValue(returnValue);//再存緩存 } this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); Iterator var8 = cachePutRequests.iterator(); while(var8.hasNext()) { CachePutRequest cachePutRequest = (CachePutRequest)var8.next(); cachePutRequest.apply(cacheValue); } this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; }
6.總結(jié)
以上就是SpringBoot使用Cache集成Redis做緩存的保姆級(jí)教程的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Cache集成Redis做緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法
這篇文章主要介紹了詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01springboot jta atomikos實(shí)現(xiàn)分布式事物管理
這篇文章主要介紹了springboot jta atomikos實(shí)現(xiàn)分布式事物管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12淺談SpringBoot實(shí)現(xiàn)自動(dòng)裝配的方法原理
SpringBoot的自動(dòng)裝配是它的一大特點(diǎn),可以大大提高開發(fā)效率,減少重復(fù)性代碼的編寫。本文將詳細(xì)講解SpringBoot如何實(shí)現(xiàn)自動(dòng)裝配,需要的朋友可以參考下2023-05-05SpringBoot中Bean拷貝及工具類封裝的實(shí)現(xiàn)
本文主要介紹了SpringBoot中Bean拷貝及工具類封裝的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05selenium4.0版本在springboot中的使用問題的坑
本文主要介紹了selenium4.0版本在springboot中的使用問題的坑,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07