Spring版本
5.0.9.RELEASE
背景
最近正好在做缓存相关的项目,用到了Spring Cache搭配Redis实现,本着“知其然亦须知其所以然”的精神,研究了一下Spring Cache的源码,记录一下,以便深入理解。本文皆是基于自身理解撰写,能力有限,若有错误之处,不妨赐教。
1. Spring Cache提供的注解
1.1 Cacheable
被该注解修饰的方法在执行之前,会去缓存中查找是都存在对应key的缓存,若存在,则不执行方法,直接返回缓存结果,否则,执行方法并将结果缓存
1.1.1 属性
-
value
:缓存名称,cacheNames的别名 -
cacheNames
:缓存名称,value的别名,当RedisCacheConfiguration
中的usePrefix
配置为true
时,作为key前缀 -
key
:缓存的key,支持SpEL
表达式,未定义默认的keyGenerator的情况下,使用方法的所有参数作为key,支持:-
#root.method
:引用方法 -
#root.target
:引用目标对象 -
#root.methodName
:引用方法名 -
#root.targetClass
:来引用目标类 -
#args[1]
或者#a1
或者#p1
: 引用参数,也可以直接使用#参数名
-
-
keyGenerator
:自定义的key生成器 -
cacheManager
:自定义的缓存管理器 -
cacheResolver
:自定义的缓存解析器 -
condition
:缓存的前提条件,支持SpEL
表达式,默认值为""
,意味着方法执行结果永远会被缓存起来 -
unless
:拒绝缓存的条件,支持SpEL
表达式,默认为""
,意味着不会拒绝缓存方法执行的结果,与condition
不同的是,该表达式在方法执行结束之后生效,并且可以通过#result
来引用执行后的结果 -
sync
:是否开启同步,开启后仅允许被注解方法有且只有一个@Cacheable
注解,并且不支持unless,并且不能同时拥有其他缓存注解,后文的源码解读中将体现sync=true的限制
1.2 CacheConfig
提供一个全局的配置,具体字段在@Cacheable
中已经说明,此处不赘述
1.3 CacheEvict
字段与@Cacheable
基本一致,多了以下几个字段:
1.3.1 属性
-
allEntries
:默认false
,代表按照key删除对应缓存,当指定为true
,代表删除对应cacheNames下的全部缓存 -
beforeInvocation
:是否在方法调用前触发删除逻辑,默认false
,代表方法执行之后再删除缓存,若方法执行过程中抛出异常,不执行删除逻辑,设置为true
,代表方法执行之前删除缓存,若方法执行过程中抛出异常,不影响删除逻辑
1.4 CachePut
与@Cacheable
不同的是,被该注解修饰的方法在执行之前,不会去缓存中检查是否存在对应key的缓存,而是直接执行方法并将结果缓存
1.5 Caching
@Cacheable
、@CachePut
和@CacheEvict
的组合注解,方便我们对多个cache执行相应逻辑
1.6 EnableCaching
开启cache
1.6.1 属性
-
proxyTargetClass
:仅当mode设置为Proxy
时生效,为false
代表jdk代理,为true
代表cglib代理,设置为true
同时会影响所有需要代理的且由spring管理的bean的代理模式,如被Transactional
修饰 -
mode
:Proxy:代理模式,AspectJ:切面模式,默认值Proxy -
order
:同个切入点有多个切面的时候的执行顺序
2. 源码解读
很明显,我们可以从EnableCaching
作为源码阅读突破口,可以看到:
@Import(CachingConfigurationSelector.class)
EnableCaching
中引入了CachingConfigurationSelector
类
@Import
用于引入一个或多个Configuration
,等效于在xml文件中的<import/>
标签
结合EnableCaching
的mode
默认为Proxy
模式,明显CachingConfigurationSelector
中的核心方法为:
/**
* Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
* for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
* respectively. Potentially includes corresponding JCache configuration as well.
*/
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
/**
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
* <p>Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getProxyImports() {
List<String> result = new ArrayList<>(3);
result.add(AutoProxyRegistrar.class.getName());
result.add(ProxyCachingConfiguration.class.getName());
if (jsr107Present && jcacheImplPresent) {
result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
}
return StringUtils.toStringArray(result);
}
在getProxyImports
方法中,我们可以看到此处引入了AutoProxyRegistrar
和ProxyCachingConfiguration
俩个类,看名字应该是自动代理注册以及代理缓存配置,先看看AutoProxyRegistrar
:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
for (String annoType : annoTypes) {
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
if (candidate == null) {
continue;
}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
candidateFound = true;
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
if (!candidateFound && logger.isWarnEnabled()) {
String name = getClass().getSimpleName();
logger.warn(String.format("%s was imported but no annotations were found " +
"having both 'mode' and 'proxyTargetClass' attributes of type " +
"AdviceMode and boolean respectively. This means that auto proxy " +
"creator registration and configuration may not have occurred as " +
"intended, and components may not be proxied as expected. Check to " +
"ensure that %s has been @Import'ed on the same class where these " +
"annotations are declared; otherwise remove the import of %s " +
"altogether.", name, name, name));
}
}
我们开启debug,启动项目,可以看到,该方法的入参importingClassMetadata
传入的是我们的启动类:
那么:
Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
这一句很明显就是获取Application
上面的注解:
这里我们设置条件断点,只查看
EnableCaching
的执行流程,核心代码在于:
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
首先使用jdk代理进行注册,如果proxyTargetClass
设置为true
,则转化为cglib代理。
进入registerAutoProxyCreatorIfNecessary
方法,层层追溯,可以看到核心代码为:
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
@Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
首先判断registry
中是否包含AUTO_PROXY_CREATOR_BEAN_NAME
对应的beanDefinition
:
- 包含
获取当前beanDefinition
,如果和请求参数cls
不是同一个bean
,则根据优先级判断是否需要替换当前bean
,那么优先级是如何定义的呢,点击任意findPriorityForClass
方法:
private static int findPriorityForClass(Class<?> clazz) {
return APC_PRIORITY_LIST.indexOf(clazz);
}
可以看到有一个集合APC_PRIORITY_LIST
,查找初始化代码,可以看到其是在静态代码块中初始化了三个AutoProxyCreator
,且通过index
表明其优先级:
static {
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}
- 不包含
初始化一个BeanDefinition
并注册
现在我们回过头来看看ProxyCachingConfiguration
:
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.config.CacheManagementConfigUtils;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
/**
* {@code @Configuration} class that registers the Spring infrastructure beans necessary
* to enable proxy-based annotation-driven cache management.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see EnableCaching
* @see CachingConfigurationSelector
*/
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.setCacheOperationSources(cacheOperationSource());
if (this.cacheResolver != null) {
interceptor.setCacheResolver(this.cacheResolver);
}
else if (this.cacheManager != null) {
interceptor.setCacheManager(this.cacheManager);
}
if (this.keyGenerator != null) {
interceptor.setKeyGenerator(this.keyGenerator);
}
if (this.errorHandler != null) {
interceptor.setErrorHandler(this.errorHandler);
}
return interceptor;
}
}
在cacheAdvisor
方法中,我们可以看到设置了cacheOperationSource
和cacheInterceptor
,而cacheOperationSource
则是AnnotationCacheOperationSource
实例,查看AnnotationCacheOperationSource
:
/**
* Create a default AnnotationCacheOperationSource, supporting public methods
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
*/
public AnnotationCacheOperationSource() {
this(true);
}
/**
* Create a default {@code AnnotationCacheOperationSource}, supporting public methods
* that carry the {@code Cacheable} and {@code CacheEvict} annotations.
* @param publicMethodsOnly whether to support only annotated public methods
* typically for use with proxy-based AOP), or protected/private methods as well
* (typically used with AspectJ class weaving)
*/
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
可以看到,这种情况下只支持public方法的缓存,同时还可以看到还有一个注解解析器SpringCacheAnnotationParser
,核心代码在于:
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(
DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
Collection<CacheOperation> ops = null;
Collection<Cacheable> cacheables = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class));
if (!cacheables.isEmpty()) {
ops = lazyInit(null);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
Collection<CacheEvict> evicts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class));
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
Collection<CachePut> puts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class));
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
Collection<Caching> cachings = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class));
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}
通过这段代码,我们不难看出,其功能在于解析@Cacheable
、@CacheEvict
、@CachePut
注解,并将其包装成通用的CacheOperation
,方便我们后续对其处理(后面的源码中很多地方都会有CacheOperation
的出场机会)
其中,localOnly
代表如果被注解接口声明和实现同时存在缓存注解,那么,实现上的注解将会覆盖接口声明的注解:
// More than one operation found -> local declarations override interface-declared ones...
疑问:为啥是覆盖而不是整合呢?待补充
再回过头来看看CacheInterceptor
:
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
可以看到CacheInterceptor
主要的实现方法是execute
,该方法的定义如下:
execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args)
参数解析如下:
-
invoker
:这里我们传入的是一个匿名方法,用于执行被注解的方法 -
target
:被注解对象 -
method
:被注解的方法 -
args
:被注解方法的参数
execute
方法实现如下:
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
// 判断对象是否已经初始化,initialized默认为false,在afterSingletonsInstantiated方法中被赋值为true
if (this.initialized) {
// 获取被注解对象类型
Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// 获取注解在方法上的注解list
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method,
new CacheAspectSupport.CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
// 缓存处理完毕之后,调用被注解方法执行逻辑
return invoker.invoke();
}
可以看到,此处调用了execute
的重载方法,这里封装了一个缓存操作上下文对象作为参数,我们先看一下CacheOperationContexts
这个内部类对象:
private class CacheOperationContexts {
// 缓存操作(@Cacheable、@CacheEvict等)和上下文环境的映射map
private final MultiValueMap<Class<? extends CacheOperation>, CacheAspectSupport.CacheOperationContext> contexts;
// 是否开启同步
private final boolean sync;
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
this.sync = determineSyncFlag(method);
}
public Collection<CacheAspectSupport.CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheAspectSupport.CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.emptyList());
}
public boolean isSynchronized() {
return this.sync;
}
// 解析sync的值
private boolean determineSyncFlag(Method method) {
// 获取@Cacheable的上下文
List<CacheAspectSupport.CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
if (cacheOperationContexts == null) { // no @Cacheable operation at all
return false;
}
boolean syncEnabled = false;
// 若是存在多个@Cacheable,如果其中一个sync标识为true,则认为是同步操作
for (CacheAspectSupport.CacheOperationContext cacheOperationContext : cacheOperationContexts) {
if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
syncEnabled = true;
break;
}
}
if (syncEnabled) {
// 不允许存在任何该同步注解之外的缓存注解
if (this.contexts.size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
}
// 不允许存在任何该同步注解之外的@Cacheable注解
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException(
"Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
}
// 拿到同步注解@Cacheable对象
CacheAspectSupport.CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
// 该同步注解的cacheNames属性只能有一个值
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
}
// @Cacheable处提到的开启同步的情况下,不支持unless
if (StringUtils.hasText(operation.getUnless())) {
throw new IllegalStateException(
"@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
}
return true;
}
return false;
}
}
回过头来看看execute
的重载方法:
Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)
该方法内部实现主要分成俩部分,一部分是对同步的处理,另一部分是非同步的处理
- 同步:
// CacheOperationContexts#determineSyncFlag解析的同步结果体现在这个条件判断中
if (contexts.isSynchronized()) {
// 获取同步的@Cacheable注解
CacheAspectSupport.CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
// 判断是否满足@Cacheable注解的conditional属性
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
// 生成key
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
// 获取方法的cacheName属性,同步情况下只会有一个
Cache cache = context.getCaches().iterator().next();
try {
// 执行缓存逻辑,同步交由缓存提供商实现,wrapCacheValue和unwrapReturnValue都是对返回值做一下Optional的处理,不展开
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);
}
}
其中,cache#get
方法在redis中的实现如下:
// 使用 synchronized 保证同步
@Override
@SuppressWarnings("unchecked")
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
// 从redis中读取缓存结果
Cache.ValueWrapper result = get(key);
// 缓存结果非空,直接以缓存结果作为返回值
if (result != null) {
return (T) result.get();
}
// 否则执行方法,获取执行结果,并缓存
T value = valueFromLoader(key, valueLoader);
put(key, value);
return value;
}
- 不同步:
// Process any early evictions
// 处理beforeInvocation为true的@CacheEvict,该部分需要在方法调用之前处理
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<>();
// 如果没有命中缓存,那么说明需要执行缓存写入逻辑,这里仅仅只是收集应该被写入缓存的@Cacheable数据到cachePutRequests中,真正的写入缓存操作在后续的apply中
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
// 应该被缓存的值
Object cacheValue;
// 方法返回值,与cacheValue的区别在于可能是一个Optional对象
Object returnValue;
// @CachePut注解表示无视缓存结果,强制执行方法并将结果缓存,所以这里必须判断是否有该注解
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
// 命中缓存,且没有@CachePut注解的情况则直接返回缓存结果
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
// 收集应该被写入缓存的@CachePut数据到cachePutRequests中
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
// 此处是@Cacheable和@CachePut的缓存真正写入逻辑
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// Process any late evictions
// 执行后续的@CacheEvict逻辑
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
以上我们讲解了缓存的处理主流程,接下来详细看看每个子节点的具体实现逻辑:
-
processCacheEvicts
:
private void processCacheEvicts(
Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
for (CacheOperationContext context : contexts) {
CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
performCacheEvict(context, operation, result);
}
}
}
核心点在于通过isConditionPassing
方法判断是否符合删除前提条件,isConditionPassing
中涉及到SpEL
表达式的逻辑,不是本文的重点,此处不予展开。若满足前提条件,执行performCacheEvict
方法来进行缓存删除逻辑:
private void performCacheEvict(
CacheAspectSupport.CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
Object key = null;
for (Cache cache : context.getCaches()) {
// cacheWide就是allEntries,只不过在CacheEvictOperation中名字不一样罢了
if (operation.isCacheWide()) {
// 打印日志
logInvalidating(context, operation, null);
// 整块缓存删除
doClear(cache);
}
else {
if (key == null) {
key = generateKey(context, result);
}
logInvalidating(context, operation, key);
// 根据key删除对应缓存
doEvict(cache, key);
}
}
}
接下来分别看看doClear
和doEvict
方法:
doClear
:
@Override
public void clear() {
byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
cacheWriter.clean(name, pattern);
}
查看createCacheKey
方法:
/**
* Customization hook for creating cache key before it gets serialized.
*
* @param key will never be {@literal null}.
* @return never {@literal null}.
*/
protected String createCacheKey(Object key) {
// 将 object 类型的key转化为string类型
String convertedKey = convertKey(key);
// 如果 redis 配置的是不自动为key添加前缀,则直接返回
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
// 如果配置了使用前缀,则对key进行前缀包装
return prefixCacheKey(convertedKey);
}
那么,usePrefix
是在哪里设置的呢?我们使用spring boot + redis的话一般都有一个配置类,用来配置redis的相关信息,如下:
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
RedisCacheManager.RedisCacheManagerBuilder builder =
RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
return builder.transactionAware().cacheDefaults(config).build();
}
}
在创建cacheManger
这个bean
的时候,可以看到这么一句代码:
RedisCacheConfiguration.defaultCacheConfig()
而defaultCacheConfig
方法里面,则初始化并返回了一个RedisCacheConfiguration
对象:
return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
SerializationPair.fromSerializer(new StringRedisSerializer()),
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()), conversionService);
这里的第三个构造参数便是usePrefix
,可见默认值为true
。此时生成的key规则为:cacheName::*
,那接下来就是清除缓存的代码:
cacheWriter.clean(name, pattern);
在clean
方法的redis实现版本中,可以看到clean
最终也是使用keys
获取符合条件的keySet,之后通过del
方法进行删除:
/*
* (non-Javadoc)
* @see org.springframework.data.redis.cache.RedisCacheWriter#clean(java.lang.String, byte[])
*/
@Override
public void clean(String name, byte[] pattern) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(pattern, "Pattern must not be null!");
execute(name, connection -> {
boolean wasLocked = false;
try {
if (isLockingCacheWriter()) {
doLock(name, connection);
wasLocked = true;
}
byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
.toArray(new byte[0][]);
if (keys.length > 0) {
connection.del(keys);
}
} finally {
if (wasLocked && isLockingCacheWriter()) {
doUnlock(name, connection);
}
}
return "OK";
});
}
疑问:众所周知,redis是单线程的,使用
keys
可能会相当耗时,按照以上的逻辑,是会对写操作进行加锁,那么一旦keys
耗时很久,也就意味着此时写入缓存就会等待很久,从而导致接口层面上的超时,所以,这种实现合理吗?
看完doClear
方法,再来看看doEvict
方法:
/**
* Execute {@link Cache#evict(Object)} on the specified {@link Cache} and
* invoke the error handler if an exception occurs.
*/
protected void doEvict(Cache cache, Object key) {
try {
cache.evict(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheEvictError(ex, cache, key);
}
}
evict
方法实现如下:
@Override
public void evict(Object key) {
cacheWriter.remove(name, createAndConvertCacheKey(key));
}
先根据key进行创建和转化得到包装后的key,之后调用remove
方法从name中将之删除,remove
最终也是使用了del
方法:
@Override
public void remove(String name, byte[] key) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
execute(name, connection -> connection.del(key));
}
到这里processCacheEvicts
就解析完毕了
-
findCachedItem
:
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
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;
}
逻辑主要是遍历多个@Cacheable
,任何一个存在缓存数据,直接返回对应数据,结束流程,否则返回null
,findInCaches
最后调用了get
方法读取缓存,该部分逻辑相对简单,不废话了
-
collectPutRequests
:
private void collectPutRequests(Collection<CacheOperationContext> contexts,
@Nullable Object result, Collection<CachePutRequest> putRequests) {
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
putRequests.add(new CachePutRequest(context, key));
}
}
}
逻辑也比较简单,遍历contexts
,若是满足condition
,生成key
并包装成CachePutRequest
对象加入到putRequests
中,注意此处的CachePutRequest
:
private class CachePutRequest {
private final CacheOperationContext context;
private final Object key;
public CachePutRequest(CacheOperationContext context, Object key) {
this.context = context;
this.key = key;
}
public void apply(@Nullable Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
}
这里面实现了一个apply
方法,该方法在非同步处理的主流程中最终会被调用,用于写入缓存,写入逻辑如下:
@Override
public void put(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}
代码写得很清晰了,不展开了
到此处,我们execute方法解析完毕!!!