SpringCache結(jié)合Redis實(shí)現(xiàn)指定過期時(shí)間和到期自動(dòng)刷新
緩存作為提升應(yīng)用性能的重要手段,其管理策略的合理性直接影響到應(yīng)用的響應(yīng)速度和數(shù)據(jù)一致性。在Spring框架中,Spring Cache提供了一種聲明式緩存的解決方案,而Redis作為高性能的緩存數(shù)據(jù)庫(kù),被廣泛應(yīng)用于緩存實(shí)現(xiàn)。本文將介紹一種通過自定義注解實(shí)現(xiàn)Spring Cache與Redis緩存過期時(shí)間管理及自動(dòng)刷新的策略。
1、自定義注解CacheExpireConfig
為了更靈活地控制緩存的過期時(shí)間,我們定義了一個(gè)名為CacheExpireConfig
的自定義注解。此注解支持在方法級(jí)別配置緩存的過期時(shí)間和自動(dòng)刷新時(shí)間。
import java.lang.annotation.*; /** * @author tangzx */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheExpireConfig { /** * 緩存過期時(shí)間,支持單位天(d)、小時(shí)(h)、分鐘(m)、秒鐘(s)(不填單位默認(rèn)秒) * 例:2h */ String expireTime() default ""; /** * 緩存過期刷新時(shí)間,支持單位天(d)、小時(shí)(h)、分鐘(m)、秒鐘(s)(不填單位默認(rèn)秒) * 例:2h */ String expireRefreshTime() default ""; }
2、使用注解
在Spring的@Cacheable
注解基礎(chǔ)上,通過@CacheExpireConfig
注解,我們可以輕松地為特定方法設(shè)置緩存過期和刷新策略。
@Override @CacheExpireConfig(expireTime = "60s", expireRefreshTime = "30s") @Cacheable(value = "testCache", condition = "#userId != null && #userName == null ") public String testCache(String userId, String userName) { System.out.println("=====================>"); return "success"; }
3、啟動(dòng)時(shí)加載緩存過期配置
在Spring Boot應(yīng)用啟動(dòng)時(shí),通過TaRedisCacheConfigListener
監(jiān)聽器,掃描所有類和方法,加載帶有@CacheExpireConfig
注解的方法的緩存過期配置。
import cn.hutool.core.lang.ClassScanner; import org.apache.commons.lang3.ArrayUtils; import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationListener; import java.lang.reflect.Method; import java.util.Set; /** * @author tangzx * @date 2022/12/17 11:05 */ public class TaRedisCacheConfigListener implements ApplicationListener<ApplicationPreparedEvent> { @Override public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) { // 掃描所有類 Set<Class<?>> classes = scanPackage(); for (Class<?> target : classes) { Method[] methods = target.getMethods(); for (Method method : methods) { // 如果方法上未同時(shí)注解@Cacheable和@CacheExpireConfig,不需要配置 if (!method.isAnnotationPresent(Cacheable.class) || !method.isAnnotationPresent(CacheExpireConfig.class)) { continue; } Cacheable cacheable = method.getAnnotation(Cacheable.class); CacheExpireConfig cacheExpireConfig = method.getAnnotation(CacheExpireConfig.class); String expireTime = cacheExpireConfig.expireTime(); String expireRefreshTime = cacheExpireConfig.expireRefreshTime(); String[] cacheNames = ArrayUtils.addAll(cacheable.cacheNames(), cacheable.value()); boolean autoRefresh = cacheExpireConfig.autoRefresh(); for (String cacheName : cacheNames) { MethodCacheExpireConfig methodCacheExpireConfig = MethodCacheExpireConfig.builder() .expireTime(DurationUtils.parseDuration(expireTime).getSeconds()) .expireRefreshTime(DurationUtils.parseDuration(expireRefreshTime).getSeconds()) .autoRefresh(autoRefresh) .target(target) .method(method) .build(); TaRedisCacheFactory.addCacheExpireConfig(cacheName, methodCacheExpireConfig); } } } } private Set<Class<?>> scanPackage() { // 使用的hutool的類掃描器,如果項(xiàng)目中未使用工具類,可自行實(shí)現(xiàn) return ClassScanner.scanPackage(); } }
public static void main(String[] args) { SpringApplication application = new SpringApplicationBuilder().sources(StartApplication.class).build(args); try { application.addListeners(new TaRedisCacheConfigListener()); application.run(args); } catch (Exception e) { e.printStackTrace(); } }
4、重寫RedisCacheManager,設(shè)置過期時(shí)間
通過重寫RedisCacheManager
,我們可以根據(jù)配置動(dòng)態(tài)設(shè)置每個(gè)緩存的過期時(shí)間。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.cache.RedisCache; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import java.time.Duration; import java.util.Map; /** * @author Tzx * @date 2022/12/13 19:33 */ public class TaRedisCacheManager extends RedisCacheManager { private static final Logger LOGGER = LoggerFactory.getLogger(TaRedisCacheManager.class); public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { super(cacheWriter, defaultCacheConfiguration); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, initialCacheNames); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) { super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations); } public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) { super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation); } @Override protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) { MethodCacheExpireConfig cacheable = TaRedisCacheFactory.getCacheExpireConfig(name); if (null != cacheable && cacheable.getExpireTime() > 0) { cacheConfig = entryTtl(name, cacheable.getExpireTime(), cacheConfig); } return super.createRedisCache(name, cacheConfig); } private RedisCacheConfiguration entryTtl(String cacheName, long ttl, @Nullable RedisCacheConfiguration cacheConfig) { Assert.notNull(cacheConfig, "RedisCacheConfiguration is required; it must not be null"); cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); if (LOGGER.isDebugEnabled()) { LOGGER.debug("redisCache {} 過期時(shí)間為{}秒", cacheName, ttl); } return cacheConfig; } }
5、緩存自動(dòng)刷新
在RedisCache
的get
方法中,如果緩存未過期,檢查是否需要進(jìn)行自動(dòng)刷新。
@Override public ValueWrapper get(@Nullable Object o) { if (null == o) { return null; } ValueWrapper wrapper = this.cache.get(o); // 刷新緩存 if (null != wrapper) { SpringContextUtil.getApplicationContext().getBean(TaRedisCacheFactory.class).refreshCache(getName(),o.toString(), this::put); } return wrapper; }
6、TaRedisCacheFactory刷新策略
TaRedisCacheFactory
負(fù)責(zé)緩存的刷新邏輯,確保緩存數(shù)據(jù)的實(shí)時(shí)性。
import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.util.MethodInvoker; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** * @author tangzx * @date 2022/12/17 11:09 */ public class TaRedisCacheFactory { /** * 緩存過期配置 */ private static final ConcurrentHashMap<String, MethodCacheExpireConfig> CACHE_EXPIRE_CONFIG = new ConcurrentHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(TaRedisCacheFactory.class); public TaRedisCacheFactory() { // document why this method is empty } public static void addCacheExpireConfig(String cacheName, MethodCacheExpireConfig methodCacheExpireConfig) { CACHE_EXPIRE_CONFIG.put(cacheName, methodCacheExpireConfig); } public static MethodCacheExpireConfig getCacheExpireConfig(String cacheName) { return CACHE_EXPIRE_CONFIG.get(cacheName); } /** * 刷新緩存 * * @param cacheName 緩存名稱 * @param cacheKey 緩存key */ public void refreshCache(String cacheName, String cacheKey, RefreshCacheFunction f) { MethodCacheExpireConfig cacheable = getCacheExpireConfig(cacheName); if (null == cacheable) { return; } Class<?> targetClass = cacheable.getTarget(); Method method = cacheable.getMethod(); long expireRefreshTime = cacheable.getExpireRefreshTime(); String redisKey = cacheName + cacheKey; long expire = RedisUtil.KeyOps.getExpire(redisKey); if (expire > expireRefreshTime) { return; } String argsStr = cacheKey.split("\\^")[1]; Object[] args = JSON.parseObject(argsStr, Object[].class); if (null == args) { return; } try { // 創(chuàng)建方法執(zhí)行器 MethodInvoker methodInvoker = new MethodInvoker(); methodInvoker.setArguments(args); methodInvoker.setTargetClass(targetClass); methodInvoker.setTargetMethod(method.getName()); methodInvoker.setTargetObject(AopProxyUtils.getSingletonTarget(SpringContextUtil.getApplicationContext().getBean(targetClass))); methodInvoker.prepare(); Object invoke = methodInvoker.invoke(); //然后設(shè)置進(jìn)緩存和重新設(shè)置過期時(shí)間 f.put(cacheKey, invoke); RedisUtil.KeyOps.expire(cacheKey, cacheable.getExpireTime(), TimeUnit.SECONDS); } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { LOGGER.error("刷新緩存失敗:" + e.getMessage(), e); } } }
7、MethodCacheExpireConfig
import lombok.Builder; import lombok.Data; import java.lang.reflect.Method; /** * @author Tzx * @date 2022/12/17 11:10 */ @Data @Builder public class MethodCacheExpireConfig { /** * 緩存過期時(shí)間 */ private long expireTime; /** * 緩存過期自動(dòng)刷新閾值 */ private long expireRefreshTime; /** * 是否自動(dòng)刷新 */ private boolean autoRefresh; /** * 類對(duì)象 */ private Class<?> target; /** * 緩存方法 */ private Method method; }
8、RefreshCacheFunction
/** * @author tangzx */ @FunctionalInterface public interface RefreshCacheFunction { /** * 緩存put * * @param key key * @param value value */ void put(String key, Object value); }
9、DurationUtils
import java.time.Duration; /** * @author Tzx * @date 2022/12/17 12:04 */ public class DurationUtils { private DurationUtils(){ // 2022/12/18 } public static Duration parseDuration(String ttlStr) { String timeUnit = ttlStr.substring(ttlStr.length() - 1); switch (timeUnit) { case "d": return Duration.ofDays(parseLong(ttlStr)); case "h": return Duration.ofHours(parseLong(ttlStr)); case "m": return Duration.ofMinutes(parseLong(ttlStr)); case "s": return Duration.ofSeconds(parseLong(ttlStr)); default: return Duration.ofSeconds(Long.parseLong(ttlStr)); } } private static long parseLong(String ttlStr) { return Long.parseLong(ttlStr.substring(0, ttlStr.length() - 1)); } }
到此這篇關(guān)于SpirngCache、Redis指定過期時(shí)間、到期自動(dòng)刷新的文章就介紹到這了,更多相關(guān)SpirngCache、Redis指定過期時(shí)間 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring boot+spring cache實(shí)現(xiàn)兩級(jí)緩存(redis+caffeine)
- spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
- 詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時(shí)間解決方案
- 淺談SpringCache與redis集成實(shí)現(xiàn)緩存解決方案
- SpringBoot中Shiro緩存使用Redis、Ehcache的方法
- Spring Cache手動(dòng)清理Redis緩存
- Spring boot redis cache的key的使用方法
- spring的Cache注解和redis的區(qū)別說明
- SpringBoot整合Redis使用@Cacheable和RedisTemplate
- springboot接入cachecloud redis示例實(shí)踐
相關(guān)文章
SpringBoot整合Web開發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學(xué)名是Controller增強(qiáng)器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對(duì)于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08mybatis模糊查詢之bind標(biāo)簽和concat函數(shù)用法詳解
大家都知道bind 標(biāo)簽可以使用 OGNL 表達(dá)式創(chuàng)建一個(gè)變量井將其綁定到上下文中,接下來通過本文給大家介紹了mybatis模糊查詢——bind標(biāo)簽和concat函數(shù)用法,需要的朋友可以參考下2022-08-08spring boot啟動(dòng)后直接關(guān)閉了的問題解決
本文主要介紹了spring boot啟動(dòng)后直接關(guān)閉了的問題解決,SpringBoot項(xiàng)目啟動(dòng)后自動(dòng)關(guān)閉的原因是未引入web依賴,導(dǎo)致以普通Java項(xiàng)目運(yùn)行,下面就來介紹一下解決方法,感興趣的可以了解一下2025-02-02深入理解Java設(shè)計(jì)模式之職責(zé)鏈模式
這篇文章主要介紹了JAVA設(shè)計(jì)模式之職責(zé)鏈模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解2021-11-11SpringBoot 中 AutoConfiguration的使用方法
這篇文章主要介紹了SpringBoot 中 AutoConfiguration的使用方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04Java使用定時(shí)器編寫一個(gè)簡(jiǎn)單的搶紅包小游戲
這篇文章主要為大家介紹了Java如何使用定時(shí)器編寫一個(gè)簡(jiǎn)單的搶紅包小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-07-07淺談Hibernate對(duì)象狀態(tài)之間的神奇轉(zhuǎn)換
這篇文章主要介紹了淺談Hibernate對(duì)象狀態(tài)之間的神奇轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09