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

 更新时间:2024年08月23日 10:35:23   作者:csdn_Ty  
本文主要介绍了SpringCache结合Redis实现指定过期时间和到期自动刷新,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

缓存作为提升应用性能的重要手段,其管理策略的合理性直接影响到应用的响应速度和数据一致性。在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 = 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,设置过期时间

通过重写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、缓存自动刷新

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(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));
    }

}

到此这篇关于SpirngCache、Redis指定过期时间、到期自动刷新的文章就介绍到这了,更多相关SpirngCache、Redis指定过期时间 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • spring配置不扫描service层的原因解答

    spring配置不扫描service层的原因解答

    这篇文章主要介绍了spring配置不扫描service层的原因解答,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 详解Java中ThreadLocal类型及简单用法

    详解Java中ThreadLocal类型及简单用法

    ThreadLocal实例通常是希望将状态与线程关联起来的类中的私有静态字段,下面通过例子给大家详细介绍Java中ThreadLocal类型及简单用法,感兴趣的朋友跟随小编一起看看吧
    2021-10-10
  • 通过weblogic API解析如何获取weblogic中服务的IP和端口操作

    通过weblogic API解析如何获取weblogic中服务的IP和端口操作

    这篇文章主要介绍了通过weblogic API解析如何获取weblogic中服务的IP和端口操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • Java编程实现统计数组中各元素出现次数的方法

    Java编程实现统计数组中各元素出现次数的方法

    这篇文章主要介绍了Java编程实现统计数组中各元素出现次数的方法,涉及java针对数组的遍历、比较、运算等相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • Java实现将导出带格式的Excel数据到Word表格

    Java实现将导出带格式的Excel数据到Word表格

    在Word中制作报表时,我们经常需要将Excel中的数据复制粘贴到Word中,这样则可以直接在Word文档中查看数据而无需打开另一个Excel文件。本文将通过Java应用程序详细介绍如何把带格式的Excel数据导入Word表格。希望这篇文章能对大家有所帮助
    2022-11-11
  • Java中的static关键字全面解析

    Java中的static关键字全面解析

    这篇文章主要介绍了Java中的static关键字全面解析的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • Java填充替换数组元素实例详解

    Java填充替换数组元素实例详解

    这篇文章主要通过两个实例说明Java填充和替换数组中元素的方法,需要的朋友可以参考下。
    2017-08-08
  • Java 中 Date 与 Calendar 之间的编辑与转换实例详解

    Java 中 Date 与 Calendar 之间的编辑与转换实例详解

    这篇文章主要介绍了Java 中 Date 与 Calendar 之间的编辑与转换 ,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07
  • Java利用MD5加盐实现对密码进行加密处理

    Java利用MD5加盐实现对密码进行加密处理

    在开发的时候,有一些敏感信息是不能直接通过明白直接保存到数据库的。最经典的就是密码了。如果直接把密码以明文的形式入库,不仅会泄露用户的隐私,对系统也是极其的不厉。本文就来和大家介绍一下如何对密码进行加密处理,感兴趣的可以了解一下
    2023-02-02
  • Servlet文件的上传与下载详解

    Servlet文件的上传与下载详解

    很多朋友不清楚在Servlet中怎么上传下载文件,谈到这个问题,首先需要我们掌握开发servlet的步骤,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-06-06

最新评论