当前位置: 首页 > news >正文

spring cache源码解析(四)——从@EnableCaching开始来阅读源码

一、spring cache四个注解

  • @EnableCaching:开启缓存功能;
  • @Cacheable:获取缓存;
  • @CachePut:更新缓存;
  • @CacheEvict:删除缓存;
  • @Caching:组合定义多种缓存功能;
  • @CacheConfig:定义公共设置,位于类之上;

1.1 @Cacheable里的几个参数

cacheManager:

cacheResolver:没太理解它的使用场景;

sync:是否同步。

  • 比如同时有3个请求进来,且三个请求的参数一致:如果sync为false,三个请求同时执行;如果sync为true,只会有一个请求执行方法操作,其他两个等待第一个的缓存结果,后续从缓存拿数据。
  • 官方话术:在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。
  • 只对参数一直参数影响,参数不一致不会影响;

拓展:

在这里插入图片描述

  • hutool工具类,测试专用:创建线程并发测试

二、spring cache实现原理-前言

没有讲源码之前,我们可以想象是怎么实现的:

  • 通过aop动态代理对指定方法进行增强;

2.1 正常的aop是怎么写的?

  • @Aspect;
  • @Before、@Around等,确定以什么样的方式,对什么样的方法进行拦截,最终实现对方法的增强;

例子就不举了,参考芋道:IdempotentAspect类。

三、@EnableCaching

@EnableCaching注解是缓存的开关,如果要使用缓存功能,就必要打开这个开关,这个注解可以定义在Configuration类或者springboot的启动类上面。

3.1 @Import(CachingConfigurationSelector.class)

在这里插入图片描述

3.2 @Import

@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:CachingConfigurationSelector.class。
它有一个实现其他类的selectImports方法,这个方法的本质是:将所有需要导入的组件以全类名的方式返回;
(出自SpringBoot自动装配原理与自己写一个starter第2.1.2.2节)

3.3 CachingConfigurationSelector

类机构图:
在这里插入图片描述

  • ImportSelector,定义了selectImports方法,返回值是字符串数组,这个数组是全路径类名,最终会spring拿到这些类名,实例化对应的类;

在这里插入图片描述>注意这里,泛型是EnableCaching类。

  • AdviceModeImportSelector#selectImports:
    在这里插入图片描述

  • CachingConfigurationSelector#selectImports:
    在这里插入图片描述

  • CachingConfigurationSelector#getProxyImports:
    在这里插入图片描述

3.4 ProxyCachingConfiguration.class

ProxyCachingConfiguration.class代码截图如下:
在这里插入图片描述
注入了三个对象:

  • BeanFactoryCacheOperationSourceAdvisor:作用相当于@Aspect注解的类做类似。注入一个xxxAdvisor对象到spring中,其实就相当于注入了一个@Aspect注解的类;
  • CacheOperationSource;
  • CacheInterceptor;

3.5 BeanFactoryCacheOperationSourceAdvisor:

在这里插入图片描述
set了两个bean:

  • CacheOperationSource:是一个解析器,用来拿类或方法上的注解;
  • CacheInterceptor:是一个拦截器,此次的核心代码就在这里;

BeanFactoryCacheOperationSourceAdvisor创建好了之后,相当于aop就创建好了,这个创建方式和我们手动写@Aspect类不太一样,但执行的过程是一样的。

由于CacheInterceptor类内容过多,且比较重要,下面单开一节讲:

四、CacheInterceptor

在这里插入图片描述

4.1 CacheInterceptor类关系图:

在这里插入图片描述

  • MethodInterceptor:spring aop相关的一个接口,用来拦截方法调用,它只有一个invoke方法;
  • CacheAspectSupport:

这里的MethodInterceptor接口是位于"org.aopalliance.intercept"包,除此之外Cglib工具包中的"org.springframework.cglib.proxy"包也有MethodInterceptor接口,但这个接口不是我们所需要的。

4.2 CacheInterceptor#invoke

此方法是重写的MethodInterceptor#invoke方法。
在这里插入图片描述

  1. 用匿名内部类,包装一个函数(此处是指原被注解接口的方法),后面可以调用到这个函数;
  2. 调用父类CacheAspectSupport#execute方法;
    在这里插入图片描述
    图上的三个问题下面一一解答。

4.1.1 initialized的是如何变为true的:

CacheAspectSupport实现了这两个类:

  • InitializingBean;
  • SmartInitializingSingleton;

InitializingBean:
在这里插入图片描述
SmartInitializingSingleton:
在这里插入图片描述

可以看到这两个都只有一个方法供实现类实现,看方法名可以看出来,是实例化之后或属性设置了之后分别调用这两个方法的。

看下afterSingletonsInstantiated方法:
在这里插入图片描述

4.1.2 Collection包装的是什么?

在这里插入图片描述
从上图可以看出,拿到的是:

  • 当前请求的方法信息,以及注解的相关信息(以列表的形式,只一个注解);

接下来追一下这段上面这段代码:

Collection<CacheOperation> operations =
cacheOperationSource.getCacheOperations(method, targetClass);

4.1.3 cacheOperationSource.getCacheOperations(method, targetClass);

在这里插入图片描述
追踪到了AbstractFallbackCacheOperationSource类,类结构如下:

public abstract class AbstractFallbackCacheOperationSource implements
CacheOperationSource

可以看到,他是一个抽象类,实现了CacheOperationSource接口。

继续往下:
在这里插入图片描述
无非就是三个步骤:

  1. 从attributeCache的Map中拿该方法对应的spring cache注解;
  2. 如果拿到了(空List也算拿到了),直接返回;如果拿的是空,证明没拿过,解析该方法,拿该方法上的spring cache注解;
  3. 把拿到的spring cache注解放到attributeCache的Map中;
  4. 解析到是空,放空List;

接下来看AbstractFallbackCacheOperationSource#computeCacheOperations源码:
在这里插入图片描述
上面用到了AopUtils#getMostSpecificMethod,它的源码我们也来看一下;

/**获取最特殊的方法*/
public static Method getMostSpecificMethod(Method method, @Nullable Class<?> targetClass) {  Class<?> specificTargetClass = targetClass != null ? ClassUtils.getUserClass(targetClass) : null;   // 获取最为准确的方法,即如果传入的method只是一个接口方法,则会去找其实现类的同一方法进行Method resolvedMethod = ClassUtils.getMostSpecificMethod(method, specificTargetClass);  // 如果当前方法是一个泛型方法,则会找Class文件中实际实现的方法return BridgeMethodResolver.findBridgedMethod(resolvedMethod);}

接下来,看一下findCacheOperations(specificMethod),中间的我了跳过了,直接追溯到SpringCacheAnnotationParser#parseCacheAnnotations方法:
在这里插入图片描述
parseCacheableAnnotation方法:
在这里插入图片描述
parseCacheableAnnotation方法:
在这里插入图片描述
这是解析Caching注解的方法,其他的spring cache注解也有对应的解析方法,这里就不往下看了。

到这里cacheOperationSource.getCacheOperations(method, targetClass)方法解读已经结束了,Collection装的是什么对象我们也知道了。

4.1.4 CacheAspectSupport#execute

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
......
}

这个方法是核心逻辑了。

4.1.4.1 同步处理

先看上半部分:如果sync为true,会先执行哪些逻辑:
在这里插入图片描述

  • 1处我没弄懂,这里的同步体现在哪?——难道是CucurrentHashMap,一定是它!!
  • 4处,就是拿缓存,如果没有,调底层方法,但同样没体现出同步;——同上,CucurrentHashMap,其他的比如redis,线程安全的。

注意:
sync如果是true,一定是@Cacheable注解。其他注解没有sync属性。
从上面可以看出,sync如果是true,其他的注解就不生效了,它直接就返回了。代码就是这样的

所以我们再回过头去看Cacheable中sync属性上方的注释,它写到:使用sync为true,会有这些限制:
在这里插入图片描述

  1. 不支持unless,这个从代码可以看到,只支持了condition,没有支持unless;这个我没想清楚为什么。。。但Interceptor代码就是这样写的;
  2. 只能有一个cache,因为代码就写死了一个。我猜这是为了更好地支持同步,它把同步放到了Cache里面去实现;
  3. 不支持其它的Cache操作,代码里面写死了,只支持Cachable,我猜这也是为了支持同步;

拓展阅读:https://blog.csdn.net/yiyihuazi/article/details/109065327

4.1.4.2 后续处理

继续看后续代码:

         //如果是@CacheEvict并配置了beforeInvocation是true,代表在执行实际方法前清除缓存processCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);// 检查是否有@Cacheable命中缓存Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));// 如果没有找到缓存项,则把每个没命中的@Cacheable,转成CachePut参数,供后面put缓存用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)) {// 如果没有@CachePut注解操作,直接获取缓存值cacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {// 执行实际方法,不需要取缓存returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// Collect any explicit @CachePuts//收集任何显式的@CachePutscollectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// Process any collected put requests, either from @CachePut or a @Cacheable miss//处理任何收集到的放置请求,无论是来自@CachePut还是@Cacheable missfor (CachePutRequest cachePutRequest : cachePutRequests) {cachePutRequest.apply(cacheValue);}// Process any late evictions//处理任何迟来的驱逐注解;与前面的区别在于beforeInvocation是true还是false。true是方法执行之前删除缓存,false是执行之后processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);return returnValue;

猜测是@CacheEvict的allEntries设置:
在这里插入图片描述
在这里插入图片描述


http://www.mrgr.cn/news/81113.html

相关文章:

  • C++ 面向对象编程:+号运算符重载,左移运算符重载
  • CDN信息收集(小迪网络安全笔记~
  • Mac上详细配置java开发环境和软件(更新中)
  • 剑指 Offer 60. n个骰子的点数
  • 进程内存转储工具|内存镜像提取-取证工具
  • test_4_17_2Snake
  • 【数据结构练习题】栈与队列
  • 浏览器工作原理与实践-12|栈空间和堆空间:数据是如何存储的
  • 【Linux进程】进程间通信(共享内存、消息队列、信号量)
  • Jetpack 练手项目 —— Sunflower
  • 计算机毕业设计PyFlink+Hadoop广告推荐系统 广告预测 广告数据分析可视化 广告爬虫 大数据毕业设计 Spark Hive 深度学习 机器学
  • 洛谷 P2142:高精度减法 ← string+数组
  • 大语言模型驱动的Agent:定义、工作原理与应用
  • HTML5前端实现毛玻璃效果的可拖拽登录框
  • 大数据操作实验一
  • Unittest01|TestCase、断言、装饰器、夹具、清理函数、ddt
  • 计算机网络基础图解
  • 【Python】pandas库---数据分析
  • 人工智能ACA(四)--机器学习基础
  • 「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具
  • SpringBoot提供的常用接口(拓展接口)示例
  • Flutter/Dart:使用日志模块Logger Easier
  • 【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
  • 「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
  • 电商项目-数据同步解决方案(一)
  • 「Mac畅玩鸿蒙与硬件46」UI互动应用篇23 - 自定义天气预报组件