SpringBoot 如何快速使用 Caffeine 缓存?
发布日期:2022-06-18 17:09 点击次数:142
前边咱们有学习Caffeine 《腹地缓存性能之王Caffeine》,而且也提到SpringBoot默许使用的腹地缓存亦然Caffeine啦,今天咱们来望望Caffeine如何与SpringBoot集成的。
集成caffeinecaffeine与SpringBoot集成有两种状貌:
一种是咱们平直引入 Caffeine 依赖,然后使用 Caffeine 智商扫尾缓存。绝顶于使用原生api 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解智商扫尾缓存。SpringCache帮咱们封装了Caffeine pom文献引入<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.6.0</version> </dependency>第一种状貌
率先设置一个Cache,通过构造者时势构建一个Cache对象,然后后续对于缓存的增删查都是基于这个cache对象。
@Configuration public class CacheConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 开导临了一次写入或走访后经过固定时辰逾期 .expireAfterWrite(60, TimeUnit.SECONDS) // 开动的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) .build(); }
第一种状貌咱们就逐一不先容了,基本上即是使用caffeineCache来阐明你我方的业务来操作以下智商
这种状貌使用的话是对代码有侵入性的。
第二种状貌 需要在SpingBoot启动类标上EnableCaching注解,这个玩意跟许多框架都一样,比如咱们肴集成dubbo也需要标上@EnableDubbole注解等。@SpringBootApplication @EnableCaching public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }在application.yml设置咱们的使用的缓存类型、逾期时辰、缓存计策等。
spring: profiles: active: dev cache: type: CAFFEINE caffeine: spec: maximumSize=500,expireAfterAccess=600s
如若咱们不民风使用这种状貌的设置,虽然咱们也不错使用JavaConfig的设置状貌来代替设置文献。
@Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() // 开导临了一次写入或走访后经过固定时辰逾期 .expireAfterAccess(600, TimeUnit.SECONDS) // 开动的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(500)); return cacheManager; }
接下来即是代码中如何来使用这个缓存了。
@Override @CachePut(value = "user", key = "#userDTO.id") public UserDTO save(UserDTO userDTO) { userRepository.save(userDTO); return userDTO; } @Override @CacheEvict(value = "user", key = "#id")//2 public void remove(Long id) { logger.info("删除了id、key为" + id + "的数据缓存"); } @Override @Cacheable(value = "user",key = "#id") public UserDTO getUserById(Long id) { return userRepository.findOne(id); }
上述代码中咱们不错看到有几个注解@CachePut、@CacheEvict、@Cacheable咱们只需要在智商上标上这几个注解,咱们就大致使用缓存了,咱们差别来先容下这几个注解。
@Cacheable@Cacheable它是既不错标注在类上也不错标注在智商上,当它象征在类上的技艺它表述这个类上头的通盘智商都会维持缓存,相似的 当它作用在法上头技艺它暗意这个智商是维持缓存的。比如上头咱们代码中的getUserById这个智商第一次缓存内部没罕有据,咱们会去查询DB,然而第二次来查询的技艺就不会走DB查询了,而是平直从缓存内部拿到成果就复返了。
value 属性 @Cacheable的value属性是必须指定的,其暗意现时智商的复返值是会被缓存在哪个Cache上的,对应Cache的称号。 key @Cacheable的key 有两种状貌一种是咱们我方表露的去指定咱们的key,还有一种默许的生成计策,默许的生成计策是SimpleKeyGenerator这个类,这个生成key的状貌也比拟浅显咱们不错看下它的源码:public static Object generateKey(Object... params) { // 如若智商莫得参数 key即是一个 new SimpleKey() if (params.length == 0) { return SimpleKey.EMPTY; } // 如若智商只须一个参数 key即是现时参数 if (params.length == 1) { Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } // 如若key是多个参数,key即是new SimpleKey ,不外这个SimpleKey对象的hashCode 和Equals智商是阐明智商传入的参数重写的。 return new SimpleKey(params); }
上述代码还口舌常好清醒的分为三种情况:
智商莫得参数,那就new使用一个全局空的SimpleKey对象来手脚key。 智商就一个参数,就使用现时参数来手脚key 智商参数大于1个, 小12萝8禁在线喷水观看就new一个SimpleKey对象来手脚key,new 这个SimpleKey的技艺用传入的参数重写了SimpleKey的hashCode和equals智商, 至于为啥需要重写的原因话,就跟Map用自界说对象来手脚key的技艺必须要重写hashCode和equals智商道理是一样的,因为caffein亦然借助了ConcurrentHashMap来扫尾, 小结上述代码咱们不错发现默许生成key只跟咱们传入的参数关系系,如若咱们有一个类内部如若存在多个莫得参数的智商,然后咱们使用了默许的缓存生成计策的话,就会变成缓存丢失。或者缓存互相隐敝,或者还有可能会发生ClassCastException 因为都是使用归拢个key。比如底下这代码就会发生格外(ClassCastException)。
@Cacheable(value = "user") public UserDTO getUser() { UserDTO userDTO = new UserDTO(); userDTO.setUserName("Java金融"); return userDTO; } @Cacheable(value = "user") public UserDTO2 getUser1() { UserDTO2 userDTO2 = new UserDTO2(); userDTO2.setUserName2("javajr.cn"); return userDTO2; }
是以一般不怎样保举使用默许的缓存生成key的计策。如若非要用的话咱们最佳我方重写一下,带上智商名字等。近似于如下代码:
@Component public class MyKeyGenerator extends SimpleKeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { Object generate = super.generate(target, method, params); String format = MessageFormat.format("{0}{1}{2}", method.toGenericString(), generate); return format; }自界说key
咱们不错通过Spring的EL抒发式来指定咱们的key。这里的EL抒发式不错使用智商参数及它们对应的属性。使用智商参数时咱们不错平直使用“#参数名”或者“#p参数index”这亦然咱们比拟保举的做法:
@Cacheable(value="user",精品国产综合区久久久久久 key="#id") public UserDTO getUserById(Long id) { UserDTO userDTO = new UserDTO(); userDTO.setUserName("java金融"); return userDTO; } @Cacheable(value="user", key="#p0") public UserDTO getUserById1(Long id) { return null; } @Cacheable(value="user", key="#userDTO.id") public UserDTO getUserById2(UserDTO userDTO) { return null; } @Cacheable(value="user", key="#p0.id") public UserDTO getUserById3(UserDTO userDTO) { return null; }@CachePut
@CachePut指定的属性是和@Cacheable一样的,然而它们两个是有区别的,@CachePut标注的智商不会先去查询缓存是否有值,而是每次都会先去实行该智商,然后把成果复返,而且成果也会缓存起来。
为什么是这么的一个进程咱们不错去望望它的源码重要代码即是这一排,
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
beforeInvocation 这个属性需要介意下它的默许值是false,false代表的道理是再执调用智商之前不删除缓存,只须智商实行得手之后才会去删除缓存。开导为true的话调用智商之前会去删除一下缓存,智商实行得手之后还会去调用删除缓存这么即是双删了。如若智商实行格外的话就不会去删除缓存。 allEntrie 是否清空通盘缓存本色,默许值为 false,如若指定为 true,则智商调用后将立即清空通盘缓存 @Caching这是一个组合注解集成了上头三个注解,有三个属性:cacheable、put和evict,差别用于来指定@Cacheable、@CachePut和@CacheEvict。
小结第二种状貌是侵入式的,它的扫尾道理也比拟浅显即是通过切面的智商禁止器来扫尾,禁止通盘的智商,它的中枢代码如下:看起来就跟咱们的业务代码差不了些许,感兴味的也不错去瞅一瞅。
if (contexts.isSynchronized()) { CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT); Cache cache = context.getCaches().iterator().next(); try { return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker)))); } catch (Cache.ValueRetrievalException ex) { // The invoker wraps any Throwable in a ThrowableWrapper instance so we // can just make sure that one bubbles up the stack. throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause(); } } else { // No caching required, only call the underlying method return invokeOperation(invoker); } } // Process any early evictions // beforeInvocation 属性是否为true,如若是true就删除缓存 processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); // Check if we have a cached item matching the conditions Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); // Collect puts from any @Cacheable miss, if no cached item is found List<CachePutRequest> cachePutRequests = new LinkedList<>(); if (cacheHit == null) { collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } Object cacheValue; Object returnValue; if (cacheHit != null && !hasCachePut(contexts)) { // If there are no put requests, just use the cache hit cacheValue = cacheHit.get(); returnValue = wrapCacheValue(method, cacheValue); } else { // Invoke the method if we don't have a cache hit returnValue = invokeOperation(invoker); cacheValue = unwrapReturnValue(returnValue); } // Collect any explicit @CachePuts collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // Process any collected put requests, either from @CachePut or a @Cacheable miss for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(cacheValue); } // Process any late evictions processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; }
截止
由于我方胸无点墨,不免会有疏忽,假如你发现了造作的所在,还望留言给我指出来,我会对其加以修正。
感谢您的阅读,十分宽宥并感谢您的柔顺。
站在忠良的肩膀上摘苹果: https://www.cnblogs.com/fashflying/p/6908028.html#!comments