SpringBoot中循环依赖问题由浅入深看源码

Spring在启动类启动的时候,就会自动扫描所有加了对应注解的类,然后把它们通过反射,调用默认的构造方法以单例模式创建出来,然后存放在Map容器中。这个是传说中的IOC。
但是假设现在有这种情况:
@Component
public class A {
    
    
    public B b = new B();

    public A(){
    
    
        System.out.println("Bean A 的实例化依赖了 B:"+b);
    }
}
@Component
public class B {
    
    
    public A a = new A();

    public B(){
    
    
        System.out.println("Bean B 的实例化依赖了 A:"+a);
    }
}
启动会发生什么?
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in file [/Users/jojo/jojo.springcloud/cloud-purchase/target/classes/com/jojo/cloud/pojo/A.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.jojo.cloud.pojo.A]: Constructor threw exception; nested exception is java.lang.StackOverflowError
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1320) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1214) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
	.........
Caused by: java.lang.StackOverflowError: null
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.A.<init>(A.java:7) ~[classes/:na]
	at com.jojo.cloud.pojo.B.<init>(B.java:7) ~[classes/:na]
这个就是两个Bean之间循环依赖导致的栈溢出,原因是IOC创建 A的时候,发现需要new B ,于是又去创建B,创建B的时候,发现又需要A…

如何快速解决?

基于JVM类加载的基础,可以知道类加载过程中,实例化分配内存地址的时候,不一定要初始化赋值,所以从这个方向入手,我们采用懒汉式创建,改动如下:
@Component
public class B {
    
    
    public A a ;

    public B(){
    
    
        System.out.println("Bean B 的实例化依赖了 A:"+a);
    }
}
@Component
public class A {
    
    
    public B b ;

    public A(){
    
    
        System.out.println("Bean A 的实例化依赖了 B:"+b);
    }
}
启动成功后可以看到调用默认的无餐构造,直接打印出:
2020-10-05 12:22:44.749  WARN 64539 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : Unable to start LiveReload server
Bean A 的实例化依赖了 B:null
Bean B 的实例化依赖了 A:null
2020-10-05 12:22:45.475  WARN 64539 --- [  restartedMain] c.n.c.sources.URLConfigurationSource     : No URLs will be polled as dynamic configuration sources.

对于null是因为还没初始化。在要使用的时候,再new A () ;这就是懒汉式。

在这里插入图片描述

这样确实能快速解决循环依赖的问题,但是这样就破坏了单例模式,一定程度上违背了Spring IOC 的原则。

网上有的博客说循环依赖问题可以加@Lazy解决,这个…我的理解是@Lazy是启动时候不加载,所以才不报错,用到对应的Bean的时候才构造出来放入IOC容器,但是它还是没办法解决循环依赖问题啊,创建的时候一样会依赖死循环,除非也是改成以上这种懒汉式。有大神骚操作的讲解下谢谢。
以上代码都是我们DIY的POJO,那么如果是Spring,有两个Controller互相依赖,它会报错吗?会强制要求我们自己处理吗?

在这里插入图片描述

很明显发现不会,并且跟我们处理方式一样,都是null,这个时候又有新疑问,既然是未初始化,假如要访问其中的方法或者变量,是否访问不到:

在这里插入图片描述

结果当然是可以的,Spring是允许,并且支持循环依赖的!这个时候,任谁都会忽然心生好奇,为何这么神奇,Spring底层做了什么,能完美解决循环依赖这个问题?它的具体操作又是怎么样?
Spring采用了三级缓存:

singletonObjects:一级缓存,实例化的对象;
earlySingletonObjects:二级缓存,提前曝光的对象;
singletonFactories:三级缓存,实例化的对象的对象工厂。

三个步骤:

1:实例化
2:填充属性
3:初始化

在这里插入图片描述
在这里插入图片描述

上图应该可以很清晰看懂源码的逻辑了。回到我们最初的问题,Spring的解决流程大概是:
1:创建A,第一步就在三级缓存中实例化,分配内存空间,产生内存地址引用,但是这个时候还是null。
2:帮A进行填充属性,发现有需要B,于是去容器中找B的引用。
3:当然找不到B,所以要创建B。
4:创建B跟创建A步骤一样,先实例化,分配内存,保存引用。
5:发现填充B需要A,于是回去容器找A。
6:一级缓存没有,二级缓存没有,三级缓存找到了A的引用,于是返回(此时的A还是null,但是总比没有好),同时把A移到二级缓存中,此时B的引用以及找到并且可以给到A,所以这个没问题。
7:B拿着A的引用,首先成功初始化。然后返回。
8:A获得初始化成功的B,也成功初始化。
9:至此全流程结束,循环引用问题完美解决~
以上就是我的个人总结,希望大佬们热情洋溢发表自己的意见,提出问题,谢谢~

猜你喜欢

转载自blog.csdn.net/whiteBearClimb/article/details/108927445