Spring?@Cacheable指定失效時間實例
Spring @Cacheable指定失效時間
新版本配置
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> {
for (Map.Entry<String, Duration> entry : RedisCacheName.getCacheMap().entrySet()) {
builder.withCacheConfiguration(entry.getKey(),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(entry.getValue()));
}
};
}
public static class RedisCacheName {
public static final String CACHE_10MIN = "CACHE_10MIN";
@Getter
private static final Map<String, Duration> cacheMap;
static {
cacheMap = ImmutableMap.<String, Duration>builder().put(CACHE_10MIN, Duration.ofSeconds(10L)).build();
}
}
}
老版本配置
interface CacheNames{
String CACHE_15MINS = "sssss:cache:15m";
/** 30分鐘緩存組 */
String CACHE_30MINS = "sssss:cache:30m";
/** 60分鐘緩存組 */
String CACHE_60MINS = "sssss:cache:60m";
/** 180分鐘緩存組 */
String CACHE_180MINS = "sssss:cache:180m";
}
@Component
public class RedisCacheCustomizer
implements CacheManagerCustomizer<RedisCacheManager> {
/** CacheManager緩存自定義初始化比較早,盡量不要@autowired 其他spring 組件 */
@Override
public void customize(RedisCacheManager cacheManager) {
// 自定義緩存名對應(yīng)的過期時間
Map<String, Long> expires = ImmutableMap.<String, Long>builder()
.put(CacheNames.CACHE_15MINS, TimeUnit.MINUTES.toSeconds(15))
.put(CacheNames.CACHE_30MINS, TimeUnit.MINUTES.toSeconds(30))
.put(CacheNames.CACHE_60MINS, TimeUnit.MINUTES.toSeconds(60))
.put(CacheNames.CACHE_180MINS, TimeUnit.MINUTES.toSeconds(180)).build();
// spring cache是根據(jù)cache name查找緩存過期時長的,如果找不到,則使用默認(rèn)值
cacheManager.setDefaultExpiration(TimeUnit.MINUTES.toSeconds(30));
cacheManager.setExpires(expires);
}
}
@Cacheable(key = "key", cacheNames = CacheNames.CACHE_15MINS)
public String demo2(String key) {
return "abc" + key;
}
@Cacheable緩存失效時間策略默認(rèn)實現(xiàn)及擴(kuò)展
之前對Spring緩存的理解是每次設(shè)置緩存之后,重復(fù)請求會刷新緩存時間,但是問題排查閱讀源碼發(fā)現(xiàn),跟自己的理解大相徑庭。所有的你以為都僅僅是你以為?。。?!
背景
目前項目使用的spring緩存,主要是CacheManager、Cache以及@Cacheable注解,Spring現(xiàn)有的緩存注解無法單獨設(shè)置每一個注解的失效時間,Spring官方給的解釋:Spring Cache是一個抽象而不是一個緩存實現(xiàn)方案。
因此對于緩存失效時間(TTL)的策略依賴于底層緩存中間件,官方給舉例:ConcurrentMap是不支持失效時間的,而Redis是支持失效時間的。

Spring Cache Redis實現(xiàn)
Spring緩存注解@Cacheable底層的CacheManager與Cache如果使用Redis方案的話,首次設(shè)置緩存數(shù)據(jù)之后,每次重復(fù)請求相同方法讀取緩存并不會刷新失效時間,這是Spring的默認(rèn)行為(受一些緩存影響,一直以為每次讀緩存也會刷新緩存失效時間)。
可以參見源碼:
org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
因此如果我們需要自行控制緩存失效策略,就可能需要一些開發(fā)工作,具體如下。
Spring Cache 失效時間自行刷新
1:基于Spring的Cache組件進(jìn)行定制,對get方法進(jìn)行重寫,刷新過期時間。相對簡單,不難;此處不貼代碼了。
2:可以使用后臺線程進(jìn)行定時的緩存刷新,以達(dá)到刷新時間的作用。
3:使用spring data redis模塊,該模塊提供對了TTL更新策略的,可以參見:org.springframework.data.redis.core.PartialUpdate
注意:
Spring對于@Cacheable注解是由spring-context提供的,spring-context提供的緩存的抽象,是一套標(biāo)準(zhǔn)而不是實現(xiàn)。
而PartialUpdate是由于spring-data-redis提供的,spring-data-redis是一套spring關(guān)于redis的實現(xiàn)方案。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot 創(chuàng)建容器的實現(xiàn)
這篇文章主要介紹了SpringBoot 創(chuàng)建容器的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
SpringBoot 使用Mybatis分頁插件實現(xiàn)詳解
這篇文章主要介紹了SpringBoot 使用Mybatis分頁插件實現(xiàn)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10
Spring boot中使用ElasticSearch的方法詳解
這篇文章主要給大家介紹了關(guān)于Spring boot中使用ElasticSearch的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01
Java實現(xiàn)深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)算法
深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)是兩種基本的圖搜索算法,可用于圖的遍歷、路徑搜索等問題。DFS采用棧結(jié)構(gòu)實現(xiàn),從起點開始往深處遍歷,直到找到目標(biāo)節(jié)點或遍歷完整個圖;BFS采用隊列結(jié)構(gòu)實現(xiàn),從起點開始往廣處遍歷,直到找到目標(biāo)節(jié)點或遍歷完整個圖2023-04-04
關(guān)于Mybatis-Plus?Update更新策略問題
這篇文章主要介紹了關(guān)于Mybatis-Plus?Update更新策略問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

