小言_互联网的博客

Spring 如何解决循环依赖

422人阅读  评论(0)

一、Spring中的循环依赖

1.Spring中的循环依赖基础

1.什么是循环依赖?

循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。

我们这里以两个类A和B为例进行讲解,如下是A和B的声明:


  
  1. @Component
  2. public class A {
  3. private B b;
  4. public void setB (B b) {
  5. this.b = b;
  6. }
  7. }
  8. @Component
  9. public class B {
  10. private A a;
  11. public void setA (A a) {
  12. this.a = a;
  13. }
  14. }

结论先行:

1.构造器循环依赖----初始化失败

2.field属性注入循环依赖----初始化成功

3.field属性注入循环依赖(prototype)----初始化失败

现象总结:同样对于循环依赖的场景,构造器注入和prototype类型的属性注入都会初始化Bean失败。因为@Service默认是单例的,所以单例的属性注入是可以成功的。

2.Spring 如何解决循环依赖?

Spring bean注入流程
类实例化 -> 属性注入 -> 执行初始化方法 -> (如果有需要)生成代理对象 -> 使用

三级缓存解决循环依赖流程

A、B两个类相互依赖,初始化A的时候,第一步实例化A完成(生成对象工厂实例放入三级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(生成对象工厂实例放入三级缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A没有,三级缓存查询到A的对象工厂,需要AOP增强则生成A的代理对象,没有则直接创建A实例对象,并将A放入到二级缓存,注入A的代理对象完成,生成代理对象B,B移入一级缓存。

继续A属性注入(B的代理对象),然后可能还会依赖注入C对象流程和B一致,所有依赖注入完成后A初始化,生成A的代理对象,发现A的代理对象已存在,则跳过,放入一级缓存。此时A的代理对象也是提前生成的,但是仅针对循环依赖提前生成。

下面为流程图:

三级缓存不存在循环依赖的正常生成流程

第一步实例化A完成(生成对象工厂实例放入三级缓存,此时虽然放入三级缓存,但没有生成代理对象),注入属性,执行初始化方法,生成代理对象,移入一级缓存。

spring为什么不能只用一二级缓存来解决循环依赖? - sahara-随笔 - 博客园

3.spring中出现循环依赖的场景

spring中出现循环依赖主要有以下场景:

2.三级缓存

1.一级缓存

已经完成了bean的所有流程,即实例化、注入、初始化完成的bean实例,并放进了单列池中了。


  
  1. 一级缓存:
  2. /** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
  3. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>( 64);

2.二级缓存

保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入,但已经提前完成了AOP增强。


  
  1. /** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
  2. private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>( 16);

为什么需要二级缓存?

如果出现循环依赖+aop时,多个地方注入这个动态代理对象需要保证都是同一个对象,而三级缓存中的取出来的动态代理对象每次都是新对象,地址值不一样。

3.三级缓存

singletonBean的生产工厂,即创建单列bean的工厂。

存的是一个函数接口, 函数接口实现 创建动态代理调用BeanPostProcessor 。 为了避免重复创建, 调用把返回的动态代理对象或者原实例存储在二级缓存中。


  
  1. /** singletonBean的生产工厂,即创建单列bean的工厂*/
  2. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>( 16);

在取三级缓存并创建bean时,ObjectFactory调用getObject(),不仅会取到bean实例而且还会判断是否生成代理对象,并放到二级缓存中。

为什么需要第三级缓存?

解决如果出现循环依赖,判断是否需要提前进行aop等操作


  
  1. 一级缓存:
  2. /** 保存所有的singletonBean的实例,已经完成了所有流程可以用的bean */
  3. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>( 64);
  4. 二级缓存:
  5. /** 保存所有早期创建的Bean对象,这个Bean还没有完成依赖注入 */
  6. private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>( 16);
  7. 三级缓存:
  8. /** singletonBean的生产工厂,即创建单列bean的工厂*/
  9. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>( 16);
  10. /** 保存所有已经完成初始化的Bean的名字(name) */
  11. private final Set<String> registeredSingletons = new LinkedHashSet<String>( 64);
  12. /**
  13. 标识指定name的Bean对象是否处于创建状态,这个状态非常重要。
  14. 如果创建完毕之后,会将其从 singletonsCurrentlyInCreation 列表中移除,
  15. 并且会将创建好的 bean 放到另外一个单例列表中,这个列表叫做 singletonObjects即一级缓存中。
  16. */
  17. private final Set<String> singletonsCurrentlyInCreation =
  18. Collections.newSetFromMap( new ConcurrentHashMap<String, Boolean>( 16));

思考

1.为什么不使用一级缓存?

如果只使用一级缓存:

1.A先从单列池中查询A,没有则创建A,A对象实例化后准备依赖注入B

2.B对象先从单列池中查询B,没有则创建B,B对象实例化后准备依赖注入A

3.A又从单列池中查询A,此时A由于没有完成创建,单列池中没有A,又回重复1过程,从而造成死循环。

可见只使用一级缓存是会造成循环依赖的,那么就需要把只完成了类的实例化还没有进行后面依赖注入,初始化等过程的对象缓存起来(三级缓存),这样从一级缓存取不到,则取三级缓存。

如果这样貌似使用两级缓存就可以了,那为什么还需要三级缓存?

2.为什么需要二级缓存?只用一级缓存和三级缓存可以吗?

先说结论,

1.如果该对象不涉及AOP代理,只用二级缓存就能解决循环依赖了。

2.如果A不仅被B引用,还同时被C引用等,如果存在代理的情况。只用二级缓存也不能解决循环依赖,因为可能出现两个不同的代理对象违背单一原则。

看下图

即三级缓存获取到的对象工厂ObjectFactory,并取对象时,需要创建新的代理对象,为了防止创建新的代理对象违背单列原则,会将第一次创建的代理对象放到二级缓存中。

spring中为什么要三级缓存?二级不行吗?_知识浅谈的博客-CSDN博客_spring二级缓存与三级缓存

二级缓存因为代理存在的问题流程

第一步实例化A完成(原始实例放入二级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(放入缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A注入(此时A是原始实例,未被代理),生成代理对象B,移入一级缓存,继续A属性注入(B的代理对象),A初始化完成。

B中的依赖对象A是未代理的,存在问题(环境中存在代理和未代理的两种类型的同一个对象A)。

如果上述流程放入二级缓存的是代理对象,则和spring bean生成流程(实例最后生成代理对象)相悖。

二级缓存因为两个以上存在的问题

试想一下,如果出现以下这种情况,我们要如何处理?


  
  1. @Service
  2. public class TestService1 {
  3. @Autowired
  4. private TestService2 testService2;
  5. @Autowired
  6. private TestService3 testService3;
  7. public void test1 () {
  8. }
  9. }
  10. @Service
  11. public class TestService2 {
  12. @Autowired
  13. private TestService1 testService1;
  14. public void test2 () {
  15. }
  16. }
  17. @Service
  18. public class TestService3 {
  19. @Autowired
  20. private TestService1 testService1;
  21. public void test3 () {
  22. }
  23. }

TestService1依赖于TestService2和TestService3,而TestService2依赖于TestService1,同时TestService3也依赖于TestService1。

按照上图的流程可以把TestService1注入到TestService2,并且TestService1的实例是从第三级缓存中获取的。

假设不用第二级缓存,TestService1注入到TestService3的流程如图:

TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象(主要是代理aop对象,如果没有aop对象则不会存在问题)每次可能都不一样的。

这样ObjectFactory就不一样了,违反的单一实例原则,这样不是有问题吗?

为了解决这个问题,spring引入的第二级缓存。上面图1其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。

 

3.为什么需要提前进行aop?按照spring生命周期初始化完成之后进行AOP代理对象创建不可以吗?

我们都知道Bean的aop动态代理创建时在初始化之后,但是循环依赖的Bean如果使用了AOP。 那无法等到解决完循环依赖再创建动态代理, 因为这个时候已经注入属性了,比如A已经注入到B对象的属性中了,此时B中的A对象就不是代理对象了。

所以如果循环依赖的Bean使用了aop. 需要提前创建aop。

并且还要提前判断是否需要进行提前AOP。

4.如何判断需要提前进行aop?

创建本身的时候是没法判断自己是不是循环依赖, 只有在B 引用A (不同bean的引用)下才能判断是不是循环依赖(比如B引用A,A正在创建中,那说明是循环依赖), 所以判断要写在getSingleton中。


  
  1. 假如A是proxy:
  2. A创建Bean - ->注入属性B- -> getBean(B)- ->创建B- ->注入属性A--- -> getSingleton( "a")之后写如下代码
  3. ==================================================================================================
  4. public object getSingleton(beanName){
  5. 先从一级缓存拿 省略code...
  6. if(二级缓存有吗?){
  7. return 二级缓存中拿;
  8. }
  9. if(A是否还在创建中){
  10. 说明是循环依赖.
  11. 二级缓存=调用创建动态代理BeanPostProcessor(判断是否使用aop,没有依然返回原实例);
  12. }
  13. }

所以说二级缓存确实完全可以解决循环依赖的任何情况:包括扩展能力(因为也可以在这里调用BeanPostProcessor, 当然AOP也是基于BeanPostProcessor)

spring源码的实现方式如下:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean


  
  1. boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
  2. if (earlySingletonExposure) {
  3. if ( this.logger.isTraceEnabled()) {
  4. this.logger.trace( "Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
  5. }
  6. this.addSingletonFactory(beanName, () -> {
  7. return this.getEarlyBeanReference(beanName, mbd, bean);
  8. });
  9. }

earlySingletonExposure就是判断后的结果。

5.第三级缓存中为什么要添加ObjectFactory对象,直接保存实例对象不行吗?

答:不行,同二级缓存存在的原因一样。

因为假如你想判断是否对添加到三级缓存中的实例对象进行增强,直接用实例对象是行不通的,假如都是创建代理对象那违反单一原则,都不创建,则无法进行增强。所以得有一个中间态来判断是否需要加AOP加强以及存储加强后的结果避免重复创建。

针对这种场景spring是怎么做的呢?

答案就在AbstractAutowireCapableBeanFactory类doCreateBean方法的这段代码中:

它定义了一个匿名内部类,通过getEarlyBeanReference方法获取代理对象,其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象。
 


版权声明:本文为CSDN博主「蜀州凯哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_72088858/article/details/126698574

二、构造器注入与循环依赖

这种注入方式现在其实用的已经非常少了,但是我们还是有必要了解一下,看看如下代码:


  
  1. @Service
  2. public class TestService1 {
  3. public TestService1 (TestService2 testService2) {
  4. }
  5. }
  6. @Service
  7. public class TestService2 {
  8. public TestService2 (TestService1 testService1) {
  9. }
  10. }

运行结果:

Requested bean is currently in creation: Is there an unresolvable circular reference?

出现了循环依赖,为什么呢?

创建实例TestService1时需要执行构造方法,构造方法需要传入TestService2,所以开始初始化TestService2,这时并没有用到第三级缓存,所以会出现循环依赖的情况。

从图中的流程看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。 

那如何解决基于构造器所造成的循环依赖?

通过添加使用@Lazy注解,延迟加载

如下面代码:


  
  1. @Service
  2. public class TestService1 {
  3. public TestService1 (TestService2 testService2) {
  4. }
  5. }
  6. @Service
  7. public class TestService2 {
  8. @Lazy
  9. public TestService2 (TestService1 testService1) {
  10. }
  11. }

为什么能解决勒?

执行构造器的时候,实际上是用到的TestService2的代理对象

参考资料


1.高频面试题:Spring 如何解决循环依赖?https://zhuanlan.zhihu.com/p/84267654
2.Spring 如何解决循环依赖的问题
https://www.jianshu.com/p/8bb67ca11831


转载:https://blog.csdn.net/sinat_34814635/article/details/128015738
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场