问题
使用动态数据源后,当我们使用注解进行缓存操作时,例如下面的写法。如果先缓存数据源1的值,然后数据源2访问该方法且参数相同,那么数据源2会直接获取到数据源1的结果,获取到错误的缓存值,出现冲突。
@Cacheable(value = "objCache", key = "(#root.targetClass.getSimpleName()).concat(#root.method.name).concat(':key:').concat(#key)", unless = "#result eq null")
public String xxx(String key) {
return demoMapper.xxx(key);
}
分析
一、基本概念
Spring Cache是Spring框架提供的一个抽象层,用于简化缓存的使用。它允许开发者通过注解的方式轻松地实现缓存功能,而不需要关心具体的缓存实现细节。Spring Cache的原理主要基于以下几个基本概念:
-
缓存注解:Spring Cache提供了一系列注解,如@Cacheable、@CacheEvict、@CachePut等,用于标记需要进行缓存操作的方法。这些注解可以直接应用于方法上,告诉Spring框架在方法执行前后进行哪些缓存操作。
-
缓存解析器(CacheResolver):当方法上标记了缓存注解后,Spring Cache会根据配置的缓存解析器(Cache Resolver)来确定使用哪个缓存管理器(Cache Manager)进行缓存操作。缓存解析器可以根据方法的参数、返回值等信息来选择合适的缓存管理器。resolveCaches会根据传入的上下文,返回匹配的Cache。
-
缓存管理器:缓存管理器是Spring Cache的核心组件,它负责具体的缓存实现。Spring Cache支持多种缓存实现,如EhCache、Redis、Caffeine等。开发者可以根据实际需求选择合适的缓存实现,并通过配置将其与Spring Cache集成。
-
键生成器(Key Generator):根据方法信息生成key值。
二、注解缓存的执行部分逻辑
spring cache定义了缓存的执行流程,而开放了缓存的管理。
CacheInterceptor 拦截了注解缓存操作,下面是它的execute方法。
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
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();
}
CacheOperationContexts包含了不同cache操作的的上下文信息,内部由MultiValueMap维护。
在创建cache上下文CacheOperationContext对象时,会调用CacheResolver的resolveCaches方法,匹配该操作对应的Cache,所以我们可以从CacheResolver入手,例如给cacheNames拼接上当前访问数据源的信息,这么做相当于从cache