SpringCache結(jié)合Redis實(shí)現(xiàn)指定過期時(shí)間和到期自動刷新
緩存作為提升應(yīng)用性能的重要手段,其管理策略的合理性直接影響到應(yīng)用的響應(yīng)速度和數(shù)據(jù)一致性。在Spring框架中,Spring Cache提供了一種聲明式緩存的解決方案,而Redis作為高性能的緩存數(shù)據(jù)庫,被廣泛應(yīng)用于緩存實(shí)現(xiàn)。本文將介紹一種通過自定義注解實(shí)現(xiàn)Spring Cache與Redis緩存過期時(shí)間管理及自動刷新的策略。
1、自定義注解CacheExpireConfig
為了更靈活地控制緩存的過期時(shí)間,我們定義了一個名為CacheExpireConfig的自定義注解。此注解支持在方法級別配置緩存的過期時(shí)間和自動刷新時(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、啟動時(shí)加載緩存過期配置
在Spring Boot應(yī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ù)配置動態(tài)設(shè)置每個緩存的過期時(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、緩存自動刷新
在RedisCache的get方法中,如果緩存未過期,檢查是否需要進(jìn)行自動刷新。
@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;
/**
* 緩存過期自動刷新閾值
*/
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));
}
}到此這篇關(guān)于SpirngCache、Redis指定過期時(shí)間、到期自動刷新的文章就介紹到這了,更多相關(guān)SpirngCache、Redis指定過期時(shí)間 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- spring boot+spring cache實(shí)現(xiàn)兩級緩存(redis+caffeine)
- spring整合redis緩存并以注解(@Cacheable、@CachePut、@CacheEvict)形式使用
- 詳解SpringBoot2.0的@Cacheable(Redis)緩存失效時(shí)間解決方案
- 淺談SpringCache與redis集成實(shí)現(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示例實(shí)踐
相關(guān)文章
SpringBoot整合Web開發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學(xué)名是Controller增強(qiáng)器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08
mybatis模糊查詢之bind標(biāo)簽和concat函數(shù)用法詳解
大家都知道bind 標(biāo)簽可以使用 OGNL 表達(dá)式創(chuàng)建一個變量井將其綁定到上下文中,接下來通過本文給大家介紹了mybatis模糊查詢——bind標(biāo)簽和concat函數(shù)用法,需要的朋友可以參考下2022-08-08
spring boot啟動后直接關(guān)閉了的問題解決
本文主要介紹了spring boot啟動后直接關(guān)閉了的問題解決,SpringBoot項(xià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-11
SpringBoot 中 AutoConfiguration的使用方法
這篇文章主要介紹了SpringBoot 中 AutoConfiguration的使用方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
淺談Hibernate對象狀態(tài)之間的神奇轉(zhuǎn)換
這篇文章主要介紹了淺談Hibernate對象狀態(tài)之間的神奇轉(zhuǎn)換,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09

