spring cache 源码分析,看看@cachable @CachePut @CacheEvict的实现方式

注:禁止转载,谢谢!

1、结论

话不多说,先放几个结论:
1、使用cache,默认在类ConcurrentMapCache的ConcurrentMap中存储,存在JVM内存中,重启服务缓存丢失。
2、如果集成了redis,需要配置cacheManager,使用redis存储,重启service服务缓存不丢失。
3、使用本地缓存,默认使用ConcurrentMapCacheManager的来管理;如果使用redis,使用redisCacheManager来管理
4、基本原理:基于代理模式,在代理方法中,执行对应方法前先会判断是否添加了@cacheable,如果没有,则直接调用方法返回结果。如果有,则会执行到缓存拦截链(CacheInterceptor),进入CacheAspectSupport.execute(),执行具体的缓存逻辑。

2、三种缓存注解的意思

@Cacheable:执行 @Cacheable 标注的方法前先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,执行该方法并将方法返回值放进缓存。

@CachePut:每次方法的返回值都会更新缓存, 主要用于数据新增和更新方法

@CacheEvict:方法执行成功后会从缓存中移除相应数据。

3、看代码

伪代码如下,每个方法缓存配置了name,指定了key

@RestController
public class TestController {
    @Autowired
    private TestService testService ;
    @GetMapping("/getName")
    public String getName(Integer age) {
        return testService.getName(age);
    }

    @GetMapping("/getAddress")
    public String getAddress(String name) {
        return testService.getAddress(name);
    }
    
}
@Service
public class TestServiceImpl implements TestService {

    @Cacheable(value = "name", key = "#age")
    @Override
    public String getName(Integer age) {
        String uuid = UUID.randomUUID().toString();
        System.out.println("查询名称:"+uuid);
        return uuid;
    }

    @Cacheable(value = "address", key = "#name")
    @Override
    public String getAddress(String name) {
        String uuid = UUID.randomUUID().toString();
        System.out.println("查询地址:"+uuid);
        return uuid;
    }
}

controller调用service,实际调用的是service实例的代理,进入代理JdkDynamicAopProxy的invoke方法:

@Override
	@Nullable
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			.......省略部分代码........
			target = targetSource.getTarget();
			Class<?> targetClass = (target != null ? target.getClass() : null);

			/** 获得方法的调用链,如果是添加了缓存注解的,会获得CacheInterceptor的调用链,
			 如果没有,chain就是空,下面判断chain为空就直接执行目标方法*/
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			if (chain.isEmpty()) {
				// 直接执行
				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				// 执行拦截链,返回retVal结果
				retVal = invocation.proceed();
			}

			// 返回结果类型检查
			Class<?> returnType = method.getReturnType();
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				retVal = proxy;
			}
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// Restore old proxy.
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}

retVal = invocation.proceed(); 进入ReflectiveMethodInvocation.proceed()

@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			...........省略............
		else {
			// 进入,执行CacheInterceptor.invoke,里面再执行CacheAspectSupport.execute()
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

现在进入到CacheAspectSupport.execute(…)

@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
		// 判断是否已经初始化完成
		if (this.initialized) {
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
				//获取CacheOperation封装了@cacheable的注解信息
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
				    //进入
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

	/**
		缓存逻辑的核心方法,这里查找缓存,如果不存在,就执行目标方法获取结果,并把结果缓存起来.
		这里也看到了@cachable @CachePut @CacheEvict三种注解的不同处理方式
	*/
	@Nullable
	private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
		// 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, 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);
			}
		}


		// 判断@CacheEvict,是否有需要执行beforeInvocation,提前清除
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);

		// 根据CacheableOperation去查缓存,在CacheableOperation中封装了注解@cacheable的信息的,如果配置了condition,要看是否匹配;如果配置了key,根据key去查找。(下方有解析)
		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)) {
			cacheValue = cacheHit.get();
			returnValue = wrapCacheValue(method, cacheValue);
		}
		else {
			// 执行目标方法
			returnValue = invokeOperation(invoker);
			cacheValue = unwrapReturnValue(returnValue);
		}

		// 收集被@CachePut修饰的方法放到cachePutRequests中,因为@CachePut修饰的方法需要每次都更新缓存值
		collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

		// 更新缓存值,里面的doPut()方法,如果本地存储,就存在map中,如果redis,就连接存到redis中
		for (CachePutRequest cachePutRequest : cachePutRequests) {
			cachePutRequest.apply(cacheValue);
		}

		// 执行被@CacheEvict修饰的,需要缓存剔除的值
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

		return returnValue;
	}

	//根据注解信息,查询缓存中是否有值
	@Nullable
	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
			//过滤condition条件是否成立
			if (isConditionPassing(context, result)) {
				//根据注解的key生成查询的key
				Object key = generateKey(context, result);
				//根据配置,在本地或者redis中查找缓存
				Cache.ValueWrapper cached = findInCaches(context, key);
				if (cached != null) {
					return cached;
				}
				else {
					if (logger.isTraceEnabled()) {
						logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
					}
				}
			}
		}
		return null;
	}

4、小结

以上对缓存执行原理,过程代码进行了简单说明,自己跟着跑一遍会更清晰。
另外,以上过程是在看源码时的感悟,没有另外查资料,如果不准可以指出!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值