SpirngCache、Redis指定过期时间、到期自动刷新

8 篇文章 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<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的类扫描器,如果项目中未使用工具类,可自行实现
        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<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、重写Cache的get方法,在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刷新

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<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 {
            // 创建方法执行器
            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));
    }

}

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值