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:初始化