introduction
Earlier we have to learn Caffeine
king of the local cache performance of Caffeine , and also mentioned SpringBoot
local cache is used by default Caffeine
friends, today we take a look at Caffeine
how to SpringBoot
integration.
Integrated caffeine
There are two ways to integrate caffeine
and SpringBoot
- One is that we directly introduce the
Caffeine
dependency, and then use theCaffeine
method to implement caching. Equivalent to using native api Introduce
Caffeine
andSpring Cache
dependencies, and use theSpringCache
annotation method to implement caching. SpringCache helped us encapsulate Caffeine
Pom file introduction<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>
The first way
First configure a
Cache
Cache
object through the constructor mode, and then add, delete and check the cache based on thiscache
object.@Configuration public class CacheConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 设置最后一次写入或访问后经过固定时间过期 .expireAfterWrite(60, TimeUnit.SECONDS) // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) .build(); }
We will not introduce the first method one by one. Basically, use
caffeineCache
to operate the following methods according to your own business.
Used in this way is intrusive to the code.The second way
EnableCaching
annotation on the SpingBoot startup class. This thing is the same as many frameworks. For example, our food integrationdubbo
also needs to be marked with the@EnableDubbole
annotation.@SpringBootApplication @EnableCaching public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
Configure the type of cache we use, expiration time, cache strategy, etc. at
application.yml
spring: profiles: active: dev cache: type: CAFFEINE caffeine: spec: maximumSize=500,expireAfterAccess=600s
If we are not accustomed to using this configuration, of course we can also use
JavaConfig
configuration instead of configuration files.@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; }
The next step is how to use this cache in the code
@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); }
In the above code, we can see that there are several annotations
@CachePut、@CacheEvict、@Cacheable
We only need to mark these annotations on the method, and we can use the cache. Let's introduce these annotations separately.@Cacheable
@Cacheable
It can be marked either on the class or on the method. When it is marked on the class, it states that all methods on this class will support caching, the same
When it acts on the method, it indicates that the method supports caching. For example, thegetUserById
our code above has no data in the cache for the first time, we will queryDB
, but the second query will notDB
, but directly get the result from the cache and return it NS.value attribute
@Cacheable
ofvalue
attributes must be specified, and its return value indicates the current process will be cached in whichCache
the correspondingCache
name.key
@Cacheable
ofkey
. One is to specify ourkey
displayed by ourselves, and there is also a default generation strategy. The default generation strategy isSimpleKeyGenerator
. The way to generatekey
is relatively simple. We can look at it. The source code: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); }
The above code is still very well understood and divided into three situations:
- The method has no parameters, so new uses a globally empty
SimpleKey
object askey
. - If the method has only one parameter, use the current parameter as
key
The method parameter is greater than
1
months, itnew
aSimpleKey
object askey
,new
thisSimpleKey
when a parameter rewrite the incomingSimpleKey
ofhashCode
andequals
method,
As for the reasons why need to rewrite the words, just likeMap
with custom objects askey
time must be rewrittenhashCode
andequals
is the same principle of the method, becausecaffein
also help theConcurrentHashMap
to achieve,summary
In the above code, we can find that the default generation of
key
only related to the parameters we passed in. If we have multiple methods without parameters in a class, and then we use the default cache generation strategy, it will cause cache loss.
Either the caches cover each other, orClassCastException
may happen because they all use the samekey
. For example, the following code willClassCastException
an exception (0618482212674c)@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; }
Therefore, it is generally not recommended to use the default cache generation strategy of
key
If we have to use it, we'd better rewrite it ourselves, including the method name, etc. Similar to the following code:@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; }
Custom key
We can
Spring
specify our EL expressionskey
. The EL expression here can use method parameters and their corresponding attributes.
When using method parameters, we can directly use "name" or "
index", which is also our recommended practice:
@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
specified property is and@Cacheable
the same, but they are two distinct,@CachePut
method annotated will not go to check whether the cache has a value, but each will go to perform the method, and the results returned, and The results will also be cached.![在这里插入图片描述](https://img-blog.csdnimg.cn/ff516023113046adbf86caaea6e499f6.png)
Why is it such a process? We can go and see the key code of its source code is this line,
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
When we use the method with @Cacheable
annotation, then contexts
will add CacheableOperation
, only if the content retrieved by contexts.get(CacheableOperation.class) is not empty, will the content be fetched from the cache, otherwise cacheHit
will Return directly to null
. As for when contexts will be added to CacheableOperation, we will understand the method SpringCacheAnnotationParser#parseCacheAnnotations
The specific source code will not be shown, and those who are interested can read it by themselves.
@CacheEvict
Delete the data in the cache. The usage is similar to the previous two annotations. There are value and key attributes. One thing to note is that it has an additional attribute beforeInvocation
beforeInvocation
This attribute needs to pay attention to its default value is false, false means that the cache will not be deleted before the method is called, and the cache will be deleted only after the method is successfully executed. If it is set totrue
, the cache will be deleted before calling the method, and the delete cache will be called after the method is executed successfully. This is double deletion. If the method executes abnormally, the cache will not be deleted.allEntrie
Whether to clear all cached content, the default value isfalse
, if specified astrue
, all caches will be cleared immediately after the method is called
@Caching
This is a combined annotation that integrates the above three annotations and has three attributes: cacheable, put and evict, which are used to specify
@Cacheable
, @CachePut
and @CacheEvict
, respectively.
summary
The second method is intrusive, and its implementation principle is relatively simple. It is implemented through the aspect method interceptor. It intercepts all methods. Its core code is as follows: it seems to be indistinguishable from our business code. Those who are interested can also take a look.
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;
}
Finish
- Due to my lack of knowledge, there will inevitably be mistakes. If you find something wrong, please leave a message to point it out and I will correct it.
- If you think the article is not bad, your forwarding, sharing, admiration, liking, and commenting are your greatest encouragement to me.
- Thank you for reading, welcome and thank you for your attention.
Standing on the shoulders of giants and picking apples:
https://www.cnblogs.com/fashflying/p/6908028.html#!comments
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。