SpringCache結合Redis實現(xiàn)指定過期時間和到期自動刷新
緩存作為提升應用性能的重要手段,其管理策略的合理性直接影響到應用的響應速度和數(shù)據(jù)一致性。在Spring框架中,Spring Cache提供了一種聲明式緩存的解決方案,而Redis作為高性能的緩存數(shù)據(jù)庫,被廣泛應用于緩存實現(xiàn)。本文將介紹一種通過自定義注解實現(xiàn)Spring Cache與Redis緩存過期時間管理及自動刷新的策略。
1、自定義注解CacheExpireConfig
為了更靈活地控制緩存的過期時間,我們定義了一個名為CacheExpireConfig
的自定義注解。此注解支持在方法級別配置緩存的過期時間和自動刷新時間。
import java.lang.annotation.*; /** * @author tangzx */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheExpireConfig { /** * 緩存過期時間,支持單位天(d)、小時(h)、分鐘(m)、秒鐘(s)(不填單位默認秒) * 例:2h */ String expireTime() default ""; /** * 緩存過期刷新時間,支持單位天(d)、小時(h)、分鐘(m)、秒鐘(s)(不填單位默認秒) * 例:2h */ String expireRefreshTime() default ""; }
2、使用注解
在Spring的@Cacheable
注解基礎上,通過@CacheExpireConfig
注解,我們可以輕松地為特定方法設置緩存過期和刷新策略。
@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、啟動時加載緩存過期配置
在Spring Boot應用啟動時,通過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) { // 如果方法上未同時注解@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à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,設置過期時間
通過重寫RedisCacheManager
,我們可以根據(jù)配置動態(tài)設置每個緩存的過期時間。
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 {} 過期時間為{}秒", cacheName, ttl); } return cacheConfig; } }
5、緩存自動刷新
在RedisCache
的get
方法中,如果緩存未過期,檢查是否需要進行自動刷新。
@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
負責緩存的刷新邏輯,確保緩存數(shù)據(jù)的實時性。
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(); //然后設置進緩存和重新設置過期時間 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 { /** * 緩存過期時間 */ private long expireTime; /** * 緩存過期自動刷新閾值 */ private long expireRefreshTime; /** * 是否自動刷新 */ private boolean autoRefresh; /** * 類對象 */ 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)); } }
到此這篇關于SpirngCache、Redis指定過期時間、到期自動刷新的文章就介紹到這了,更多相關SpirngCache、Redis指定過期時間 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- spring boot+spring cache實現(xiàn)兩級緩存(redis+caffeine)
- spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
- 詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時間解決方案
- 淺談SpringCache與redis集成實現(xiàn)緩存解決方案
- SpringBoot中Shiro緩存使用Redis、Ehcache的方法
- Spring Cache手動清理Redis緩存
- Spring boot redis cache的key的使用方法
- spring的Cache注解和redis的區(qū)別說明
- SpringBoot整合Redis使用@Cacheable和RedisTemplate
- springboot接入cachecloud redis示例實踐
相關文章
SpringBoot整合Web開發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學名是Controller增強器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對于@ControllerAdvice,我們比較熟知的用法是結合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08mybatis模糊查詢之bind標簽和concat函數(shù)用法詳解
大家都知道bind 標簽可以使用 OGNL 表達式創(chuàng)建一個變量井將其綁定到上下文中,接下來通過本文給大家介紹了mybatis模糊查詢——bind標簽和concat函數(shù)用法,需要的朋友可以參考下2022-08-08SpringBoot 中 AutoConfiguration的使用方法
這篇文章主要介紹了SpringBoot 中 AutoConfiguration的使用方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-04-04