@EnableCaching

这个注解的作用就是开启缓存,使得加在方法上面的缓存注解生效,我们看下这个注解是做了什么

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {boolean proxyTargetClass() default false;AdviceMode mode() default AdviceMode.PROXY;int order() default Ordered.LOWEST_PRECEDENCE;}

可以看到还是一样的套路,里面通过@Import注解往spring容器中注册了一个CachingConfigurationSelector类

public String[] selectImports(AdviceMode adviceMode) {switch (adviceMode) {case PROXY:return getProxyImports();case ASPECTJ:return getAspectJImports();default:return null;}
}
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);
}

然后这个类又是一个ImportSelect组件,所以从上面的代码可以看出最终往spring容器中注册了AutoProxyRegistrar以及ProxyCachingConfiguration

往容器中注册缓存支持的组件

@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.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);interceptor.setCacheOperationSource(cacheOperationSource());return interceptor;}}

这个类往spring容器中注册了三个bean,分别是BeanFactoryCacheOperationSourceAdvisor,CacheOperationSource以及CacheInterceptor,这看上去怎么和spring的事务源码实现差不多?没错,spring缓存切面的实现与spring事务的代理实现大同小异,同样的都是会往容器中导入三个bean,一个是advisor,一个是advice,一个是pointcut,而一个advisor中会有一个advice以及pointcut

注册生成代理的后置处理器

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {private final Log logger = LogFactory.getLog(getClass());@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {boolean candidateFound = false;Set<String> annTypes = importingClassMetadata.getAnnotationTypes();for (String annType : annTypes) {AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);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.isInfoEnabled()) {String name = getClass().getSimpleName();logger.info(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));}}}
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

AutoProxyRegistrar是一个ImportBeanDefinitionRegistrar组件,会通过registry去给容器中手动注册bd,与spring事务一样,最终都是通过AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)去给容器注册InfrastructureAdvisorAutoProxyCreator,InfrastructureAdvisorAutoProxyCreator这里就不细说了,之前在spring事务的代理实现中已经说过它就是一个BeanPostProcessor,并且会在bean初始化的时候在postProcessAfterInitialization方法中生成了代理对象,而负责生成代理对象的方法就在wrapIfNecessary方法中

在wrapIfNecessary方法中会去spring容器中寻找role=BeanDefinition.ROLE_INFRASTRUCTURE的advisor,也就是ProxyCachingConfiguration往容器中注册的BeanFactoryCacheOperationSourceAdvisor这个bean,并且会通过这个advisor的pointcut去给当前正在初始化的bean进行匹配,判断该bean的类上面或者方法上面是否有缓存注解,如果有缓存注解,那么这个bean就可以被生成代理对象,增强的逻辑就是advisor里面的advice,也就是CacheInterceptor

解析缓存注解

在生成代理对象之前,需要通过advisor的pointcut去判断这个advisor是否适用于当前的bean,判断的依据就是去解析这个bean中是否存在缓存注解,下面我们看下是如何去解析缓存注解的

org.springframework.cache.interceptor.CacheOperationSourcePointcut#matches

public boolean matches(Method method, Class<?> targetClass) {CacheOperationSource cas = getCacheOperationSource();return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}

在CacheOperationSourcePointcut这个pointcut中,当advisor去进行判断是否适用bean的时候就会调用这个pointcut的matches方法,其中又会去调用了CacheOperationSource的getCacheOperations方法,而这个CacheOperationSource是一个接口,具体就是在ProxyCachingConfiguration配置类中创建的AnnotationCacheOperationSource

org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource#getCacheOperations

public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {if (method.getDeclaringClass() == Object.class) {return null;}Object cacheKey = getCacheKey(method, targetClass);Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);if (cached != null) {return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);}else {Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);if (cacheOps != null) {if (logger.isTraceEnabled()) {logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);}this.attributeCache.put(cacheKey, cacheOps);}else {this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);}return cacheOps;}
}

org.springframework.cache.interceptor.AbstractFallbackCacheOperationSource#computeCacheOperations

private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {// Don't allow no-public methods as required.// 必须要是public的方法if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);// First try is the method in the target class.// 通过解析方法某些信息得到缓存属性信息Collection<CacheOperation> opDef = findCacheOperations(specificMethod);if (opDef != null) {return opDef;}// Second try is the caching operation on the target class.opDef = findCacheOperations(specificMethod.getDeclaringClass());if (opDef != null && ClassUtils.isUserLevelMethod(method)) {return opDef;}if (specificMethod != method) {// Fallback is to look at the original method.opDef = findCacheOperations(method);if (opDef != null) {return opDef;}// Last fallback is the class of the original method.opDef = findCacheOperations(method.getDeclaringClass());if (opDef != null && ClassUtils.isUserLevelMethod(method)) {return opDef;}}return null;
}

AnnotationCacheOperationSource继承了AbstractFallbackCacheOperationSource,在父类AbstractFallbackCacheOperationSource的getCacheOperations中主要就是调用computeCacheOperations方法返回声明的缓存信息并且放在attributeCache这个map缓存中,findCacheOperations是一个抽象方法,它抽象了解析方法中什么样的缓存信息,怎样去解析这些缓存信息,而AnnotationCacheOperationSource这个子类实现的就是去解析缓存注解

org.springframework.cache.annotation.AnnotationCacheOperationSource#findCacheOperations(java.lang.reflect.Method)

protected Collection<CacheOperation> findCacheOperations(Method method) {return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}

通过解析器去对method进行解析,这里具体的解析器是SpringCacheAnnotationParser

org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(java.lang.reflect.Method)

public Collection<CacheOperation> parseCacheAnnotations(Method method) {DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());return parseCacheAnnotations(defaultConfig, method);
}

上面的方法中会通过method得到该method所属的class然后把这个class作为构造方法参数创建出了DefaultCacheConfig对象,这个对象有什么用?下面会说到

org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(org.springframework.cache.annotation.SpringCacheAnnotationParser.DefaultCacheConfig, java.lang.reflect.AnnotatedElement, boolean)

private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {// 找到方法上面所有的缓存注解(@Cacheable,@CacheEvict,@CachePut,@Caching)Collection<? extends Annotation> anns = (localOnly ?AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));if (anns.isEmpty()) {return null;}final Collection<CacheOperation> ops = new ArrayList<>(1);// 如果有@Cacheable注解,就把注解属性信息封装成CacheableOperation对象anns.stream().filter(ann -> ann instanceof Cacheable).forEach(ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));// 如果有@CacheEvict注解,就把注解属性信息封装成CacheEvictOperation对象anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));// 如果有@CachePut注解,就把注解属性信息封装成CachePutOperation对象anns.stream().filter(ann -> ann instanceof CachePut).forEach(ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));// 如果有@Caching注解,就解析@Caching,封装成上面三种对象放到集合中anns.stream().filter(ann -> ann instanceof Caching).forEach(ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));return ops;
}

可以看到上面对各种缓存注解都做了不同的解析,我们就看下@Cacheable注解是如何解析的

private CacheableOperation parseCacheableAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {CacheableOperation.Builder builder = new CacheableOperation.Builder();builder.setName(ae.toString());builder.setCacheNames(cacheable.cacheNames());builder.setCondition(cacheable.condition());builder.setUnless(cacheable.unless());builder.setKey(cacheable.key());builder.setKeyGenerator(cacheable.keyGenerator());builder.setCacheManager(cacheable.cacheManager());builder.setCacheResolver(cacheable.cacheResolver());builder.setSync(cacheable.sync());defaultConfig.applyDefault(builder);CacheableOperation op = builder.build();validateCacheOperation(ae, op);return op;
}

通过构造器模式去从缓存注解中获取对应的属性,最后构造出CacheableOperation对象,这对象里面就封装了缓存注解的信息,其中还调用了一个defaultConfig.applyDefault(builder)方法,我们看下这个方法是干嘛的

private static class DefaultCacheConfig {private final Class<?> target;@Nullableprivate String[] cacheNames;@Nullableprivate String keyGenerator;@Nullableprivate String cacheManager;@Nullableprivate String cacheResolver;private boolean initialized = false;public DefaultCacheConfig(Class<?> target) {this.target = target;}/*** Apply the defaults to the specified {@link CacheOperation.Builder}.* @param builder the operation builder to update*/public void applyDefault(CacheOperation.Builder builder) {if (!this.initialized) {// 找到方法所在的类上面的@CacheConfig注解,通过解析这个注解去拿到关于缓存组件的一些配置信息CacheConfig annotation = AnnotatedElementUtils.findMergedAnnotation(this.target, CacheConfig.class);if (annotation != null) {// 缓存组件this.cacheNames = annotation.cacheNames();// key名称生成器this.keyGenerator = annotation.keyGenerator();// 缓存管理器this.cacheManager = annotation.cacheManager();// 缓存解析器this.cacheResolver = annotation.cacheResolver();}this.initialized = true;}// 如果缓存注解中没有声明上面的缓存配置,就使用@CacheConfig注解声明的缓存配置if (builder.getCacheNames().isEmpty() && this.cacheNames != null) {builder.setCacheNames(this.cacheNames);}if (!StringUtils.hasText(builder.getKey()) && !StringUtils.hasText(builder.getKeyGenerator()) &&StringUtils.hasText(this.keyGenerator)) {builder.setKeyGenerator(this.keyGenerator);}if (StringUtils.hasText(builder.getCacheManager()) || StringUtils.hasText(builder.getCacheResolver())) {// One of these is set so we should not inherit anything}else if (StringUtils.hasText(this.cacheResolver)) {builder.setCacheResolver(this.cacheResolver);}else if (StringUtils.hasText(this.cacheManager)) {builder.setCacheManager(this.cacheManager);}}
}

这个方法是SpringCacheAnnotationParser的内部类DefaultCacheConfig的一个方法,DefaultCacheConfig这个对象在上面也有提到,在解析缓存注解之前就会传入该方法所属的class作为targetClass创建出一个DefaultCacheConfig对象出来,而在它的applyDefault方法中会去判断这个targetClass上是否存在@CacheConfig注解,如果targetClass上存在@CacheConfig注解,那么当方法上面没有声明其余的缓存注解的时候就会使用@CacheConfig注解上面声明的缓存配置,也就是说如果方法上面声明了缓存注解,优先使用缓存注解作为缓存配置,否则就以@CacheConfig注解作为缓存配置

缓存切面的增强

具体缓存是怎样执行的,那么就是缓存增强器的逻辑了,我们直接看到CacheInterceptor

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {@Override@Nullablepublic 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();}}}

直接看execute方法

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)if (this.initialized) {Class<?> targetClass = getTargetClass(target);// 获取缓存注解解析类CacheOperationSource cacheOperationSource = getCacheOperationSource();if (cacheOperationSource != null) {// 使用缓存注解解析类去解析调用方法上面的缓存注解,返回缓存注解属性信息Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);if (!CollectionUtils.isEmpty(operations)) {// 如果方法上有缓存注解,那么就执行execute方法return execute(invoker, method,// 把注解信息,方法,方法参数,目标对象,目标class封装成CacheOperationContexts对象new CacheOperationContexts(operations, method, args, target, targetClass));}}}return invoker.invoke();
}

可以看到首先会去解析该方法上面的缓存注解,如果方法上面不存在缓存注解,那么就直接调用走到下一个拦截器,而如果方法存在缓存注解,就会调用另一个重载的execute方法,而在看这个重载的execute方法之前,还创建了一个CacheOperationContexts对象,我们看下这个对象的构造方法

public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,Object[] args, Object target, Class<?> targetClass) {this.contexts = new LinkedMultiValueMap<>(operations.size());for (CacheOperation op : operations) {// 遍历所有的注解信息对象,把每一个注解信息对象与method, args, target, targetClass封装成一个CacheOperationContext对象// 所以contexts中就可以根据不同缓存注解信息对象的class去获取到对应的CacheOperationContext对象this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));}this.sync = determineSyncFlag(method);
}

在CacheOperationContexts的构造方法中会去遍历方法上面的所有CacheOperation对象,然后对每一个CacheOperation对象都执行getOperationContext方法,返回一个CacheOperationContext对象,然后再把该CacheOperation对象与它对应产生的CacheOperationContext对象放到contexts这个map中对应缓存起来

protected CacheOperationContext getOperationContext(CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);return new CacheOperationContext(metadata, args, target);
}

在创建CacheOperationContext对象之前,会先去创建一个CacheOperationMetadata对象

protected CacheOperationMetadata getCacheOperationMetadata(CacheOperation operation, Method method, Class<?> targetClass) {CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);if (metadata == null) {KeyGenerator operationKeyGenerator;if (StringUtils.hasText(operation.getKeyGenerator())) {// 如果指定了key值生成器的名称,那么就根据名称去容器中找到KeyGeneratoroperationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);}else {// 如果没有指定key值生成器,那么就使用默认的key值生成器---SimpleKeyGeneratorSimpleKeyGeneratoroperationKeyGenerator = getKeyGenerator();}CacheResolver operationCacheResolver;if (StringUtils.hasText(operation.getCacheResolver())) {// 如果指定了缓存解析器的名称,那么就根据名称去从容器中找到具体的缓存解析器operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);}else if (StringUtils.hasText(operation.getCacheManager())) {// 否则如果指定了缓存管理器的名称,那么就根据名称去从容器中找到具体的缓存管理器CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);// 并且使用默认的缓存解析器---SimpleCacheResolveroperationCacheResolver = new SimpleCacheResolver(cacheManager);}else {// 如果既没指定缓存解析器的名称,也没指定缓存管理器的名称,那么就取默认配置的缓存解析器---SimpleCacheResolver//operationCacheResolver = getCacheResolver();Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set");}metadata = new CacheOperationMetadata(operation, method, targetClass,operationKeyGenerator, operationCacheResolver);this.metadataCache.put(cacheKey, metadata);}return metadata;
}

可以看到这个方法中主要就是对在缓存注解中声明的缓存配置信息去得到相应的缓存组件,比如说key值生成器,缓存解析器,缓存管理器,这三个组件都可以根据我们在缓存注解中指定的名称从容器中去获取,如果没有指定的话就会用spring默认提供的实现,然后把最终得到的key值生成器,缓存解析器,缓存管理器这三个组件都放到CacheOperationMetadata对象中去,再把CacheOperationCacheKey与这个CacheOperationMetadata对象放到map中缓存起来,下次再调用该方法的时候就不用再去走一遍上面的过程了

创建完了CacheOperationMetadata对象之后就把这个对象作为CacheOperationContext构造方法的参数创建出CacheOperationContext对象,所以在调用exectute方法之前,这个流程应该就是创建一个CacheOperationContexts对象,这个对象里面有一个map分别存着每一个CacheOperation对象以及对应的CacheOperationContext对象,而CacheOperationContext对象中又存着一个CacheOperationMetadata对象,CacheOperationMetadata对象里面存的分别就是key值生成器,缓存解析器,缓存管理器这三个缓存组件

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// Special handling of synchronized invocationif (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 methodreturn invokeOperation(invoker);}}// 获取@CacheEvict注解对应的CacheOperationContext对象// 如果@CacheEvict注解里面beforeInvocation属性等于true,那么就把缓存清除processCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);// 从缓存中拿到缓存值Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));// Collect puts from any @Cacheable miss, if no cached item is foundList<CachePutRequest> cachePutRequests = new LinkedList<>();// 如果缓存中没有值,那么就把context以及key封装成一个CachePutRequest对象放到集合中// 该集合会把需要put的东西都准备好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();// 如果方法返回值是Optional类型并且从缓存中拿出来的值不是Optional类型的,那么就包装为Optional类型的对象返回returnValue = wrapCacheValue(method, cacheValue);}else {// 如果缓存没有命中了,那么就执行目标方法得到返回值returnValue = invokeOperation(invoker);// 如果返回值是Optional类型的,那么就从Optional中拿到真正的结果值作为要缓存的值cacheValue = unwrapReturnValue(returnValue);}// 处理@CachePut注解完善put集合collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// 把方法返回结果放入缓存,针对两种情况// 一是如果@Cacheable注解一进来查询不到缓存// 二是@CachePut注解在方法执行完成之后这两种情况,都会最终把方法返回值放到缓存for (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// 最后如果@CacheEvicts注解的beforeInvocation属性等于false的话就清空缓存processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;
}

上面我们就来到了execute方法,可以看到会先去调用processCacheEvicts方法去处理@CacheEvict注解

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);}}
}

如果@CacheEvict注解中的beforeInvocation属性设置了true的话,那么就表示每一次进来该方法都需要清除缓存,具体调用了performCacheEvict方法

private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {Object key = null;for (Cache cache : context.getCaches()) {// 判断是否删除缓存中所有的key-valueif (operation.isCacheWide()) {logInvalidating(context, operation, null);// 删除缓存中所有的key-valuedoClear(cache, operation.isBeforeInvocation());}else {if (key == null) {key = generateKey(context, result);}logInvalidating(context, operation, key);// 从缓存中删除指定的keydoEvict(cache, key, operation.isBeforeInvocation());}}
}

判断@CacheEvict注解中的allEntries属性是否等于true,如果等于true表示需要删除缓存中所有的数据,否则会删除指定key的缓存,最后都会去调用cache的清除缓存方法,那么这些cache是从哪里来的呢?我们跟踪到CacheOperationContext的构造方法

public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {this.metadata = metadata;this.args = extractArgs(metadata.method, args);this.target = target;this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);this.cacheNames = createCacheNames(this.caches);
}

可以看到所有的cache都是通过 CacheAspectSupport.this.getCaches(this, metadata.cacheResolver)这句代码初始化的

protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {Collection<? extends Cache> caches = cacheResolver.resolveCaches(context);if (caches.isEmpty()) {throw new IllegalStateException("No cache could be resolved for '" +context.getOperation() + "' using resolver '" + cacheResolver +"'. At least one cache should be provided per cache operation.");}return caches;
}

而最终是根据指定的CacheResolver调用resolveCaches返回的,我们通过上面的分析可以知道如果我们没有给spring容器注册一个CacheResolver,那么spring会默认使用SimpleCacheResolver,我们继续来到SimpleCacheResolver的resovlerCache方法

public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {// 拿到缓存注解中声明的cache名称Collection<String> cacheNames = getCacheNames(context);if (cacheNames == null) {return Collections.emptyList();}Collection<Cache> result = new ArrayList<>(cacheNames.size());for (String cacheName : cacheNames) {// 从缓存管理器中根据cache名称去拿到具体的cache组件Cache cache = getCacheManager().getCache(cacheName);if (cache == null) {throw new IllegalArgumentException("Cannot find cache named '" +cacheName + "' for " + context.getOperation());}result.add(cache);}return result;
}

这个方法里面就是拿到所有我们在缓存注解中声明的cache的名称,然后再通过这个cache名称去从CacheManager中拿到具体的Cache对象(CacheManager对象我们一定要给spring容器提供,不然会报错)

我们回到execute方法,接下来就是调用findCachedItem方法处理@Cacheable注解了

private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {Object result = CacheOperationExpressionEvaluator.NO_RESULT;for (CacheOperationContext context : contexts) {// 判断condition条件是否成立,如果不成立直接返回if (isConditionPassing(context, result)) {// 生成key值Object key = generateKey(context, result);// 根据key值从缓存中找到对应的value值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;
}
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {// 遍历所有的cache缓存组件for (Cache cache : context.getCaches()) {// 返回valueCache.ValueWrapper wrapper = doGet(cache, key);// 如果能够从一个cache组件中得到缓存值,那么就直接返回出去了if (wrapper != null) {if (logger.isTraceEnabled()) {logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");}return wrapper;}}return null;
}
protected Cache.ValueWrapper doGet(Cache cache, Object key) {try {return cache.get(key);}catch (RuntimeException ex) {getErrorHandler().handleCacheGetError(ex, cache, key);return null;  // If the exception is handled, return a cache miss}
}

上面的代码也很简单,就是首先会去通过condition属性判断下这个key是否需要被忽略,如果不被忽略的话就继续执行下面的代码,下面的代码会先通过key值生成器得到key值,然后通过这个这个key值去从cache对象中查询得到缓存

collectPutRequests(contexts.get(CacheableOperation.class),CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);

然后再去把需要执行的缓存请求调用collectPutRequests方法放到一个集合中,因为@Cacheable注解的作用是先从缓存中查询,如果缓存没有的话那么执行完目标方法把返回值放到缓存中,所以就需要放一个缓存请求作为标识,等执行完目标方法之后,就可以直接根据这个缓存请求去把方法返回结果放到缓存中

if (cacheHit != null && !hasCachePut(contexts)) {// If there are no put requests, just use the cache hit// 如果缓存命中了,后面直接返回缓存值cacheValue = cacheHit.get();// 如果方法返回值是Optional类型并且从缓存中拿出来的值不是Optional类型的,那么就包装为Optional类型的对象返回returnValue = wrapCacheValue(method, cacheValue);
}
else {// 如果缓存没有命中了,那么就执行目标方法得到返回值returnValue = invokeOperation(invoker);// 如果返回值是Optional类型的,那么就从Optional中拿到真正的结果值作为要缓存的值cacheValue = unwrapReturnValue(returnValue);
}

然后接着就是去判断缓存是否命中了,如果没有命中的话就需要执行目标方法得到返回值

// 处理@CachePut注解完善put集合
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

然后再去处理@CachePut注解,把缓存请求放到集合中

// 把方法返回结果放入缓存,针对两种情况
// 一是如果@Cacheable注解一进来查询不到缓存
// 二是@CachePut注解在方法执行完成之后
// 这两种情况,都会最终把方法返回值放到缓存
for (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);
}
public void apply(@Nullable Object result) {// 判断该返回值是否符合unless条件,如果符合就放到缓存if (this.context.canPutToCache(result)) {for (Cache cache : this.context.getCaches()) {// 把方法返回结果放到缓存doPut(cache, this.key, result);}}
}

然后最终就会针对缓存请求集合最终把数据放到缓存中,而此时我们可以知道这个缓存请求结合存放的缓存请求是可以来自两方面的,一个是@Cacheable注解进来查询不到缓存的时候会创建一个缓存请求,二是@CachePut注解创建的缓存请求

// 最后如果@CacheEvicts注解的beforeInvocation属性等于false的话就清空缓存
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

然后最后还是调用了processCacheEvicts这个方法,不过这一次是针对当@CacheEvict注解的beforeInvocation属性等于false的时候,表示在目标方法执行完成之后才会清除缓存

Spring缓存切面源码解析相关推荐

  1. Spring AOP面向切面源码解析

    IoC 和 AOP 被称为 Spring 两大基础模块 AOP(Aspect-Oriented Programming) 在程序设计领域拥有其不可替代的适用场景和地位.Spring AOP 作为 AO ...

  2. 【Spring源码】AOP切面源码

    [Spring源码]AOP切面源码 关键词 后置处理器BeanPostProcessor后置方法:applyBeanPostProcessorsAfterInitialization() 切面后置处理 ...

  3. Spring 之 @Cacheable 源码解析(上)

    一.@EnableCaching 源码解析 当要使用 @Cacheable 注解时需要引入 @EnableCaching 注解开启缓存功能.为什么呢?现在就来看看为什么要加入 @EnableCachi ...

  4. spring aop 注入源码解析

    spring aop 注入源码解析 aop启动 AbstractApplicationContext.java @Overridepublic void refresh() throws BeansE ...

  5. spring aop 注入源码解析 1

    spring aop 注入源码解析 aop启动 AbstractApplicationContext.java @Overridepublic void refresh() throws BeansE ...

  6. Spring 之 @Cacheable 源码解析(下)

    CacheInterceptor 缓存切面处理逻辑 接着上篇 Spring 之 @Cacheable 源码解析(上) 说起,代理对象已经创建成功,接着分析调用流程.那么应该从哪里入手呢?当然是去看 A ...

  7. Mybatis 缓存系统源码解析

    Mybatis 缓存系统源码解析 转载于:https://juejin.im/post/5bfa50905188251d0920006c

  8. FileInputFormat切片源码解析

    文章目录 FileInputFormat切片源码解析 1.MapTask并行度决定机制 2.源码步骤 3.FileInputFormat切片机制 3.1 源代码中计算切片大小的公式 3.2 获取切片信 ...

  9. Spring Cloud Gateway 源码解析(3) —— Predicate

    目录 RoutePredicateFactory GatewayPredicate AfterRoutePredicateFactory RoutePredicateHandlerMapping Fi ...

  10. spring 多线程 事务 源码解析(一)

    大家好,我是烤鸭: 今天分享的是spring 多线程事务源码分析. 环境: spring-jdbc 5.0.4.REALEASE 今天分享一下spring事务的方法,这一篇还没涉及到多线程. 简单说一 ...

最新文章

  1. 新浪微博登录接口实例
  2. 图的基本算法实现(邻接矩阵与邻接表两种方法)
  3. 这次是在没有外网yum仓库的情况下搭建内网yum仓库和无人值守pxe装机
  4. Matlab中pickic_法语「野餐」怎么写?不是picnic哦
  5. (4) hibernate增删查改+批量操作+类似Mybatis动态sql
  6. ppt变成了图片不能编辑文字怎么办_谁说水印一定要去掉?用到PPT里贼好看好吗!...
  7. JPA-EntityManager.merge()
  8. Python base64编码解码
  9. cisco engine memory
  10. 新手教程——在Linux Mint 16中找到保存的WiFi密码
  11. @RequestBody 的正确使用办法
  12. ★LeetCode(559)——N叉树的最大深度(JavaScript)
  13. javascript--方法(函数)
  14. 毫米波雷达探测技术,雷达人体存在感应器,实时检测静止存在应用
  15. 奇妙的数字 小明发现了一个奇妙的数字。它的平方和立方正好把0~9的10个数字每个用且只用了一次。你能猜出这个数字是多少吗?
  16. 全球及中国食用油市场竞争态势与营销策略分析报告2022版
  17. jQuery插件库链接
  18. python excel 读写,定义名称
  19. 二维占用栅格地图Occupancy grid maps
  20. ARP 应用——检测IP地址冲突

热门文章

  1. TensorFlow by Google CNN识别猫和狗 Machine Learning Foundations: Ep #6 - Convolutional cats and dogs
  2. Postman 导出 curl命令 到命令行运行 Mac OS
  3. 算法:在有序的链表中删除掉所有重复的数据(包括重复本身的节点)Remove Duplicates from Sorted List II
  4. 算法:回溯七 Permutation Sequence数组全排列定位
  5. linux命令hexdump,Linux中hexdump命令起什么作用呢?
  6. mysql设置report_host语法_mysqlreport使用详解
  7. 2021-09-23图嵌入方法
  8. 【机器学习系列】MCMC第二讲:Markov Chain Monte Carlo基本概念和核心思想
  9. Websocket 从header读取数据
  10. linux开发读取外部存储,Android 获取外接储存的设备路径(如挂载的U盘),android挂载...