SpirngCache、Redis指定过期时间、到期自动刷新
创始人
2024-04-25 21:41:57
0

前面简单实现了一个指定过期时间[SpringCache、Redis实现指定过期时间_csdn_Ty的博客-CSDN博客],但是是基于修改cacheName,对springCache设计改动太大,而且不能支持到期自动刷新,所以打算新增一个注解去配置过期时间、过期自动刷新时间。

1、新增自定义注解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、使用

    @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、启动时加载缓存过期配置

import cn.hutool.core.lang.ClassScanner;
import com.yinhai.ta404.module.cache.annotation.CacheExpireConfig;
import com.yinhai.ta404.module.cache.redis.cache.MethodCacheExpireConfig;
import com.yinhai.ta404.module.cache.redis.cache.TaRedisCacheFactory;
import com.yinhai.ta404.module.cache.redis.util.DurationUtils;
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 Tzx* @date 2022/12/17 11:05*/
public class TaRedisCacheConfigListener implements ApplicationListener {@Overridepublic void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent) {// 扫描所有类Set> 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> scanPackage() {// 使用的hutool的类扫描器,如果项目中未使用工具类,可自行实现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,设置过期时间

       

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 initialCacheConfigurations) {super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);}public TaRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map initialCacheConfigurations, boolean allowInFlightCacheCreation) {super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);}@Overrideprotected 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、重写Cache的get方法,在get时如果不为空,检查是否需要刷新

    @Overridepublic 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刷新

import com.alibaba.fastjson.JSON;
import com.yinhai.ta404.module.cache.redis.function.RefreshCacheFunction;
import com.yinhai.training.util.SpringContextUtil;
import com.yinhai.yyy.util.RedisUtil;
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 Tzx* @date 2022/12/17 11:09*/
public class TaRedisCacheFactory {/*** 缓存过期配置*/private static final ConcurrentHashMap 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 {// 创建方法执行器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));}}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...