SpringBoot使用Cache集成Redis做緩存的保姆級教程
1. 項目背景
Spring Cache是Spring框架提供的一個緩存抽象層,它簡化了緩存的使用和管理。Spring Cache默認使用服務(wù)器內(nèi)存,并無法控制緩存時長,查找緩存中的數(shù)據(jù)比較麻煩。
因此Spring Cache支持將緩存數(shù)據(jù)集成到各種緩存中間件中。本文已常用的Redis作為緩存中間件作為示例,詳細講解項目中如何使用Cache提高系統(tǒng)性能。
2. Spring Cache介紹
Spring Cache是Spring框架提供的一種緩存解決方案,基于AOP原理,實現(xiàn)了基于注解的緩存功能,只需要簡單地加一個注解就能實現(xiàn)緩存功能,對業(yè)務(wù)代碼的侵入性很小。
使用Spring Cache的方法很簡單,只需要在方法上添加注解即可實現(xiàn)將方法返回數(shù)據(jù)存入緩存,以及清理緩存等注解的使用。
2.1 主要特點
- 統(tǒng)一的緩存抽象:Spring Cache為應(yīng)用提供了一種統(tǒng)一的緩存抽象,可以輕松集成各種緩存提供者(如Ehcache、Redis、Caffeine等),使用統(tǒng)一的API。
- 注解驅(qū)動:Spring Cache通過簡單的注解配置,如
@Cacheable、@CachePut、@CacheEvict等,可以快速實現(xiàn)緩存功能,而無需處理底層緩存邏輯。 - 靈活性和擴展性:Spring Cache允許根據(jù)業(yè)務(wù)需求自定義緩存策略,如緩存的失效時間、緩存的淘汰策略等。同時,它也提供了
CacheManager接口和Cache接口,可以實現(xiàn)降低對各種緩存框架的耦合。
2.2 常用注解
@EnableCaching
- 作用:開啟Spring的緩存注解支持。
- 使用場景:在配置類上添加此注解,以啟用Spring Cache的注解處理功能。
- 注意:此注解本身并不提供緩存實現(xiàn),而是允許你使用
@Cacheable、@CachePut、@CacheEvict等注解來定義緩存行為。
@Cacheable
- 作用:在方法執(zhí)行前檢查緩存,如果緩存中存在數(shù)據(jù)則直接返回,否則執(zhí)行方法并將結(jié)果緩存。
- value:指定緩存的名稱(或名稱數(shù)組)。緩存名稱與
CacheManager中配置的緩存對應(yīng)。 - key:用于生成緩存鍵的表達式(可選)。如果不指定,則默認使用方法的參數(shù)值作為鍵。
- condition:條件表達式(可選),用于決定是否執(zhí)行緩存操作。
- unless:否定條件表達式(可選),用于在方法執(zhí)行后決定是否緩存返回值。
@Cacheable注解配置參數(shù)說明
value/cacheNames:
- 用于指定緩存的名稱(或名稱數(shù)組),緩存名稱作為緩存key的前綴。這是緩存的標識符,用于在
CacheManager中查找對應(yīng)的緩存。 value和cacheNames是互斥的,即只能指定其中一個。
- 用于指定緩存的名稱(或名稱數(shù)組),緩存名稱作為緩存key的前綴。這是緩存的標識符,用于在
key:
- 用于生成緩存鍵的表達式。這個鍵用于在緩存中唯一標識存儲的值。
- 如果不指定
key,則默認使用方法的參數(shù)值(經(jīng)過某種轉(zhuǎn)換)作為鍵。 - 可以使用Spring Expression Language(SpEL)來編寫
key表達式,以實現(xiàn)動態(tài)鍵的生成。
keyGenerator:
- 指定一個自定義的鍵生成器(實現(xiàn) org.springframework.cache.interceptor.KeyGenerator 接口的類),用于生成緩存的鍵。與 key 屬性互斥,二者只能選其一。
- 如果同時指定了
key和keyGenerator,則會引發(fā)異常,因為它們是互斥的。 - 開發(fā)者可以編寫自己的
KeyGenerator實現(xiàn),并將其注冊到Spring容器中,然后在@Cacheable注解中引用。
cacheManager:
CacheManager表示緩存管理器,通過緩存管理器可以設(shè)置緩存過期時間。- 用于指定要使用的
CacheManager。這是一個可選參數(shù),通常不需要顯式指定,因為Spring會默認使用配置的CacheManager。 - 如果系統(tǒng)中配置了多個
CacheManager,則需要通過此參數(shù)指定使用哪一個。
cacheResolver:
- 緩存解析器,用于解析緩存名稱并返回相應(yīng)的
Cache對象。這也是一個可選參數(shù)。 - 類似于
cacheManager,如果系統(tǒng)中配置了多個緩存解析邏輯,可以通過此參數(shù)指定使用哪一個。
- 緩存解析器,用于解析緩存名稱并返回相應(yīng)的
condition:
- 條件表達式,用于決定是否執(zhí)行緩存操作。這是一個可選參數(shù)。
- 條件表達式使用SpEL編寫,如果表達式返回
true,則執(zhí)行緩存操作;否則不執(zhí)行。
unless:
- 否定條件表達式,用于在方法執(zhí)行后決定是否緩存返回值。這也是一個可選參數(shù)。
- 與
condition類似,unless也使用SpEL編寫,但它是在方法執(zhí)行后才進行評估的。 - 如果
unless表達式返回true,則不緩存返回值;否則緩存。
sync:
- 是否使用異步模式進行緩存操作。這是一個可選參數(shù),通常不需要顯式指定。
- 在多線程環(huán)境中,如果多個線程同時請求相同的數(shù)據(jù)并觸發(fā)緩存操作,使用異步模式可以避免線程阻塞和重復(fù)計算。
@Cacheable注解的這些參數(shù)是互斥或相互關(guān)聯(lián)的,例如value和cacheNames不能同時指定,key和keyGenerator也不能同時指定。此外,cacheManager和cacheResolver也是互斥的,因為它們都用于指定緩存的解析和管理方式。
對于前兩個注解的應(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 框架中用于處理字符串表達式的強大工具,它可以實現(xiàn)獲取對象的屬性,調(diào)用對象的方法操作。
- 單個緩存名稱:
@Cacheable(value = "myCache")表示使用名為myCache的緩存。 - 多個緩存名稱:
@Cacheable(value = {"cache1", "cache2"})表示方法的結(jié)果將同時緩存到cache1和cache2中。 - 與
@CacheConfig結(jié)合使用:如果類上使用了@CacheConfig注解,并且指定了cacheNames屬性,那么類中的方法在使用@Cacheable時可以省略value屬性,直接使用類級別的緩存配置。
@CacheEvict
- 作用:從緩存中刪除數(shù)據(jù)。
- value:指定要刪除的緩存的名稱(或名稱數(shù)組)。
- key:用于指定要刪除的緩存鍵(可選)。如果不指定,則默認使用方法的參數(shù)值作為鍵。
- allEntries:布爾值,指定是否刪除緩存中的所有條目(而不是僅刪除與指定鍵匹配的條目)。
- beforeInvocation:布爾值,指定是否在方法執(zhí)行之前刪除緩存(默認為
false,即在方法執(zhí)行之后刪除)。
@CachePut
- 作用:更新緩存中的數(shù)據(jù),無論方法是否成功執(zhí)行,都會將結(jié)果放入緩存。
- value、key、condition、unless:與
@Cacheable中的這些屬性相同。
@Caching
- 作用:允許在同一個方法上組合使用多個緩存注解(如
@Cacheable、@CachePut、@CacheEvict)。 - 屬性:包含一個或多個緩存注解。
@CacheConfig
- 作用:為類級別提供緩存相關(guān)的默認配置。
- cacheNames:指定該類中所有方法使用的默認緩存名稱(或名稱數(shù)組)。
- keyGenerator:指定自定義的鍵生成器(可選)。
- cacheManager:指定要使用的
CacheManager(可選)。
3. 示例代碼
項目依賴于Redis配置,這里就不多贅述了。
緩存管理器配置:
定義了兩個緩存管理器,默認cacheManager(使用@Primary標注),一個緩存返回值為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 {
/**
* 默認緩存管理器
* 只有CacheManger才能掃描到cacheable注解
* spring提供了緩存支持Cache接口,實現(xiàn)了很多個緩存類,其中包括RedisCache。但是我們需要對其進行配置,這里就是配置RedisCache
*/
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.RedisCacheManagerBuilder
//Redis鏈接工廠
.fromConnectionFactory(redisConnectionFactory)
//緩存配置 通用配置 默認存儲一小時
.cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))
//配置同步修改或刪除 put/evict
.transactionAware()
//對于不同的cacheName我們可以設(shè)置不同的過期時間
.withCacheConfiguration("cache2:cacheByUser", getCacheConfigurationWithTtl(Duration.ofHours(2)))
.build();
}
/**
* 創(chuàng)建并返回一個CacheManager Bean,用于管理Redis緩存。
* 主要返回結(jié)果為null時使用,會緩存null值,緩存時間為10分鐘,防止緩存穿透。
* 使用時通過 cacheManager = "cacheNullManager" 指定使用該緩存管理器。
*/
@Bean
public CacheManager cacheNullManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.RedisCacheManagerBuilder
//Redis鏈接工廠
.fromConnectionFactory(redisConnectionFactory)
//緩存配置 通用配置 默認存儲一小時
.cacheDefaults(RedisCacheConfiguration
.defaultCacheConfig()
// 設(shè)置key為String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 設(shè)置value 為自動轉(zhuǎn)Json的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))
.entryTtl(Duration.ofMinutes(10)))
//配置同步修改或刪除 put/evict
.transactionAware()
.build();
}
/**
* 緩存的基本配置對象
*/
private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {
return RedisCacheConfiguration
.defaultCacheConfig()
//設(shè)置key value的序列化方式
// 設(shè)置key為String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 設(shè)置value 為自動轉(zhuǎn)Json的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()))
// 不緩存null
.disableCachingNullValues()
// 設(shè)置緩存的過期時間
.entryTtl(duration);
}
/**
* 緩存的異常處理
*/
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 異常處理,當Redis發(fā)生異常時,打印日志,但是程序正常走
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對象就id、name兩個字段,大家隨意~
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é)果會被緩存到名為"cache:simpleCache"的緩存中。
* 如果在緩存中找到相同請求的結(jié)果,將直接返回緩存的值,避免重復(fù)執(zhí)行方法體中的邏輯。
*
* <p>方法內(nèi)部,使用Thread.sleep(5000)模擬了一個耗時操作,
*/
@GetMapping("/simpleCache")
@Cacheable(cacheNames = "cache:simpleCache")
public String simpleCache() throws InterruptedException {
Thread.sleep(5000);
log.info("執(zhí)行了simpleCache方法");
return "test";
}
/**
* 如果緩存中存在對應(yīng)的ID,則直接從緩存中獲取結(jié)果,避免重復(fù)執(zhí)行耗時操作。
* 如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。
* 方法執(zhí)行過程中,通過Thread.sleep模擬了一個耗時操作。
*/
@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注解集成了多個緩存策略:</p>
* <ul>
* <li>
* 當方法返回值為null時(即緩存穿透情況),使用名為"cacheNullManager"的CacheManager進行緩存處理,
* 緩存名稱為"cache2:cacheByKey",緩存鍵為傳入的用戶ID,并設(shè)置緩存過期時間為10分鐘。
* 這通過@Cacheable注解的cacheManager屬性指定緩存管理器,unless屬性設(shè)置緩存條件(當結(jié)果為null時緩存)。
* </li>
* <li>
* 當方法返回值不為null時,使用默認的CacheManager進行緩存處理,
* 緩存名稱和鍵的設(shè)置與上述相同,但此時緩存管理器為默認配置。
* 這通過另一個@Cacheable注解實現(xiàn),其unless屬性設(shè)置為當結(jié)果為null時不緩存。
* </li>
* </ul>
*
* <p>在方法執(zhí)行過程中,通過Thread.sleep模擬了一個耗時操作。</p>
*/
@Caching(
cacheable = {
//result為null時,屬于緩存穿透情況,使用cacheNullManager緩存管理器進行緩存,并且設(shè)置過期時間為10分鐘。
@Cacheable(cacheNames = "cache2:cacheByKey", key = "#id", unless = "#result != null", cacheManager = "cacheNullManager"),
//result不為null時,使用默認緩存管理器進行緩存。
@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的條件進行緩存處理。
*
* @param user 用戶對象,包含用戶ID等信息。
* @return 返回傳入的用戶對象。
* @throws InterruptedException 如果線程被中斷,則拋出此異常。
*
* 通過@Cacheable注解實現(xiàn)了緩存功能,當請求的用戶ID大于100時,會觸發(fā)緩存機制。
* 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶對象的ID。
* 如果緩存中已存在對應(yīng)的用戶數(shù)據(jù),則直接從緩存中獲取并返回,避免重復(fù)執(zhí)行耗時操作。
* 如果緩存中不存在,則執(zhí)行方法體中的邏輯,將結(jié)果存入緩存并返回。
* 在方法執(zhí)行過程中,通過Thread.sleep模擬了一個耗時操作。
*/
@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對象的ID。
* 如果緩存中已存在對應(yīng)的用戶數(shù)據(jù),則更新緩存中的值;如果不存在,則創(chuàng)建新的緩存條目。
* 在方法執(zhí)行過程中,通過Thread.sleep模擬了一個耗時操作。
*/
@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的用戶,并從緩存中移除對應(yīng)的數(shù)據(jù)。
* <p>
* 使用@CacheEvict注解用于從緩存中移除指定ID的用戶數(shù)據(jù)。
* 緩存的名稱設(shè)置為"cache2:cacheByUser",緩存的鍵為傳入的用戶ID。
* 在執(zhí)行刪除操作前,方法通過Thread.sleep模擬了一個耗時操作。
*/
@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對數(shù)據(jù)進行緩存或更新。deleteUser使用@CacheEvict刪除緩存。cacheMore根據(jù)條件選擇不同的緩存管理器進行緩存數(shù)據(jù)。
簡單附幾張測試截圖吧
第一次查詢,沒有緩存截圖:

后續(xù)查詢走緩存的截圖

redis緩存數(shù)據(jù)格式:

redis緩存數(shù)據(jù)詳情:

4. SpEL在Spring Cache中的應(yīng)用
4.1 SpEL概述
SpEL是Spring框架提供的一種功能強大的表達式語言,它能夠在運行時查詢和操作對象圖。SpEL的語法簡潔,支持方法調(diào)用、字符串模板、集合操作、邏輯運算等復(fù)雜功能,使得在Spring配置和代碼中能夠更輕松地處理復(fù)雜的邏輯和數(shù)據(jù)結(jié)構(gòu)。
4.2 SpEL應(yīng)用
動態(tài)生成緩存鍵
- 在Spring Cache中,緩存鍵(Key)用于在緩存中唯一標識數(shù)據(jù)。通過使用SpEL表達式,可以根據(jù)方法參數(shù)、返回值等動態(tài)生成緩存鍵。
- 例如,在@Cacheable注解中,可以使用key屬性配合SpEL表達式來指定緩存鍵的生成規(guī)則。
條件緩存
- Spring Cache允許通過condition屬性來指定緩存的條件。當條件滿足時,才會執(zhí)行緩存操作(如緩存數(shù)據(jù)或移除緩存)。
除非條件
- unless屬性與condition屬性類似,但它用于指定不執(zhí)行緩存操作的條件。
- 當unless條件滿足時,即使方法被調(diào)用,其結(jié)果也不會被緩存。
- unless屬性同樣支持SpEL表達式。
4.3 SpEL表達式在Spring Cache中的常用變量
#參數(shù)名:
- 表示方法參數(shù)??梢酝ㄟ^參數(shù)名來引用方法參數(shù)的值。
- 例如,#param1表示第一個參數(shù)的值。
#result:
- 表示方法的返回值。在@CachePut和@CacheEvict注解中,可以使用#result來引用方法的返回值。
#root:
- 表示緩存表達式根對象(CacheExpressionRootObject)。它提供了對緩存操作上下文的訪問。
- 通過#root,可以獲取到緩存的詳細信息,如緩存名稱、方法參數(shù)等。
注意:
condition屬性在Spring Cache中用于在方法執(zhí)行前判斷是否執(zhí)行緩存操作,并且不能引用方法的返回值;而unless屬性則用于在方法執(zhí)行后根據(jù)返回值或其他條件來決定是否緩存數(shù)據(jù)。
5. 工作原理
Spring Cache是基于AOP原理,對添加注解@Cacheable的類生成代理對象,在方法執(zhí)行前查看是否有緩存對應(yīng)的數(shù)據(jù),如果有直接返回數(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做緩存的保姆級教程的詳細內(nèi)容,更多關(guān)于SpringBoot Cache集成Redis做緩存的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于jackson序列化和feign返回值的問題
這篇文章主要介紹了關(guān)于jackson序列化和feign返回值的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
Windows安裝兩個或多個JDK并實現(xiàn)自由切換的方法
最近新接手一個項目,啟動的時候,發(fā)現(xiàn)有些jar和現(xiàn)在正在使用的JDK版本不一致,一直啟動有問題,想著就多裝一個JDK,由于為了保證java的運行環(huán)境和編譯環(huán)境保持一致,就需要我們設(shè)置jdk的環(huán)境變量,所以本文給大家介紹了Windows安裝兩個或多個JDK并實現(xiàn)自由切換的方法2025-03-03
SpringBoot全局配置long轉(zhuǎn)String丟失精度的問題解決
web項目中,Java后端傳過來的Long/long類型,前端JS接收會丟失精度。那么應(yīng)該如何解決,本文就來介紹一下幾種方法,感興趣的可以了解一下2021-08-08
WebSocket實現(xiàn)系統(tǒng)后臺消息實時通知功能
在現(xiàn)代Web應(yīng)用中,提供實時通知對于改善用戶體驗至關(guān)重要,WebSocket技術(shù)允許建立雙向通信通道,從系統(tǒng)后臺將消息實時傳送給系統(tǒng)用戶,下面我們就來深入探討一下如何使用WebSocket來實現(xiàn)這一功能吧2023-10-10
MyBatis-Plus 分頁插件配置的兩種方式實現(xiàn)
本文主要介紹了MyBatis-Plus 分頁插件配置的兩種方式實現(xiàn),包括使用PaginationInterceptor和MybatisPlusInterceptor兩種方式,具有一定的參考價值,感興趣的可以了解一下2025-03-03
mybatis實現(xiàn)動態(tài)升降序的問題小結(jié)
文章介紹了如何在MyBatis的XML文件中實現(xiàn)動態(tài)排序,使用$符號而不是#符號來引用變量,以避免SQL注入,同時,強調(diào)了在Java代碼中進行防注入處理的重要性,感興趣的朋友一起看看吧2025-02-02

