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:至此全流程结束,循环引用问题完美解决~
以上就是我的个人总结,希望大佬们热情洋溢发表自己的意见,提出问题,谢谢~