什么是Spring的缓存抽象?

wg204wg
发布于 2023-7-4 12:33
浏览
0收藏

在项目中是否有遇到过这样的需求?

你负责的项目需要一个功能,但是这个负责实现这个功能却不是你,很可能是第三方厂商给你的一个服务。


那么如何保证插件化呢?


有时候我会想spring是怎么做的,spring如何做到适配不同的数据库连接池,不同的缓存组件呢?


带着疑问,我翻阅了下Spring的源码,关于缓存抽象这部分。


首先我去查看官方文档,


Spring Boot只留下了简短的几行话,首先是开启@EnableCaching,然后更多文档请移步Spring Framework的文档。

什么是Spring的缓存抽象?-鸿蒙开发者社区

行吧,那去Spring Framework文档看下

什么是Spring的缓存抽象?-鸿蒙开发者社区

这里吐槽下,Spring文档的左边大纲不见好久了,每次都不能愉快的翻来翻去了。


文档较长,我这里总结下。


  1. 讲了缓存是干嘛的
  2. 讲了@Cacheable @CacheEvict等注解怎么用的
  3. 讲了缓存的key如何生成
  4. 讲了条件缓存
  5. 讲了如何自定义缓存注解
  6. 讲了JSR-107注解
  7. 讲了几种缓存的实现组件 ConcurrentHashMap/Caffine等实现


大部分是将如何使用的,那我们试试。


如果只是使用ConcurrentHashMap这种类型的缓存的话,我们甚至都不需要依赖spring-boot-starter-cahce(spring-context-support)这个依赖就可以使用。


依赖如下(Gradle)

什么是Spring的缓存抽象?-鸿蒙开发者社区

这里有一个特别简单的方法, 就是传入一个参数,获取一个字符串.

什么是Spring的缓存抽象?-鸿蒙开发者社区

主方法如下,加上@EnableCaching注解

什么是Spring的缓存抽象?-鸿蒙开发者社区

运行后可以发现,调用了两次方法,但是实际只执行了一次

什么是Spring的缓存抽象?-鸿蒙开发者社区

缓存生效了。


那么现在用的是哪种缓存呢?我们根据官网可以看到有很多种。


我们知道想要生效,指定是经过了某些配置的,那么一定是在autoconfigure包内,于是我们找到了CacheAutoConfiguration.

什么是Spring的缓存抽象?-鸿蒙开发者社区

里面有个方法,往spring容器注册bean定义

什么是Spring的缓存抽象?-鸿蒙开发者社区

于是跟着找到了如下配置

什么是Spring的缓存抽象?-鸿蒙开发者社区

CacheAutoConfiguration导入了这么多的缓存配置,有EnCache的,有Redis的,有CAFFEINE的,真正生效的是哪个呢?


于是我们把所有的配置都打上断点,发现最终是SimpleCacheConfiguration生效

什么是Spring的缓存抽象?-鸿蒙开发者社区

那么问题来了,为什么这么多Configuration,偏偏只是SimpleCacheConfiguration生效呢?更何况按照上面的顺序,SimpleCacheConfiguration甚至还是倒数第二的优先级。


那我们首先肯定是想要这些配置类首先是需要被解析的(不了解的可以看我之前写的spring bean的声明周期),解析Configuration Class, 然后注册bean定义,当然这里注册肯定是有条件的。


那我们现在想要搞明白的是,到底其他的9个配置类有没有注册bean定义到spring容器中呢?


我们现在知道什么?


CacheAutoConfiguration自动配置类,导入了10个Cache的配置。然后只有一个配置类生效。


那我们不妨把断点打到导入配置的部分来,也就是这里

什么是Spring的缓存抽象?-鸿蒙开发者社区

重新启动下,查看调用栈

什么是Spring的缓存抽象?-鸿蒙开发者社区

可以看到,在refresh,调用了invokeBeanFactoryPostProcessors,然后来到ConfigurationClassPostProcessor的processConfigBeanDefinitions()的方法中。


从名字我们就能猜出来,这个解析标注了@Configuration的类的。


然后进入到这个方法中

什么是Spring的缓存抽象?-鸿蒙开发者社区

人家的注释也印证了我们的猜想,这个是解析所有的@Configuration类。


然后执行ConfigurationClassParser的parse方法。


在ConfigurationClassParser 有几个重要的方法:

processImports

processConfigurationClass

doProcessConfigurationClass


首先是解析CacheAutoConfiguration这个类,进入到processImports方法,processImports方法内会调用processConfigurationClass方法,然后调用doProcessConfigurationClass方法,然后会接着调用processImports,然后解析ImportSelector,获得了10个类。


也就是说在解析CacheAutoConfiguration时,加载到了10个CacheConfiguration,然后再递归解析每个配置类。

什么是Spring的缓存抽象?-鸿蒙开发者社区

接下来比较有趣。

会调用一个asSourceClasses方法,因为我们得到的一个类的全限定名,又不是一个真正的Class对象,那我们势必是要转化一波的。


当我们解析到JCacheConfiguration时,发现在

什么是Spring的缓存抽象?-鸿蒙开发者社区

这一步时,永远都会返回一个object对象,这里是因为这一步测试调用了相当于@ConditionalOnClass。


我们看看是怎么做的?

什么是Spring的缓存抽象?-鸿蒙开发者社区

其实特别简单,就是Class.forName调用一下,没抛出异常就说明@ConditionOnClass匹配成功。于是就返回true。


于是上一步就返回了Object对象。


于是根据导入的10个Configuration上面的@ConditinalOnXxx。生成了如下的配置类

什么是Spring的缓存抽象?-鸿蒙开发者社区

接下来我们把断点直接放到

什么是Spring的缓存抽象?-鸿蒙开发者社区

可以看到,在ConfigurationParser解析完毕后,关于Cache的配置类有四个。其中一个是Object类。


然后将这个四个配置注册到bean定义中,当然,首先还是要判断@Conditioanl的,@Conditional 有两个状态  一个是解析Configuration时生效,一个是注册bean定义时。在注册bean定义的时候就用到了@ConditionalOnBean了。


然后在注册bean定义时,过滤掉了GenericCacheConfiguration和NoOpsConfiguration这两个配置类。


所以到最后只有SimpleCacheConfiguration生效了。


以上是简单的缓存实现,使用的ConcurrentHashMap进行缓存。


那我们换成Caffine试一下

什么是Spring的缓存抽象?-鸿蒙开发者社区

这里看出来,应该是有这两个类应该就能生效了。


那我们试试看,如下图,我们加入caffeine的依赖,查看是否真的能够自动配置成功?

什么是Spring的缓存抽象?-鸿蒙开发者社区

什么是Spring的缓存抽象?-鸿蒙开发者社区

我们看到,确实是caffeine的配置生效了。


注意,这里面有个坑!


如果caffeine的版本不对,很可能配置不生效,因为ClassLoader加载类失败,导致OnClassCondition过不去,从而导致caffeine的配置不起作用。


为什么只加入了caffeine的实现,spring就能操作caffeine,就好像你操作其他的缓存实现一样呢?


这就奇了怪了,caffeine又不是Spring自己写的东西,给我们的感觉就像caffeine实现了spring的cache相关的接口似的。


其实我们翻开spring-boot-starter-cache这个依赖,会发现里面只有一个spring-context-support的依赖。


在spring-context-support的依赖内

什么是Spring的缓存抽象?-鸿蒙开发者社区

Spring对caffeine进行了一层包装,使得spring的缓存抽象能够完成像其他缓存一样的操作。


似乎这可以说是 策略模式?适配器模式?装饰者模式?


或者说是防腐层?


似乎都是,你们觉得呢?


文章转载自公众号:凯哥的Java技术活

标签
已于2023-7-4 12:33:00修改
收藏
回复
举报
回复
    相关推荐