什么是Spring的缓存抽象?

2022-07-08 14:15:39 浏览数 (1)

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

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

那么如何保证插件化呢?

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

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

首先我去查看官方文档,

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

行吧,那去Spring Framework文档看下

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

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

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

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

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

依赖如下(Gradle)

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

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

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

缓存生效了。

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

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

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

于是跟着找到了如下配置

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

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

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

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

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

我们现在知道什么?

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

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

重新启动下,查看调用栈

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

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

然后进入到这个方法中

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

然后执行ConfigurationClassParser的parse方法。

在ConfigurationClassParser 有几个重要的方法:

processImports

processConfigurationClass

doProcessConfigurationClass

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

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

接下来比较有趣。

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

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

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

我们看看是怎么做的?

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

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

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

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

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

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

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

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

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

那我们换成Caffine试一下

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

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

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

注意,这里面有个坑!

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

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

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

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

在spring-context-support的依赖内

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

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

或者说是防腐层?

似乎都是,你们觉得呢?

0 人点赞