转载 spring @Cacheable扩展实现缓存自动过期时间以及自动刷新

这篇具有很好参考价值的文章主要介绍了转载 spring @Cacheable扩展实现缓存自动过期时间以及自动刷新。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

转自:聊聊如何基于spring @Cacheable扩展实现缓存自动过期时间以及自动刷新-腾讯云开发者社区-腾讯云 (tencent.com)

前言

用过spring cache的朋友应该会知道,Spring Cache默认是不支持在@Cacheable上添加过期时间的,虽然可以通过配置缓存容器时统一指定。形如

@Bean
public CacheManager cacheManager(
        @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
   RedisCacheManager cacheManager= new RedisCacheManager(redisTemplate);
    cacheManager.setDefaultExpiration(60);
    Map<String,Long> expiresMap = new HashMap<>();
    expiresMap.put("customUser",30L);
    cacheManager.setExpires(expiresMap);
    return cacheManager;
}

复制

但有时候我们会更习惯通过注解指定过期时间。今天我们就来聊一下如何扩展@Cacheable实现缓存自动过期以及缓存即将到期自动刷新

2

实现注解缓存过期前置知识

SpringCache包含两个顶级接口,Cache和CacheManager,通过CacheManager可以去管理一堆Cache。因此我们要扩展@Cacheable,就脱离不了对Cache和CacheManager进行扩展

其次要实现过期时间,首先是引入的缓存产品,他本身就要支持过期时间,比如引入的缓存为ConcurrentHashMap,他原本就是不支持过期时间,如果要扩展,就要非常耗费精力实现

3

实现注解缓存过期

01

方法一:通过自定义cacheNames方式

形如下

@Cacheable(cacheNames = "customUser#30", key = "#id")

复制

通过#分隔,#后面部分代表过期时间(单位为秒)

实现逻辑步骤为:

1、自定义缓存管理器并继承RedisCacheManager,同时重写createRedisCache方法

示例:

public class CustomizedRedisCacheManager extends RedisCacheManager {

    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        String[] array = StringUtils.delimitedListToStringArray(name, "#");
        name = array[0];
        if (array.length > 1) {
            long ttl = Long.parseLong(array[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); 
        }
        return super.createRedisCache(name, cacheConfig);
    }
}

复制

2、将默认的缓存管理器改成我们自定义的缓存管理器

示例:

@EnableCaching 
@Configuration
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1));

        CustomizedRedisCacheManager redisCacheManager = new CustomizedRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig);
        return redisCacheManager;
    }

}

复制

通过如上2个步骤,即可实现缓存过期

02

方法二:通过自定义派生@Cacheable注解

第一种方法的实现是简单,但缺点是语义不直观,因此得做好宣导以及wiki,不然对于新人来说,他可能都不知道cacheName用#分割是代表啥意思

方法二的实现逻辑步骤如下

1、自定义注解LybGeekCacheable

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Cacheable(cacheManager = CacheConstant.CUSTOM_CACHE_MANAGER,keyGenerator = CacheConstant.CUSTOM_CACHE_KEY_GENERATOR)
public @interface LybGeekCacheable {


  @AliasFor(annotation = Cacheable.class,attribute = "value")
  String[] value() default {};


  @AliasFor(annotation = Cacheable.class,attribute = "cacheNames")
  String[] cacheNames() default {};


  @AliasFor(annotation = Cacheable.class,attribute = "key")
  String key() default "";


  @AliasFor(annotation = Cacheable.class,attribute = "keyGenerator")
  String keyGenerator() default "";


  @AliasFor(annotation = Cacheable.class,attribute = "cacheResolver")
  String cacheResolver() default "";


  @AliasFor(annotation = Cacheable.class,attribute = "condition")
  String condition() default "";


  @AliasFor(annotation = Cacheable.class,attribute = "unless")
  String unless() default "";


  @AliasFor(annotation = Cacheable.class,attribute = "sync")
  boolean sync() default false;


   long expiredTimeSecond() default 0;


   long preLoadTimeSecond() default 0;


}

复制

大部分注解和@Cacheable保持一致,新增expiredTimeSecond缓存过期时间以及缓存自动刷新时间preLoadTimeSecond

2、自定义缓存管理器并继承RedisCacheManager并重写loadCaches和createRedisCache

public class CustomizedRedisCacheManager extends RedisCacheManager implements BeanFactoryAware {

    private Map<String, RedisCacheConfiguration> initialCacheConfigurations;

    private RedisTemplate cacheRedisTemplate;

    private RedisCacheWriter cacheWriter;

    private DefaultListableBeanFactory beanFactory;

    private RedisCacheConfiguration defaultCacheConfiguration;

    protected CachedInvocation cachedInvocation;


    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations,RedisTemplate cacheRedisTemplate) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
        this.initialCacheConfigurations = initialCacheConfigurations;
        this.cacheRedisTemplate = cacheRedisTemplate;
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfiguration = defaultCacheConfiguration;
        //采用spring事件驱动亦可
        //EventBusHelper.register(this);
    }

    public Map<String, RedisCacheConfiguration> getInitialCacheConfigurations() {
        return initialCacheConfigurations;
    }

    @Override
    protected Collection<RedisCache> loadCaches() {
        List<RedisCache> caches = new LinkedList<>();

        for (Map.Entry<String, RedisCacheConfiguration> entry : getInitialCacheConfigurations().entrySet()) {
            caches.add(createRedisCache(entry.getKey(), entry.getValue()));
        }
        return caches;
    }

    @Override
    public RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
       CustomizedRedisCache customizedRedisCache = new CustomizedRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfiguration);
       return customizedRedisCache;
    }

}

复制

3、在spring bean初始化完成后,设置缓存过期时间,并重新初始化缓存。

Component
@Slf4j
public class CacheExpireTimeInit implements SmartInitializingSingleton, BeanFactoryAware {
    
    private DefaultListableBeanFactory beanFactory;
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory)beanFactory;
    }

    @Override
    public void afterSingletonsInstantiated() {
        Map<String, Object> beansWithAnnotation = beanFactory.getBeansWithAnnotation(Component.class);
        if(MapUtil.isNotEmpty(beansWithAnnotation)){
            for (Object cacheValue : beansWithAnnotation.values()) {
                ReflectionUtils.doWithMethods(cacheValue.getClass(), method -> {
                    ReflectionUtils.makeAccessible(method);
                    boolean cacheAnnotationPresent = method.isAnnotationPresent(LybGeekCacheable.class);
                    if(cacheAnnotationPresent){
                         LybGeekCacheable lybGeekCacheable = method.getAnnotation(LybGeekCacheable.class);
                          CacheHelper.initExpireTime(lybGeekCacheable);
                    }

                });
            }
            CacheHelper.initializeCaches();
        }
    }

复制

注: 为啥要重新初始化缓存,主要是为了一开始默认的是没设置缓存过期,重新初始化是为了设置过期时间。为啥调用initializeCaches()这个方法,看下官方描述就知道了

/**
   * Initialize the static configuration of caches.
   * <p>Triggered on startup through {@link #afterPropertiesSet()};
   * can also be called to re-initialize at runtime.
   * @since 4.2.2
   * @see #loadCaches()
   */
  public void initializeCaches() {
    Collection<? extends Cache> caches = loadCaches();

    synchronized (this.cacheMap) {
      this.cacheNames = Collections.emptySet();
      this.cacheMap.clear();
      Set<String> cacheNames = new LinkedHashSet<>(caches.size());
      for (Cache cache : caches) {
        String name = cache.getName();
        this.cacheMap.put(name, decorateCache(cache));
        cacheNames.add(name);
      }
      this.cacheNames = Collections.unmodifiableSet(cacheNames);
    }
  }

复制

他就是在运行的时候,可以重新初始化缓存

4、将默认的缓存管理器改成我们自定义的缓存管理器

@Bean(CacheConstant.CUSTOM_CACHE_MANAGER)
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory,RedisTemplate cacheRedisTemplate) {

        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1));

        Map<String, RedisCacheConfiguration> initialCacheConfiguration = new HashMap<>();

        return new CustomizedRedisCacheManager(redisCacheWriter,defaultCacheConfig,initialCacheConfiguration,cacheRedisTemplate);
    }

复制

5、测试

@LybGeekCacheable(cacheNames = "customUser", key = "#id",expiredTimeSecond = 30)
    public User getUserFromRedisByCustomAnno(String id){
        System.out.println("get user with id by custom anno: 【" + id + "】");
        Faker faker = Faker.instance(Locale.CHINA);
        return User.builder().id(id).username(faker.name().username()).build();

    }

复制

@Test
    public void testCacheExpiredAndPreFreshByCustom() throws Exception{
        System.out.println(userService.getUserFromRedisByCustomAnno("1"));

    }

复制

cacheable设置过期时间,spring,缓存,java

以上就是扩展缓存过期的实现主要方式了,接下来我们来聊一下缓存自动刷新

4

缓存自动刷新

一般来说,当缓存失效时,请求就会打到后端的数据库上,此时可能就会造成缓存击穿现象。因此我们在缓存即将过期时主动刷新缓存,提高缓存的命中率,进而提高性能。

spring4.3的@Cacheable提供了一个sync属性。当缓存失效后,为了避免多个请求打到数据库,系统做了一个并发控制优化,同时只有一个线程会去数据库取数据其它线程会被阻塞

5

缓存即将到期自动刷新

1、封装缓存注解对象CachedInvocation

/**
 * @description: 标记了缓存注解的方法类信息,用于主动刷新缓存时调用原始方法加载数据
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public final class CachedInvocation {

    private CacheMetaData metaData;
    private Object targetBean;
    private Method targetMethod;
    private Object[] arguments;


    public Object invoke()
            throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        final MethodInvoker invoker = new MethodInvoker();
        invoker.setTargetObject(this.getTargetBean());
        invoker.setArguments(this.getArguments());
        invoker.setTargetMethod(this.getTargetMethod().getName());
        invoker.prepare();
        return invoker.invoke();
    }


}

复制

2、编写一个获取即将到期时间参数切面,并进行事件发布调用对象CachedInvocation

@Component
@Aspect
@Slf4j
@Order(2)
public class LybGeekCacheablePreLoadAspect {

    @Autowired
    private ApplicationContext applicationContext;


    @SneakyThrows
    @Around(value = "@annotation(lybGeekCacheable)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint,LybGeekCacheable lybGeekCacheable){
        buildCachedInvocationAndPushlish(proceedingJoinPoint,lybGeekCacheable);
        Object result = proceedingJoinPoint.proceed();
        return result;

    }

    private void buildCachedInvocationAndPushlish(ProceedingJoinPoint proceedingJoinPoint,LybGeekCacheable lybGeekCacheable){
        Method method = this.getSpecificmethod(proceedingJoinPoint);
        String[] cacheNames = getCacheNames(lybGeekCacheable);
        Object targetBean = proceedingJoinPoint.getTarget();
        Object[] arguments = proceedingJoinPoint.getArgs();
        KeyGenerator keyGenerator = SpringUtil.getBean(CacheConstant.CUSTOM_CACHE_KEY_GENERATOR,KeyGenerator.class);
        Object key = keyGenerator.generate(targetBean, method, arguments);
        CachedInvocation cachedInvocation = CachedInvocation.builder()
                .arguments(arguments)
                .targetBean(targetBean)
                .targetMethod(method)
                .metaData(CacheMetaData.builder()
                        .cacheNames(cacheNames)
                        .key(key)
                        .expiredTimeSecond(lybGeekCacheable.expiredTimeSecond())
                        .preLoadTimeSecond(lybGeekCacheable.preLoadTimeSecond())
                        .build()
                )
                .build();
      // EventBusHelper.post(cachedInvocation);
        applicationContext.publishEvent(cachedInvocation);
    }

复制

3、自定义缓存管理器,接收CachedInvocation

示例

public class CustomizedRedisCacheManager extends RedisCacheManager implements BeanFactoryAware {

 
    //@Subscribe
    @EventListener
    private void doWithCachedInvocationEvent(CachedInvocation cachedInvocation){
        this.cachedInvocation = cachedInvocation;
    }

复制

4、自定义cache并重写get方法

@Slf4j
public class CustomizedRedisCache extends RedisCache {

    private ReentrantLock lock = new ReentrantLock();

    public CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter,cacheConfig);
    }

    @Override
    @Nullable
    public ValueWrapper get(Object key) {
        ValueWrapper valueWrapper = super.get(key);
        CachedInvocation cachedInvocation = CacheHelper.getCacheManager().getCachedInvocation();
        long preLoadTimeSecond = cachedInvocation.getMetaData().getPreLoadTimeSecond();
        if(ObjectUtil.isNotEmpty(valueWrapper) && preLoadTimeSecond > 0){
            String cacheKey = createCacheKey(key);
            RedisTemplate cacheRedisTemplate = CacheHelper.getCacheManager().getCacheRedisTemplate();
            Long ttl = cacheRedisTemplate.getExpire(cacheKey, TimeUnit.SECONDS);
            if(ObjectUtil.isNotEmpty(ttl) && ttl <= preLoadTimeSecond){
                log.info(">>>>>>>>>>> cacheKey:{}, ttl: {},preLoadTimeSecond: {}",cacheKey,ttl,preLoadTimeSecond);
                ThreadPoolUtils.execute(()->{
                     lock.lock();
                     try{
                         CacheHelper.refreshCache(super.getName());
                     }catch (Exception e){
                         log.error("{}",e.getMessage(),e);
                     }finally {
                         lock.unlock();
                     }
                });
            }


        }
        return valueWrapper;
    }




}

复制

5、缓存即将到期主动刷新缓存方法

public static void refreshCache(String cacheName){
        boolean isMatchCacheName = isMatchCacheName(cacheName);
        if(isMatchCacheName){
            CachedInvocation cachedInvocation = getCacheManager().getCachedInvocation();
            boolean invocationSuccess;
            Object computed = null;
            try {
                computed = cachedInvocation.invoke();
                invocationSuccess = true;
            } catch (Exception ex) {
                invocationSuccess = false;
                log.error(">>>>>>>>>>>>>>>>> refresh cache fail",ex.getMessage(),ex);
            }

            if (invocationSuccess) {
                    Cache cache = getCacheManager().getCache(cacheName);
                    if(ObjectUtil.isNotEmpty(cache)){
                        Object cacheKey = cachedInvocation.getMetaData().getKey();
                        cache.put(cacheKey, computed);
                        log.info(">>>>>>>>>>>>>>>>>>>> refresh cache with cacheName-->【{}】,key--> 【{}】 finished !",cacheName,cacheKey);
                    }
            }
        }

    }

复制

6、测试

@LybGeekCacheable(cacheNames = "customUserName", key = "#username",expiredTimeSecond = 20,preLoadTimeSecond = 15)
    public User getUserFromRedisByCustomAnnoWithUserName(String username){
        System.out.println("get user with username by custom anno: 【" + username + "】");
        Faker faker = Faker.instance(Locale.CHINA);
        return User.builder().id(faker.idNumber().valid()).username(username).build();

    }

复制

@Test
    public void testCacheExpiredAndPreFreshByCustomWithUserName() throws Exception{
        System.out.println(userService.getUserFromRedisByCustomAnnoWithUserName("zhangsan"));

        TimeUnit.SECONDS.sleep(5);

        System.out.println("sleep 5 second :" + userService.getUserFromRedisByCustomAnnoWithUserName("zhangsan"));

        TimeUnit.SECONDS.sleep(10);

        System.out.println("sleep 10 second :" + userService.getUserFromRedisByCustomAnnoWithUserName("zhangsan"));

        TimeUnit.SECONDS.sleep(5);

        System.out.println("sleep 5 second :" + userService.getUserFromRedisByCustomAnnoWithUserName("zhangsan"));

    }

复制

cacheable设置过期时间,spring,缓存,java

6

总结

本文主要介绍了如何基于spring @Cacheable扩展实现缓存自动过期时间以及缓存即将到期自动刷新。

不知道有没有朋友会有疑问,为啥@Cacheable不提供一个ttl属性,毕竟也不是很难。在我看来,spring更多提供的是一个通用的规范和标准,如果定义的缓存,本身不支持ttl,你在@Cacheable里面配置ttl就不合适了,有时候实现一个组件或者框架,考虑的是不是能不能实现,而是有没有必要实现,更多是一种权衡和取舍文章来源地址https://www.toymoban.com/news/detail-772259.html

到了这里,关于转载 spring @Cacheable扩展实现缓存自动过期时间以及自动刷新的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 前端接口请求支持内容缓存和过期时间

    支持用户自定义缓存时间,在规则时间内读取缓存内容,超出时间后重新请求接口 首先封装一下 axios,这一步可做可不做。但是在实际开发场景中都会对 axios 做二次封装,我们在二次封装的 axios 基础上继续封装,增加支持缓存功能 request.js 新建 catchAjax.js ,当我们想用接口

    2024年02月06日
    浏览(37)
  • SpringBoot通过@Cacheable注解实现缓存功能

    Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107) 注解简化我们的开发。 其使用方法和原理都类似于 Spring 对事务管理的支持,Spring Cache 是作用在方法上的,其核

    2024年02月10日
    浏览(37)
  • 【微信小程序】缓存过期时间的相关设置

      每个微信小程序都可以有自己的本地缓存,可以通过 wx.setStorage(wx.setStorageSync) 、 wx.getStorage(wx.getStorageSync) 、 wx.clearStorage(wx.clearStorageSync) 可以对本地缓存进行设置、获取和清理。   但是微信默认设置了缓存是无限长的过期时限,这对于我们的小程序开发,是

    2024年02月12日
    浏览(56)
  • SpringCache 框架使用以及序列化和缓存过期时间问题的解决

    目录 为什么使用Spring Cache 如何使用Spring Cache 1 加依赖 2 开启缓存 3 加缓存注解 序列化以及过期时间等问题 解决方案:自定义序列化方式 1.自定义序列化方式并设置白名单 2.配置并设置缓存的过期时间         缓存有诸多的好处,于是大家就摩拳擦掌准备给自己的应用加

    2024年02月15日
    浏览(40)
  • 【Spring boot】RedisTemplate中String、Hash、List设置过期时间

    时间类型:TimeUnit import java.util.concurrent.TimeUnit; TimeUnit.SECONDS:秒 TimeUnit.MINUTES:分 TimeUnit.HOURS:时 TimeUnit.DAYS:日 TimeUnit.MILLISECONDS:毫秒 TimeUnit.MILLISECONDS:微秒 TimeUnit.NANOSECONDS:纳秒 Java对于Redis的封装不是能满足所有的业务需求的,但是我们可以通过lua脚本来直接向Redis发送命

    2024年02月04日
    浏览(32)
  • 04.利用Redis国逻辑过期实现缓存功能---解决缓存击穿

    提示:学习如何利用Redis逻辑过期实现添加缓存功能解决缓存击穿 缓存击穿讲解图 : 解决方案: 采用互斥锁 采用逻辑过期 1. 准备pom环境 2. 配置ThreadLocal和过滤器 3. RedisData接收数据 3. Controller层:负责接收请求和向下分配 4. Service层:负责业务的处理逻辑

    2024年02月13日
    浏览(55)
  • @Cacheable 注解(指定缓存位置)

    1、缓存使用步骤:@Cacheable这个注解,用它就是为了使用缓存的。所以我们可以先说一下缓存的使用步骤: 1、开启基于注解的缓存,使用 @EnableCaching 标识在 SpringBoot 的主启动类上。 2、标注缓存注解即可  使用  @Cacheable  注解就可以将运行结果缓存,以后查询相同的数据,

    2024年02月07日
    浏览(50)
  • SpringBoot的Cacheable缓存注解

    当我们的应用程序需要频繁地读取和写入数据时,为了提高应用程序的性能,我们通常会使用缓存技术。Spring Boot 提供了一种简单而强大的缓存框架,它可以轻松地将数据缓存到 Redis 中。 在 Spring Boot 中可以在方法上简单的加上注解实现缓存。 首先,您需要在您的项目中添加

    2024年02月10日
    浏览(41)
  • redis缓存神器:@Cacheable注解

    在之前的文章中,我们写了redis结合springboot做缓存分页的方法: 在 Spring Boot 中结合 Redis 进行缓存分页数据,可以通过以下步骤实现: 在 pom.xml 文件中添加 Redis 相关依赖: 在 application.properties 文件中配置 Redis 连接信息: 创建一个 RedisTemplate 对象,用于操作 Redis 缓存: 在

    2024年02月06日
    浏览(48)
  • SpringBoot 缓存之 @Cacheable 详细介绍

    1、缓存介绍 Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。 其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方

    2024年04月27日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包