开发者

SpringCache结合Redis实现指定过期时间和到期自动刷新

目录
  • 1、自定义注解CacheExpireConfig
  •  2、使用注解
  • 3、启动时加载缓存过期配置
  • 4、重写RedisCacheManager,设置过期时间
  • 5、缓存自动刷新
  • 6、TaRedisCacheFactory刷新策略
  • 7、MethodCacheExpireConfig
  • 8、RefreshCacheFunction
  • 9、DurationUtils

缓存作为提升应用性能的重要手段,其管理策略的合理性直接影响到应用的响应速度和数据一致性。在Spring框架中,Spring Cache提供了一种声明式缓存的解决方案,而Redis作为高性能的缓存数据库,被广泛应用于缓存实现。本文将介绍一种通过自定义注解实现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监听器,扫描所有类和方法,加载带有@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 = cacheExpireCandroidonfig.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,设置过期时间

通过重写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 = ent编程客栈ryTtl(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、缓存自动刷新

RedisCacheget方法中,如果缓存未过期,检查是否需要进行自动刷新。

    @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负责缓存的刷新逻辑,确保缓存数据的实时性。

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(cajscheName, 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":
       http://www.devze.com         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指定过期时间 内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)! 

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜