文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringBoot 如何快速使用 Caffeine 缓存?

2024-12-02 17:55

关注

集成caffeine

caffeine与SpringBoot集成有两种方式:

  1.  
  2.     org.springframework.boot 
  3.     spring-boot-starter-cache 
  4.  
  5.  
  6.     com.github.ben-manes.caffeine 
  7.     caffeine 
  8.     2.6.0 
  9.  

第一种方式

首先配置一个Cache,通过构造者模式构建一个Cache对象,然后后续关于缓存的增删查都是基于这个cache对象。

  1. @Configuration 
  2. public class CacheConfig { 
  3.     @Bean 
  4.     public Cache caffeineCache() { 
  5.         return Caffeine.newBuilder() 
  6.                 // 设置最后一次写入或访问后经过固定时间过期 
  7.                 .expireAfterWrite(60, TimeUnit.SECONDS) 
  8.                 // 初始的缓存空间大小 
  9.                 .initialCapacity(100) 
  10.                 // 缓存的最大条数 
  11.                 .maximumSize(1000) 
  12.                 .build(); 
  13.     } 

第一种方式我们就一一不介绍了,基本上就是使用caffeineCache来根据你自己的业务来操作以下方法

这种方式使用的话是对代码有侵入性的。

第二种方式

  1. @SpringBootApplication 
  2.   @EnableCaching 
  3.   public class DemoApplication { 
  4.       public static void main(String[] args) { 
  5.           SpringApplication.run(DemoApplication.class, args); 
  6.       } 
  1. spring: 
  2.   profiles: 
  3.     active: dev 
  4.   cache: 
  5.     type: CAFFEINE 
  6.     caffeine: 
  7.       spec: maximumSize=500,expireAfterAccess=600s 

如果我们不习惯使用这种方式的配置,当然我们也可以使用JavaConfig的配置方式来代替配置文件。

  1. @Configuration 
  2. public class CacheConfig { 
  3.         @Bean 
  4.         public CacheManager cacheManager() { 
  5.             CaffeineCacheManager cacheManager = new CaffeineCacheManager(); 
  6.             cacheManager.setCaffeine(Caffeine.newBuilder() 
  7.                     // 设置最后一次写入或访问后经过固定时间过期 
  8.                     .expireAfterAccess(600, TimeUnit.SECONDS) 
  9.                     // 初始的缓存空间大小 
  10.                     .initialCapacity(100) 
  11.                     // 缓存的最大条数 
  12.                     .maximumSize(500)); 
  13.             return cacheManager; 
  14.         } 

接下来就是代码中如何来使用这个缓存了。

  1. @Override 
  2. @CachePut(value = "user"key = "#userDTO.id"
  3. public UserDTO save(UserDTO userDTO) { 
  4.     userRepository.save(userDTO); 
  5.     return userDTO; 
  6.  
  7. @Override 
  8. @CacheEvict(value = "user"key = "#id")//2 
  9. public void remove(Long id) { 
  10.     logger.info("删除了id、key为" + id + "的数据缓存"); 
  11.  
  12. @Override 
  13. @Cacheable(value = "user",key = "#id"
  14. public UserDTO getUserById(Long id) { 
  15.     return userRepository.findOne(id); 

上述代码中我们可以看到有几个注解@CachePut、@CacheEvict、@Cacheable我们只需要在方法上标上这几个注解,我们就能够使用缓存了,我们分别来介绍下这几个注解。

@Cacheable

@Cacheable它是既可以标注在类上也可以标注在方法上,当它标记在类上的时候它表述这个类上面的所有方法都会支持缓存,同样的 当它作用在法上面时候它表示这个方法是支持缓存的。比如上面我们代码中的getUserById这个方法第一次缓存里面没有数据,我们会去查询DB,但是第二次来查询的时候就不会走DB查询了,而是直接从缓存里面拿到结果就返回了。

value 属性

key

  1. public static Object generateKey(Object... params) { 
  2.         // 如果方法没有参数 key就是一个 new SimpleKey() 
  3.   if (params.length == 0) { 
  4.    return SimpleKey.EMPTY; 
  5.   } 
  6.   // 如果方法只有一个参数 key就是当前参数 
  7.   if (params.length == 1) { 
  8.    Object param = params[0]; 
  9.    if (param != null && !param.getClass().isArray()) { 
  10.     return param; 
  11.    } 
  12.   } 
  13.   // 如果key是多个参数,key就是new SimpleKey ,不过这个SimpleKey对象的hashCode 和Equals方法是根据方法传入的参数重写的。 
  14.   return new SimpleKey(params); 
  15.  } 

上述代码还是非常好理解的分为三种情况:

小结

上述代码我们可以发现默认生成key只跟我们传入的参数有关系,如果我们有一个类里面如果存在多个没有参数的方法,然后我们使用了默认的缓存生成策略的话,就会造成缓存丢失。或者缓存相互覆盖,或者还有可能会发生ClassCastException 因为都是使用同一个key。比如下面这代码就会发生异常(ClassCastException)。

  1. @Cacheable(value = "user"
  2.   public UserDTO getUser() { 
  3.       UserDTO userDTO = new UserDTO(); 
  4.       userDTO.setUserName("Java金融"); 
  5.       return userDTO; 
  6.   } 
  7.   @Cacheable(value = "user"
  8.   public UserDTO2 getUser1() { 
  9.       UserDTO2 userDTO2 = new UserDTO2(); 
  10.       userDTO2.setUserName2("javajr.cn"); 
  11.       return userDTO2; 
  12.   } 

所以一般不怎么推荐使用默认的缓存生成key的策略。如果非要用的话我们最好自己重写一下,带上方法名字等。类似于如下代码:

  1. @Component 
  2. public class MyKeyGenerator extends SimpleKeyGenerator { 
  3.  
  4.     @Override 
  5.     public Object generate(Object target, Method method, Object... params) { 
  6.         Object generate = super.generate(target, method, params); 
  7.         String format = MessageFormat.format("{0}{1}{2}", method.toGenericString(), generate); 
  8.         return format; 
  9.     } 

自定义key

我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”这也是我们比较推荐的做法:

  1. @Cacheable(value="user"key="#id"
  2.  public UserDTO getUserById(Long id) { 
  3.      UserDTO userDTO = new UserDTO(); 
  4.      userDTO.setUserName("java金融"); 
  5.      return userDTO; 
  6.  } 
  7.  @Cacheable(value="user"key="#p0"
  8.  public UserDTO getUserById1(Long id) { 
  9.      return null
  10.  } 
  11.  @Cacheable(value="user"key="#userDTO.id"
  12.  public UserDTO getUserById2(UserDTO userDTO) { 
  13.      return null
  14.  } 
  15.  @Cacheable(value="user"key="#p0.id"
  16.  public UserDTO getUserById3(UserDTO userDTO) { 
  17.      return null
  18.  } 

@CachePut

@CachePut指定的属性是和@Cacheable一样的,但是它们两个是有区别的,@CachePut标注的方法不会先去查询缓存是否有值,而是每次都会先去执行该方法,然后把结果返回,并且结果也会缓存起来。

为什么是这样的一个流程我们可以去看看它的源码关键代码就是这一行,

  1. Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 

当我们使用方法上有@Cacheable注解的时候再contexts里面会把CacheableOperation加入进去,只有contexts.get(CacheableOperation.class)取到的内容不为空的话,才会去从缓存里面取内容,否则的话cacheHit会直接返回null。至于contexts什么时候加入CacheableOperation的话我们看下SpringCacheAnnotationParser#parseCacheAnnotations这个方法就会明白的。具体的源码就不展示了,感兴趣的可以自己去翻。

@CacheEvict

把缓存中数据删除,用法跟前面两个注解差不多有value和key属性,需要注意一点的是它多了一个属性beforeInvocation

@Caching

这是一个组合注解集成了上面三个注解,有三个属性:cacheable、put和evict,分别用于来指定@Cacheable、@CachePut和@CacheEvict。

小结

第二种方式是侵入式的,它的实现原理也比较简单就是通过切面的方法拦截器来实现,拦截所有的方法,它的核心代码如下:看起来就跟我们的业务代码差不了多少,感兴趣的也可以去瞅一瞅。

  1. if (contexts.isSynchronized()) { 
  2.    CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); 
  3.    if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { 
  4.     Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); 
  5.     Cache cache = context.getCaches().iterator().next(); 
  6.     try { 
  7.      return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker)))); 
  8.     } 
  9.     catch (Cache.ValueRetrievalException ex) { 
  10.      // The invoker wraps any Throwable in a ThrowableWrapper instance so we 
  11.      // can just make sure that one bubbles up the stack. 
  12.      throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause(); 
  13.     } 
  14.    } 
  15.    else { 
  16.     // No caching required, only call the underlying method 
  17.     return invokeOperation(invoker); 
  18.    } 
  19.   } 
  20.  
  21.  
  22.   // Process any early evictions 
  23.   // beforeInvocation 属性是否为true,如果是true就删除缓存 
  24.   processCacheEvicts(contexts.get(CacheEvictOperation.class), true
  25.     CacheOperationExpressionEvaluator.NO_RESULT); 
  26.  
  27.   // Check if we have a cached item matching the conditions 
  28.   Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 
  29.  
  30.   // Collect puts from any @Cacheable miss, if no cached item is found 
  31.   List cachePutRequests = new LinkedList<>(); 
  32.   if (cacheHit == null) { 
  33.    collectPutRequests(contexts.get(CacheableOperation.class), 
  34.      CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); 
  35.   } 
  36.  
  37.   Object cacheValue; 
  38.   Object returnValue; 
  39.  
  40.   if (cacheHit != null && !hasCachePut(contexts)) { 
  41.    // If there are no put requests, just use the cache hit 
  42.    cacheValue = cacheHit.get(); 
  43.    returnValue = wrapCacheValue(method, cacheValue); 
  44.   } 
  45.   else { 
  46.    // Invoke the method if we don't have a cache hit 
  47.    returnValue = invokeOperation(invoker); 
  48.    cacheValue = unwrapReturnValue(returnValue); 
  49.   } 
  50.  
  51.   // Collect any explicit @CachePuts 
  52.   collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); 
  53.  
  54.   // Process any collected put requests, either from @CachePut or a @Cacheable miss 
  55.   for (CachePutRequest cachePutRequest : cachePutRequests) { 
  56.    cachePutRequest.apply(cacheValue); 
  57.   } 
  58.  
  59.   // Process any late evictions 
  60.   processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); 
  61.  
  62.   return returnValue; 
  63.  } 

结束

由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。

感谢您的阅读,十分欢迎并感谢您的关注。 

站在巨人的肩膀上摘苹果: https://www.cnblogs.com/fashflying/p/6908028.html#!comments

来源: java金融内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯