Spring Cache 带你飞(一)

Spring 3.1 版本引入基于 annotation 的 cache 技术,提供了一套抽象的缓存实现方案,通过注解方式使用缓存,基于配置的方式灵活使用不同缓存组件。代码具有相当的灵活性和扩展性,本文基于 Spring 5.x 源码一起分析 Spring Cache 的代码艺术。

开启 Spring Cache#

想让 Spring 提供 Cache 能力很简单,只需要在启动类加上 @EnableCaching 注解即可:

Copy
@Configuration @EnableCaching public class ServerMain { }

通过在启动类上添加 EnableCaching 注解将 Cache 相关的组件注入到 Spring 启动中,通过 Proxy 或者 AspectJ 的方式获取 Cache 对应的执行信息。

如果你希望在启动时修改一些 Cache 底层对应的基础管理信息,可以通过在启动类上覆盖 CachingConfigurerSupport 提供的相关方法来实现:

Copy
@EnableCaching @SpringBootApplication public class WebDemoApplication extends CachingConfigurerSupport { public static void main(String[] args) { SpringApplication.run(WebDemoApplication.class, args); } @Override public CacheManager cacheManager() { return super.cacheManager(); } @Override public KeyGenerator keyGenerator() { return super.keyGenerator(); } }

上面的示例代码中重写了 CacheManager 和 KeyGenerator 两个类的构建实现,分别实现的功能是 Cache 管理方式和 Cache key 生成方式。

从 Bean 加载机制了解 Spring 的 Cache 执行管理#

从启动类入手我们很容易看到整体的 Cache 管理方式:

Copy
@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 2147483647; }
  • proxyTargetClass:false,表示使用 JDK 代理,true 表示使用 cglib 代理。

  • mode:指定 AOP 的模式,当值为 AdviceMode.PROXY 时表示使用 Spring aop,当值为当值为AdviceMode.ASPECTJ 时,表示使用 AspectJ。

EnableCaching 使用时导入 CachingConfigurationSelector 类,我们看看加载了什么信息:

Copy
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> { public String[] selectImports(AdviceMode adviceMode) { switch(adviceMode) { case PROXY: return this.getProxyImports(); case ASPECTJ: return this.getAspectJImports(); default: return null; } } ...... ...... ...... static { ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader(); jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader); jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration", classLoader); } }

可以看到主要做了一件事,通过代理方式的不同加载对应的 CacheConfiguration :

  • 如果是 JDK Proxy,加载 AutoProxyRegistrar 类和 ProxyCachingConfiguration 类;
  • 如果是 AspectJ,则加载 AspectJCachingConfiguration 类。

Spring Boot 默认使用 JDK Proxy,我们就看 JDK Proxy 的使用。

AutoProxyRegistrar 的作用就是创建代理对象,内部通过调用 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry) 方法在 IOC 容器中注册一个 AutoProxyCreator。最终注入到容器中的 AutoProxyRegistrar 是一个 InfrastructureAdvisorAutoProxyCreator 类型:

Copy
@Nullable public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source); }

InfrastructureAdvisorAutoProxyCreator 类型只会为基础设施类型的 Advisor 自动创建代理对象。它只会去找符合条件的 bean创建代理,从源码可见只有 role 为 BeanDefinition.ROLE_INFRASTRUCTURE 的满足条件。

ProxyCachingConfiguration 创建了 3 个 bean,CacheOperationSource 关注如何获取所有拦截的切面,

CacheInterceptor 解决要对拦截到的切面做什么。

Copy
public class ProxyCachingConfiguration extends AbstractCachingConfiguration { @Bean( name = {"org.springframework.cache.config.internalCacheAdvisor"} ) @Role(2) public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) { BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource); advisor.setAdvice(cacheInterceptor); if (this.enableCaching != null) { advisor.setOrder((Integer)this.enableCaching.getNumber("order")); } return advisor; } ...... }

BeanFactoryCacheOperationSourceAdvisor 是 Spring Cache 自己实现的 Advisor,会对所有能取出 CacheOperation 的方法执行 CacheInterceptor 这个 Advice。

小知识:

Spring AOP 的创建过程本质是实现一个 BeanPostProcessor,在创建 bean 的过程中创建 Proxy,并且为 Proxy 绑定所有适用于该 bean 的 advisor,最终暴露给容器。

Spring 中 AOP 几个关键的概念 advisor, advice, pointcut

advice = 切面拦截中插入的行为

pointcut = 切面的切入点

advisor = advice + pointcut

BeanFactoryCacheOperationSourceAdvisor内部的切入点实现类是 CacheOperationSourcePointcut,切入的逻辑如下:

Copy
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { private class CacheOperationSourceClassFilter implements ClassFilter { private CacheOperationSourceClassFilter() { } public boolean matches(Class<?> clazz) { if (CacheManager.class.isAssignableFrom(clazz)) { return false; } else { CacheOperationSource cas = CacheOperationSourcePointcut.this.getCacheOperationSource(); return cas == null || cas.isCandidateClass(clazz); } } } }

可以看到切入点主要就是判断被切入的方法上是否有注解:CacheOperationSourcePointcut.this.getCacheOperationSource()

ProxyCachingConfiguration 中还有另一个值得关注的类 - AnnotationCacheOperationSource

Copy
@Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheOperationSource cacheOperationSource() { return new AnnotationCacheOperationSource(); }

CacheOperationSource 持有一个 CacheAnnotationParser 列表。 CacheAnnotationParser 只有一个实现类:SpringCacheAnnotationParser

Copy
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable { private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8); static { CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class); CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class); CACHE_OPERATION_ANNOTATIONS.add(CachePut.class); CACHE_OPERATION_ANNOTATIONS.add(Caching.class); } ...... @Nullable private Collection<CacheOperation> parseCacheAnnotations( DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) { 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); anns.stream().filter(ann -> ann instanceof Cacheable).forEach( ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann))); anns.stream().filter(ann -> ann instanceof CacheEvict).forEach( ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann))); anns.stream().filter(ann -> ann instanceof CachePut).forEach( ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann))); anns.stream().filter(ann -> ann instanceof Caching).forEach( ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops)); return ops; } ...... }

可以看到 Parser 的作用就是将注解对应的方法解析成 CacheOperation 存起来。

看到这里我们开始有点眉目,开始知道去哪里找拦截了什么和操作了什么。接下来继续对 CacheOperationCacheInterceptor 进行深入。

CacheOperation#

CacheOperationSource 接口中只有一个方法:

Copy
public interface CacheOperationSource { default boolean isCandidateClass(Class<?> targetClass) { return true; } @Nullable Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass); }

该接口中只有一个方法:通过接口和类名获得对应的 CacheOperationCacheOperation 是对缓存操作的抽象封装,它的实现类有 3 个:

三个实现类分别对应着 @CacheEvict@CachePut@Cacheable 注解。

CacheInterceptor#

CacheInterceptor 实现的功能就是对 目标方法的实际拦截操作:

Copy
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); } }; Object target = invocation.getThis(); Assert.state(target != null, "Target must not be null"); try { return execute(aopAllianceInvoker, target, method, invocation.getArguments()); } catch (CacheOperationInvoker.ThrowableWrapper th) { throw th.getOriginal(); } } }

CacheInterceptor 代码很简洁,采用函数的形式封装了真正要执行的函数逻辑,最终把此函数传交给父类的 execute()去执行。很显然最终执行目标方法的是 invocation.proceed()。我们直接去父类 CacheAspectSupport 看相关代码逻辑:

Copy
public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { protected final Log logger = LogFactory.getLog(getClass()); private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024); private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator(); @Nullable private CacheOperationSource cacheOperationSource; private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new); @Nullable private SingletonSupplier<CacheResolver> cacheResolver; ...... }

上面摘录出 的一些属性:

Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache

这个 Map 缓存了所有被注解修饰的类或者方法对应的基本属性信息。

CacheOperationExpressionEvaluator evaluator

解析一些 condition、key、unless 等可以写 el 表达式的处理器。

SingletonSupplier<KeyGenerator> keyGenerator

key 生成器默认使用的 SimpleKeyGenerator,注意 SingletonSupplier 是 Spring5.1 的新类,实现了接口java.util.function.Supplier ,主要是对 null 值进行容错。

接着看相关的方法:

Copy
public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton { // 这个接口来自于 SmartInitializingSingleton 在实例化完所有单例Bean后调用 //可以看到在这里实例化 CacheManager, CacheManager 我们后面会说作用 @Override public void afterSingletonsInstantiated() { if (getCacheResolver() == null) { // Lazily initialize cache resolver via default cache manager... Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect"); try { setCacheManager(this.beanFactory.getBean(CacheManager.class)); } catch (NoUniqueBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " + "CacheManager found. Mark one as primary or declare a specific CacheManager to use.", ex); } catch (NoSuchBeanDefinitionException ex) { throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " + "Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.", ex); } } this.initialized = true; } //根据CacheOperation 封装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())) { operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class); } else { operationKeyGenerator = 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); operationCacheResolver = new SimpleCacheResolver(cacheManager); } else { 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; } //真正执行目标方法+ 缓存 的实现 @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) // 如果已经初始化过(有CacheManager,CacheResolver),执行这里 if (this.initialized) { //getTargetClass拿到原始Class 解剖代理 Class<?> targetClass = getTargetClass(target); //简单的说就是拿到该方法上所有的CacheOperation缓存操作,最终一个一个的执行 CacheOperationSource cacheOperationSource = getCacheOperationSource(); if (cacheOperationSource != null) { // CacheOperationContexts是非常重要的一个私有内部类 // 注意它是复数!不是CacheOperationContext单数 // 所以它就像持有多个注解上下文一样 一个个执行 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(); } //内部调用的 execute 方法 @Nullable private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) { // 判断是否同步执行 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); } } // sync=false的情况,走这里 // Process any early evictions beforeInvocation=true的会在此处最先执行~~~ // 最先处理@CacheEvict注解~~~真正执行的方法请参见:performCacheEvict // context.getCaches()拿出所有的caches,看看是执行cache.evict(key);方法还是cache.clear(); // 需要注意的的是context.isConditionPassing(result); condition条件此处生效,并且可以使用#result // context.generateKey(result)也能使用#result // @CacheEvict没有unless属性 processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT); // 执行@Cacheable 看看缓存是否能够命中 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); // 如果缓存没有命中,那就准备一个cachePutRequest // 因为@Cacheable首次进来肯定命中不了,最终肯定是需要执行一次put操作,这样下次进来就能命中 List<CachePutRequest> cachePutRequests = new ArrayList<>(); if (cacheHit == null) { collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests); } Object cacheValue; Object returnValue; // 如果缓存命中了,并且并且没有@CachePut的话,也就直接返回 if (cacheHit != null && !hasCachePut(contexts)) { cacheValue = cacheHit.get(); returnValue = wrapCacheValue(method, cacheValue); } else { // 有 cachePut 的情况,先执行目标方法再 put 缓存 returnValue = invokeOperation(invoker); cacheValue = unwrapReturnValue(returnValue); } // 封装cacheput对象 collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests); // 真正统一执行 cacheput 的地方 for (CachePutRequest cachePutRequest : cachePutRequests) { cachePutRequest.apply(cacheValue); } // 最后才执行 cacheEvict processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); return returnValue; } //缓存属性上下文对象 private class CacheOperationContexts { private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts; //检查注解是否配置同步执行的开关 private final boolean sync; public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method, Object[] args, Object target, Class<?> targetClass) { //将当前 method 上所有缓存操作封装到一个map 对象中 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<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) { Collection<CacheOperationContext> result = this.contexts.get(operationClass); return (result != null ? result : Collections.emptyList()); } public boolean isSynchronized() { return this.sync; } //只有@Cacheable有sync属性,所以只需要看CacheableOperation即可 private boolean determineSyncFlag(Method method) { List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class); if (cacheOperationContexts == null) { // no @Cacheable operation at all return false; } boolean syncEnabled = false; //只要有一个@Cacheable的sync=true了,那就为true 并且下面还有检查逻辑 for (CacheOperationContext cacheOperationContext : cacheOperationContexts) { if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) { syncEnabled = true; break; } } // 执行sync=true的检查逻辑 if (syncEnabled) { // sync=true时候,不能还有其它的缓存操作 也就是说@Cacheable(sync=true)的时候只能单独使用 if (this.contexts.size() > 1) { throw new IllegalStateException( "@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'"); } //@Cacheable(sync=true)时,多个@Cacheable也是不允许的 if (cacheOperationContexts.size() > 1) { throw new IllegalStateException( "Only one @Cacheable(sync=true) entry is allowed on '" + method + "'"); } // 拿到唯一的一个@Cacheable CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next(); CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation(); //@Cacheable(sync=true)时,cacheName只能使用一个 if (cacheOperationContext.getCaches().size() > 1) { throw new IllegalStateException( "@Cacheable(sync=true) only allows a single cache on '" + operation + "'"); } //sync=true时,unless属性是不支持的,并且是不能写的 if (StringUtils.hasText(operation.getUnless())) { throw new IllegalStateException( "@Cacheable(sync=true) does not support unless attribute on '" + operation + "'"); } return true; } return false; } } }

execute 相关的代码上面注解写的很清晰,大家可以跟着多看几遍。这里还有一点没有说,最终的 get Cache 或者 put Cache 的操作在哪里呢?刚才在 execute 方法中有一句代码:

Copy
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

这里我们看到是先查缓存中是否有该 Cache,进去看有个 findCaches 方法:

Copy
@Nullable private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) { for (Cache cache : context.getCaches()) { Cache.ValueWrapper wrapper = doGet(cache, key); if (wrapper != null) { if (logger.isTraceEnabled()) { logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'"); } return wrapper; } } return null; }

重点关注 doGet 方法,可以看到这个方法是父类 AbstractCacheInvoker 中的,同类里面还有 doPut,doEvict 和 doClear。

小知识

@Cacheable 注解 sync=true 的效果

多线程环境下存在多个操作使用相同的参数同步调用相同的 key,默认情况下缓存不锁定任何资源所以可能导致多次计算。对于这种情况,sync 属性可以将底层锁住,使得只有一个线程进行操作,其他线程堵塞直到更新完缓存返回结果。

小结#

至此我们把上面说过的内容总结一下:

BeanFactoryCacheOperationSourceAdvisor

配置 Cache 的切面和拦截实现。拦截的对象即解析出来的 CacheOperation 对象。

每一个 CacheOperation 在执行的时候被封装为 CacheOperationContext 对象(一个方法可能被多个注解修饰),最终通过 CacheResolver 解析出缓存对象 Cache。

CacheOperation

封装了@CachePut@Cacheable@CacheEvict的属性信息,以便于拦截的时候能直接操作此对象来执行逻辑。

解析注解对应的代码为 CacheOperation 的工作是 CacheAnnotationParser 来完成的。

CacheInterceptor

CacheInterceptor 执行真正的方法执行和 Cache 操作。最终调用其父类提供的四个 do 方法处理 Cache。

以上整体过程为 Spring 启动对相关注解所在类或者方法的拦截和注入,从而实现 Cache 逻辑。限于篇幅本篇暂讨论这些内容,下一篇我们来看 Spring 如何实现对多缓存底层方案的支持(本地 Cache,Redis,Guava Cache,Caffeine Cache)。

posted @   rickiyang  阅读(2072)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
CONTENTS

"万一有人喜欢我呢"