一文讲懂Spring中三级缓存如何解决循环依赖问题以及各级缓存作用

三级缓存解决循环依赖问题是老生常谈的问题了,但是看了网上很多资料文章,每个人的说法和理解都不一样,而且对于一些细节几乎说的都不清楚,所以我决定一步步推导为什么使用三级缓存,希望读者都能彻底理解这个问题~

看本文知识需要:Bean的生命周期、AOP相关知识、循环依赖问题


正文开始~

首先循环依赖问题是由三级缓存解决的,一级缓存就是单例池,单例池中的Bean一定是完整创建完成的,可以直接通过getBean拿到并使用的。
将一个Bean的生命周期简单概括为四个步骤:实例化,依赖注入,后续操作(其中涉及到AOP),添加到单例池。


假设现在有2个Service,A和B,两者互相依赖,那么生命周期如下:

1.实例化A--->A普通对象(为什么说是普通对象,后面再解释)
2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
            2.1实例化B--->B普通对象
            2.2依赖注入--->填充A--->单例池中找A--->找不到则创建A(???)
            2.3后续操作(AOP)
            2.4添加到单例池
3.后续操作(AOP)
4.添加到单例池


那么问题就来了,步骤2.2中,B需要注入A,从单例池中找不到A,所以就要创建A,可是我么现在就是正在创建A,这就形成了死循环,该怎么打破循环呢?

其实我们所需要解决的问题就是,在B注入A时,可以让B找到A对象,那么我们可以在第一步A实例化后就保存它(即使此时它还不是一个完整的Bean),为了可以找到A,我们可以用Map来存储。既然我们的好兄弟Map帮我们解决了这个难题,那就叫他BroMap吧。此时生命周期如下:

1.实例化A--->A普通对象--->BroMap.put("A", A普通对象)
2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
            2.1实例化B--->B普通对象
            2.2依赖注入--->填充A--->单例池中找A--->BroMap.get("A")--->填充完毕
            2.3后续操作(AOP)
            2.4添加到单例池
3.后续操作(AOP)
4.添加到单例池

此时A和B都可以走完他们的生命周期了,那么循环依赖的问题就解决掉了!

真的解决掉了吗?还会不会存在其他的问题?
前面提到了普通对象,有普通对象那就有不普通的对象,也就是AOP中的代理对象。
在后续操作中,如果需要AOP的话,那么我们最后一步加入到单例池中的就不是普通对象A,而是代理对象A了。这看起来没什么问题,但是在步骤2.2中,B中注入的是普通对象A呀,而它也应该注入代理对象A。但是生成代理对象的操作是在步骤3中的后续操作中进行的,所以我们就需要将这一步骤提前,在B注入A前就已经生成代理对象A了,我们假设将这个操作放在步骤1,那么生命周期如下:

1.实例化A--->AOP--->A代理对象--->BroMap.put("A", A代理对象)
2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
            2.1实例化B--->B普通对象
            2.2依赖注入--->填充A--->单例池中找A--->BroMap.get("A")--->填充完毕
            2.3后续操作(AOP)
            2.4添加到单例池
3.后续操作
4.添加到单例池

看起来好像又没什么毛病,但是,但是,你怎么知道A需要AOP,万一A没有进行AOP,那么将这个步骤放在第一步是不是就不合理了。所以我们需要判断是否产生循环依赖,如果产生并且需要AOP的话,我们就将这一步骤提前。
如何判断产生了循环依赖?这些步骤中,步骤2.2是最好判断的,因为当B注入A时,如果我们能知道A此时正在被创建,那么就可以证明发生了循环依赖,我们可以用个容器记录一下哪些Bean正在被创建,这里用一个set,取名为creatingSet吧,那么生命周期如下:

0.creatingSet.add("A")
1.实例化A
2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
            2.1实例化B--->B普通对象
            2.2依赖注入--->填充A--->单例池中找A--->creatingSet中发现了A--->出现了循环依赖--->AOP--->A代理对象--->填充完毕
            2.3后续操作(AOP)
            2.4添加到单例池
3.后续操作
4.添加到单例池
5.creatingSet.remove("A")

看起来又没什么毛病,但是如果A中又注入了个C,C中也注入了个A,也就是A同时和两个Bean循环依赖,那么就出现问题了。假设现在生命周期如下:

0.creatingSet.add("A")
1.实例化A

2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
    2.1实例化B--->B普通对象
    2.2依赖注入--->填充A--->单例池中找A--->creatingSet中发现了A--->出现了循环依赖--->AOP--->A代理对象--->填充完毕
    2.3后续操作(AOP)
    2.4添加到单例池

2.依赖注入--->填充C--->单例池中找C--->找不到则创建C
    2.1实例化C--->C普通对象
    2.2依赖注入--->填充A--->单例池中找A--->creatingSet中发现了A--->出现了循环依赖--->AOP--->A代理对象--->填充完毕
    2.3后续操作(AOP)
    2.4添加到单例池

3.后续操作
4.添加到单例池
5.creatingSet.remove("A")

我们可以发现,在注入B和C时,他们都创建了A的代理对象,那么A就不满足单例了呀!那么我们就需要用一个容器来存储它,如果别的Bean在创建时也需要注入,先从容器中去找,找到就不创建了,没找到就创建,是不是就可以满足单例了。那么我们的BroMap重新上场吧,现在生命周期如下:

0.creatingSet.add("A")
1.实例化A

2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
            2.1实例化B--->B普通对象
            2.2依赖注入--->填充A--->单例池中找A--->creatingSet中发现了A--->出现了循环依赖--->BroMap.get("A")--->没找到就创建--->AOP--->A代理对象--->BroMap.put("A", A)--->填充完毕
            2.3后续操作(AOP)
            2.4添加到单例池
3.后续操作
4.添加到单例池
5.creatingSet.remove("A")

看起来好像只需要两个缓存就可以解决循环依赖的问题,那么三级缓存是用来干嘛的?
我们可以观察上面的流程,发现一个问题,在AOP生成代理对象时,我们是需要通过普通对象来生成的,可是我们并没有保存普通对象呀!所以我们还需要一个容器来保存我们的普通对象的生成方法(可以叫它工厂,专门用来生产对象),当后面我们无论是需要普通对象还是代理对象,都可以通过这个工厂来生成,我们用一个map来存储,就叫他factoryMap吧!此时生命周期如下:

0.creatingSet.add("A")
1.实例化A--->factoryMap.put("A", A的工厂)

2.依赖注入--->填充B--->单例池中找B--->找不到则创建B
    2.1实例化B--->B普通对象
    2.2依赖注入--->填充A--->单例池中找A--->creatingSet中发现了A--->出现了循环依赖--->BroMap.get("A")--->没找到就创建--->factoryMap.get("A") 拿到工厂--->AOP--->A代理对象--->BroMap.put("A", A)--->填充完毕
    2.3后续操作(AOP)
    2.4添加到单例池
3.后续操作
4.添加到单例池
5.creatingSet.remove("A")

好了,循环依赖的问题就解决了,那么我们给这几个容器重新起个名字吧!
1.singletonObjects:一级缓存,也就是单例池,用来存放完整的Bean
2.earlySingletonObjects:二级缓存,也就是我们的BroMap,用来存放实例化后还没填充属性的Bean半成品。
3.singletonFactories:三级缓存,也就是我们的factoryMap,用来存放对象的工厂,用来创建对象的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值