从源码角度,带你研究什么是三级缓存

2021-12-05 13:36:02 浏览数 (1)

  • Java开发,总会遇到问三级缓存的。
  • 看了很多文章,感觉不是很透彻。打算自己写一个自以为很详细的对三级缓存的理解。 有图文。也有文字概括。受不了动图的可以看文字概括哦

进入正题:

  • 在开发过程中会遇到循环依赖的问题。 就跟下图一样
  • Spring在为此设计了三级缓存来解决以上依赖的问题
首先我们得知道 三级缓存里面分别存的什么

一级缓存里存的是成品对象,实例化和初始化都完成了,我们的应用中使用的对象就是一级缓存中的

二级缓存中存的是半成品,用来解决对象创建过程中的循环依赖问题

三级缓存中存的是 ObjectFactory<?> 类型的 lambda 表达式,用于处理存在 AOP 时的循环依赖问题

Spring 三级缓存的顺序

三级缓存的顺序是由查询循序而来,与在类中的定义顺序无关

所以第一级缓存:singletonObjects ,第二级缓存:earlySingletonObjects ,第三级缓存:singletonFactories

Spring 的的注入方式有三种:构造方法注入、setter 方法注入、接口注入

接口注入的方式太灵活,易用性比较差,所以并未广泛应用起来,大家知道有这么一说就好,不要去细扣了

构造方法注入的方式,将实例化与初始化并在一起完成,能够快速创建一个可直接使用的对象,但它没法处理循环依赖的问题,了解就好

setter 方法注入的方式,是在对象实例化完成之后,再通过反射调用对象的 setter 方法完成属性的赋值,能够处理循环依赖的问题,是后文的基石,必须要熟悉

Spring 源码分析

  • 下面会从几种不同的情况来进行源码跟踪
1、 没有依赖,有 AOP

代码非常简单: spring-no-dependence

  • 上图的逻辑就是 ClassPathXmlApplicationContext#refresh
  • -> this.finishBeanFactoryInitialization(beanFactory)
  • -> beanFactory.preInstantiateSingletons()
  • -> isFactoryBean 判断bean, 然后调用 getBean 方法。
  • 接下来调用 doGetBean 方法
  • 上图逻辑是 DefaultSingletonBeanRegistry#getSingleton 判断是否存在缓存当中,如果没有则进行创建Bean
  • 可以观察到
代码语言:javascript复制
 Map<String, Object> singletonObjects               无 simpleBean 
 Map<String, Object> earlySingletonObjects          无 simpleBean 
 Map<String, ObjectFactory<?>> singletonFactories   无 simpleBean 
 Set<String> singletonsCurrentlyInCreation          有 simpleBean 
  • 说明bean在创建过成中
  • 我们接着从 createBean 往下跟
  • 关键代码在 doCreateBean 中,其中有几个关键方法的调用值得大家去跟下
代码语言:javascript复制
 Map<String, Object> singletonObjects               存储的 simpleBean 的代理对象
 Map<String, Object> earlySingletonObjects          由始至终都没有 simpleBean 对象
 Map<String, ObjectFactory<?>> singletonFactories   存入了一会数据立马就删除了 并未使用过  
 
 // 以下说的方法均在 AbstractAutowireCapableBeanFactory 类下
 // createBeanInstance 通过反射完成对象的实例化,获得半成品对象。 给分配内存空间, 即使半成品
 // populateBean 填充半成品属性, 如果有依赖对象则在这里引入
 // initializeBean 初始化半成品对象
 // applyBeanPostProcessorsAfterInitialization BeanPostProcessor的后置处理,AOP 的代理对象替换就是在这里完成的  
  • 此时:代理对象的创建是在对象实例化完成,并且初始化也完成之后进行的,是对一个成品对象创建代理对象
  • 所以《没有依赖,有 AOP》 情况下:只用一级缓存就够了,其他两个缓存可以不要也能完成对象

2、循环依赖,没有AOP

代码依旧非常简单: spring-circle-simple 此时循环依赖的两个类是: CircleLoop

对象的创建过程与前面的基本一致,只是多了循环依赖,少了 AOP,所以我们重点关注: populateBeaninitializeBean 方法

先创建的是 Circle 对象,那么我们就从创建它的 populateBean 开始,再开始之前,我们先看看三级缓存中的数据情况

代码语言:javascript复制
 Map<String, Object> singletonObjects               无 circle 也无 loop 对象
 Map<String, Object> earlySingletonObjects          无 circle 也无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   只有 cicle 的 lambda
 Set<String> singletonsCurrentlyInCreation          只有 circle 
  • 我们开始跟populateBean,它完成属性的填充,与循环依赖有关,一定要仔细看,仔细跟

circle 对象的属性 loop 进行填充的时候,去 Spring 容器中找 loop 对象,发现没有则进行创建,又来到了熟悉的 createBean

此时三级缓存中的数据没有变化,但是 Set<String> singletonsCurrentlyInCreation 中多了个 loop 标识loop正在创建中

loop 实例化完成之后,对其属性 circle 进行填充,去 Spring 中获取 circle 对象,又来到了熟悉的 doGetBean

此时一、二级缓存 (singletonObjects``earlySingletonObjects) 中都没有 circleloop ,而三级缓存中有这两个

通过 getSingleton 获取circle时,三级缓存调用了 getEarlyBeanReference ,但由于没有 AOP,所以 getEarlyBeanReference 直接返回了普通的 半成品 circle

然后将 半成品 circle 放到了二级缓存,并将其返回,然后填充到了 loop 对象中

此时的 loop 对象就是一个成品对象了;接着将 loop 对象返回,填充到 circle 对象中,如下如所示

我们发现直接将 成品 loop 放到了一级缓存中,二级缓存自始至终都没有过 loop ,三级缓存虽说存了 loop ,但没用到就直接 remove 了

此时缓存中的数据,相信大家都能想到了

代码语言:javascript复制
 Map<String, Object> singletonObjects               无 circle 有 loop 对象
 Map<String, Object> earlySingletonObjects          有 circle 无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   无 circle 也无 loop
 Set<String> singletonsCurrentlyInCreation          有 circle 无 loop 因为loop创建完毕了
  • 当 loop 对象完成 创建bean的时候 会调用 DefaultSingletonBeanRegistry#getSingleton -> DefaultSingletonBeanRegistry#addSingleton
  • 将数据对象移动到一级缓存中。二级缓存的 circle 没用上就删除了, 只有 circle 存在三级缓存的数据被调用到了。将半成品的 circle 给返回给 loop对象
  • 所以《循环依赖,没有AOP》情况下:可以减少某个缓存,只需要两级缓存就够了
概括:(循环依赖,没有AOP)
  • 上头的步骤可概括为:
  • 第一步。doCreateBean 进行 circle 的创建,创建步骤为:
  • circle 的流程:
代码语言:javascript复制
- `AbstractBeanFactory#doGetBean`                               获取bean
- -> `AbstractAutowireCapableBeanFactory#createBean`            创建bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean`          开始创建bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory`   把bean的一个 lambda 到三级缓存去了 singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean`          填充bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues`   检查到有要添加的一来 进行填充
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary`      注意 ! 这个位置获取 loop 对象
```java
- 断点 我们观察下 三个缓存 Map的存储情况
```java 
 Map<String, Object> singletonObjects               无 circle 也无 loop 对象
 Map<String, Object> earlySingletonObjects          无 circle 也无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   有 circle 也无 loop 对象
 Set<String> singletonsCurrentlyInCreation          有 circle 
  • 第二步 然后 get loop Bean 会重复上面的步骤
代码语言:javascript复制
- `AbstractBeanFactory#doGetBean`                               获取bean
- -> `AbstractAutowireCapableBeanFactory#createBean`            创建bean
- -> `AbstractAutowireCapableBeanFactory#doCreateBean`          开始创建bean
- -> `AbstractAutowireCapableBeanFactory#addSingletonFactory`   把bean的一个 lambda 到三级缓存去了 singletonFactories
- -> `AbstractAutowireCapableBeanFactory#populateBean`          填充bean
- -> `AbstractAutowireCapableBeanFactory#applyPropertyValues`   检查到有要添加的一来 进行填充
- -> `BeanDefinitionValueResolver#resolveValueIfNecessary`      注意 ! 这个位置改了。获取的是 circle 对象
  • 断点 我们观察下 三个缓存 Map的存储情况
代码语言:javascript复制
 Map<String, Object> singletonObjects               无 circle 也无 loop 对象
 Map<String, Object> earlySingletonObjects          无 circle 也无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   有 circle 有 loop 对象
 Set<String> singletonsCurrentlyInCreation          有 circle 有 loop 说明两个对象都在创建中 
  • 关键点来了:
  • 第三步 相当于程序是第二次进入 circleAbstractBeanFactory#doGetBean
代码语言:javascript复制
- `AbstractBeanFactory#doGetBean`                                           第二次获取 circle
- `AbstractBeanFactory#getSingleton(beanName)`                              获取 Bean 的缓存
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true)`               获取 Bean 的缓存
- `DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation(beanName)`   关键!! 判断 circle 这个名字的bean是不是在创建过程
- `this.singletonFactories.get(beanName)`                                   获取这个 circle 的 lambda 创建函数
- `singletonFactory.getObject()`                                            调用函数 获取了一个半成品的对象。 也就是 loop 还为空的 circle对象
- `this.earlySingletonObjects.put(beanName, singletonObject)`               将对象加入到二级缓存里面去   earlySingletonObjects 增加了对象
// 附,只有 earlySingletonObjects 新增了一个 circle 对象,其他map 无改变。 并且loop的 singletonFactories 也未使用到
  • 然后就返回了 circle 给到 loop 进行属性填充
  • 完成 loop 创建 将 loop 在 (earlySingletonObjects、singletonFactories、singletonsCurrentlyInCreation)清除。loop添加对象到 singletonObjects
  • 返回创建好的 loop 给到 circle 的填充属性流程
  • 填充完毕之后。在(earlySingletonObjects、singletonFactories、singletonsCurrentlyInCreation)清除。 添加circle对象到 singletonObjects
  • 注意 : circle 就算只是半成品 那他也是在bean中是唯一的。 只要 circle 的属性在后面填充了loop 那么在 loop 的那个单例缓存里面。就会有循环依赖的 circle 对象
  • 其实在整个流程中 circle 会进入到二级缓存当中。但是没使用。就被remove了
  • loop 在二级缓存从来就没有出现过。因为不会进入两次 loop 的 doGetBean流程 。 loop的三级缓存数据也没使用过就被删除了。

2、循环依赖,有AOP

代码还是非常简单:spring-circle-aop ,在循环依赖的基础上加了 AOP

比上一种情况多了 AOP,我们来看看对象的创建过程有什么不一样;同样是先创建 Circle ,在创建Loop

创建过程与上一种情况大体一样,只是有小部分区别,跟源码的时候我会在这些区别上有所停顿,其他的会跳过,大家要仔细看

实例化 Circle ,然后填充 半成品 circle 的属性 loop ,去 Spring 容器中获取 loop 对象,发现没有

则实例化 Loop ,接着填充 半成品 loop 的属性 circle ,去 Spring 容器中获取 circle 对象 

这个过程与前一种情况是一致的,就直接跳过了,此时三级缓存中的数据如下:

代码语言:javascript复制
 Map<String, Object> singletonObjects               无 circle 也无 loop 对象
 Map<String, Object> earlySingletonObjects          无 circle 也无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   有 circle 有 loop 对象
 Set<String> singletonsCurrentlyInCreation          有 circle 有 loop 说明两个对象都在创建中 

我们发现从第三级缓存获取 circle 的时候,调用了 getEarlyBeanReference 创建了 半成品circle的代理对象

将 半成品 circle 的代理对象放到了第二级缓存中,并将代理对象返回赋值给了 半成品 loopcircle 属性

注意:此时是在进行 loop 的初始化,但却把 半成品 circle 的代理对象提前创建出来了

loop 的初始化还未完成,我们接着往下看,又是一个重点,仔细看

initializeBean 方法中完成了 半成品 loop 的初始化,并在最后创建了 loop 成品 的代理对象

loop 代理对象创建完成之后会将其放入到第一级缓存中(移除第三级缓存中的 loop ,第二级缓存自始至终都没有 loop

然后将 loop 代理对象返回并赋值给 半成品 circle 的属性 loop ,接着进行 半成品 circleinitializeBean

因为 circle 的代理对象已经生成过了(在第二级缓存中),所以不用再生成代理对象了;将第二级缓存中的 circle 代理对象移到第一级缓存中,并返回该代理对象

此时各级缓存中的数据情况如下(普通circle loop 对象在各自代理对象的 target 中)

代码语言:javascript复制
 Map<String, Object> singletonObjects               有 circle 代理对象 有 loop 代理对象
 Map<String, Object> earlySingletonObjects          无 circle 无 loop 对象
 Map<String, ObjectFactory<?>> singletonFactories   无 circle 无 loop 对象
 Set<String> singletonsCurrentlyInCreation          无 circle 无 loop 

我们回顾下这种情况下各级缓存的存在感,一级缓存仍是存在感十足,二级缓存有存在感,三级缓存挺有存在感

      第三级缓存提前创建 circle 代理对象,不提前创建则只能给 loop 对象的属性 circle 赋值成 半成品 circle ,那么 loop 对象中的 circle 对象就无 AOP 增强功能了

      第二级缓存用于存放 circle 代理,用于解决循环依赖;也许在这个示例体现的不够明显,因为依赖比较简单,依赖稍复杂一些,就能感受到了 第一级缓存存放的是对外暴露的对象,可能是代理对象,也可能是普通对象

所以此种情况下:三级缓存一个都不能少

概括: (2、循环依赖,有AOP)
  • 概括:(循环依赖,没有AOP)基本一致
  • 在第三步发生变化:
代码语言:javascript复制
- `AbstractBeanFactory#doGetBean`                                           第二次获取 circle
- `AbstractBeanFactory#getSingleton(beanName)`                              获取 Bean 的缓存
- `DefaultSingletonBeanRegistry#getSingleton(beanName, true)`               获取 Bean 的缓存
- `DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation(beanName)`   判断 circle 这个名字的bean是不是在创建过程
- `this.singletonFactories.get(beanName)`                                   获取这个 circle 的 lambda 创建函数 
- `singletonFactory.getObject()`                                            调用函数 获取了一个半成品的对象。(注意!! 有AOP环绕的对象在该位置会创建代理对象, 并且将代理对象 通过 AbstractAutoProxyCreator#getEarlyBeanReference 同步到AOP的创建类里边。为了后面的使用) 也就是 loop 还为空的 circle对象
- `this.earlySingletonObjects.put(beanName, singletonObject)`               将对象加入到二级缓存里面去   earlySingletonObjects 增加了对象
// 附,只有 earlySingletonObjects 新增了一个 circle 对象,其他map 无改变。 
  • 然后就完成了 loop 的创建。
  • 然后进行完 circle 填充之后。
代码语言:javascript复制
- -> `AbstractAutowireCapableBeanFactory#populateBean`          填充完bean之后
- -> `AbstractAutowireCapableBeanFactory#initializeBean`        进行 circle 的初始化
- -> `AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization`        bean的后置通知。此位置会进行 bean AOP的环绕 返回代理对象
- 由于在上方 loop 获取 circle 的时候不是已经创建了个代理对象了吗。那么这个aop就不能在新建一个代理类了。不然不一致
- 接着往下看
- -> `AbstractAutoProxyCreator#postProcessAfterInitialization`        创建代理对象
- -> `if (this.earlyProxyReferences.remove(cacheKey) != bean)`        这个时候  二级缓存派上用场了。在这里。判断是否已经有代理类了。如果有代理类则不新建代理类对象。
// 这样 circle 的代理就不会被重复创建了。  二级缓存也派上了用场

4、循环依赖 AOP 删除第三级缓存

没有依赖,有AOP 这种情况中,我们知道 AOP 代理对象的生成是在成品对象创建完成之后创建的,这也是 Spring 的设计原则,代理对象尽量推迟创建

循环依赖 AOP 这种情况中, circle 代理对象的生成提前了,因为必须要保证其 AOP 功能,但 loop 代理对象的生成还是遵循的 Spring 的原则

如果我们打破这个原则,将代理对象的创建逻辑提前,那是不是就可以不用三级缓存了,而只用两级缓存了呢?

代码依旧简单:spring-circle-custom ,只是对 Spring 的源码做了非常小的改动,改动如下

去除了第三级缓存,并将代理对象的创建逻辑提前,置于实例化之后,初始化之前;

总结

  1、三级缓存各自的作用

    第一级缓存存的是对外暴露的对象,也就是我们应用需要用到的

    第二级缓存的作用是为了处理循环依赖的对象创建问题,里面存的是半成品对象或半成品对象的代理对象

    第三级缓存的作用处理存在 AOP 循环依赖的对象创建问题,能将代理对象提前创建

  2、Spring 为什么要引入第三级缓存

    严格来讲,第三级缓存并非缺它不可,因为可以提前创建代理对象

    提前创建代理对象只是会节省那么一丢丢内存空间,并不会带来性能上的提升,但是会破环 Spring 的设计原则

    Spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 AOP 代理(尽可能延迟代理对象的生成)

    所以 Spring 用了第三级缓存,既维持了设计原则,又处理了循环依赖;牺牲那么一丢丢内存空间是愿意接受的

0 人点赞