CacheAble、CachePut、CacheEvict的缓存注解底层逻辑解析
目录
一、前言
- 本文章大致讲一下这三个注解的对被注解方法进行的核心的逻辑处理。
- 本文章只对这三个注解的使用做简单介绍,更多的用法可以观看官方教程或是其他博主的文章。
如果本文章由错误之处,还望大家指出,共同讨论,共同进步。
二、@Cacheable @CachePut @CacheEvict 功能
提示:如果已经掌握的同学可以直接看下一节。
1. @Cacheable
作用
- 简单描述:这个注解是根据cacheNames+key查询缓存。
- 深度描述:@Cacheable在执行本方法之前回去向缓存中获取一次数据。如果获取到就直接将此缓存值放回本方法的返回值中;未能获取到就执行本方法,将本方法的结果放入缓存中。这样下次的在执行本方法就用缓存使用了。
代码使用举例
@Service
@AllArgsConstructor
public class RoleServiceImpl implements IRoleService {
private final RoleMapper roleMapper;
/**
* @Cacheable在执行本方法(getOneById)之前回去向缓存中获取一次数据
* 如果获取到就直接将此缓存值放回本方法的返回值中,
* 获取未能获取到就执行本方法,将本方法的结果放入缓存中。
*/
@Override
@Cacheable(cacheNames = "role", key = "#p0")
public Role getOneById(Long id) {
return this.roleMapper.selectById(id);
}
}
2. @CachePut
作用
- 简单描述:
这个注解是根据cacheNames+key增加缓存的,遇到同名就覆盖。 - 深度描述:
@CachePut会将此方法(getOneById)的返回值,放进指定的缓存中。所以本注解具备对缓存的新增和修改(覆盖)的功能。
代码使用举例
@Service
@AllArgsConstructor
public class RoleServiceImpl implements IRoleService {
private final RoleMapper roleMapper;
/**
* @CachePut会将此方法(getOneById)的返回值,放进指定的缓存中
* 所以具备对缓存的新增和修改(覆盖)的功能。
*/
@Override
@CachePut(cacheNames = "role", key = "#p0")
public Role getOneById(Long id) {
return this.roleMapper.selectById(id);
}
}
3. @CacheEvict
作用
- 简单描述:这个注解是根据cacheNames+key删除缓存的。
- 深度描述:@CacheEvict会将满足key的缓存删除,对该方法(getOneById)的返回值不会做任何的处理。但是要记住这个删除
默认
是在本方法结束之后
进行的。
@Service
@AllArgsConstructor
public class RoleServiceImpl implements IRoleService {
private final RoleMapper roleMapper;
/**
*@CacheEvict会将满足key的缓存删除,对该方法(getOneById)的返回值不会做任何的处理。
*/
@Override
@CacheEvict(cacheNames = "role", key = "#p0")
public Role getOneById(Long id) {
return this.roleMapper.selectById(id);
}
}
三、@Cacheable @CachePut @CacheEvict 底层处理方式
1. Springboot-cache处理核心类 CacheAspectSupport.java
对于本类来说,最重要的与这三个注解和调用方法最核心的逻辑是:
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args)
所以本次着重描述这个方法的业务逻辑,里面涉及到的方法,我会简单描述其作用和返回值,但不深挖。
2. 源码及解析
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 一、方法参数解释
// invoker : 用于执行method的对象,对应上述代码指的是:放在Spring-IOC中的RoleServiceImpl对象。
// method : 被注解的方法,对应上述的代码指的是:getOneById。
// contexts : 被注解方法的@Cacheable等注解信息,可以从里面提取出对应的缓存注解。
// 二、这段方法和主要逻辑关系不大,可以简单理解根据参数进行一些简单的设置。
// Special handling of synchronized invocation
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, handleSynchronizedGet(invoker, key, cache));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
// 三、@CacheEvict注解有一个叫做beforeInvocation的字段可以设置是否在methods执行前就将缓存删除。这个方法就是将此字段为true的缓存在此处进行删除
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 四、从Contexts获取是否有@Cacheable注解。如果有的话就去找缓存,如果缓存中存在对应数据就返回,不存在就返回null;如果没有此注解就返回null。
// 所以cacheHit这个对象代表,是否存在key对应的缓存。null代表没有,非null代表存在。
// 在此时Cacheable就已经去查询缓存了。
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 四、如果缓存为空的话,SpringCache会将@Cacheable自动降级为@CachePut,用于之后增加缓存。
// 可以理解为缓存的添加是由CachePut维护的,@Cacheable的查询和增添功能是被分隔开的。
// 如果是@CahceEvict,虽然cacheHit为null,但是不影响。
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new ArrayList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
// 用于对缓存操作(增加)的对象
Object cacheValue;
// 用于作文methods返回值的对象
Object returnValue;
// 五-1、如果cache存在,并且不是个CachePut(更新请求),就是用上面查到的缓存Cachehit进行赋值。
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
// 五-2、如果Cache不存在,或是@CachePut缓存更新操作,
// 就通过反射调用目标方法methods从而进行获取最新的数据。
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 六-1、判断是否有@CachePut请求,如果有的就加入一个请求更新链(因为一个@CachePut可以
// 确定多个要跟新的缓存区域)
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 六-2、根据上面的链(有@CacheAble但是数据不存在,也有@CachePut这种默认更新)将本次方法结果一一放入缓存。
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 七、删除@CacheEvict的删除缓存的操作。与上面提前删除有关,如果之前设置提前删除,这里就不再删除了
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
// 八、返回methods执行的结果。
return returnValue;
}
3. Springboot-cache流程图
execute方法会处理上述的三个注解,所以这个流程图这代表泵方法的流程图
如图:
四、尾声
-
不足:
- 本次方法有的没有研究到,比如execute方法,第二个过程的代码块。所以没有涉及,后续博主如果研究过后,会再写一篇博客。
- 本篇博客只涉及了这三个注解的解析过程。它们怎么使用,以及这个execute的触发都没有写到。因为前者是工具性质博客,后者是一种通用的注解拓展功能的思想,其不仅仅在Springboot-cache中使用到了,其它地方也用到了,博主想用更好的文章来讲解这块知识。
最后如果本篇博客有什么问题还请大家指出。