首先看一个使用构造函数的循环依赖,如下:
public class ObjectA { private ObjectB b; public ObjectA(ObjectB b) { this.b = b; }}public class ObjectB { private ObjectA a; public ObjectB(ObjectA a) { this.a = a; }}public class Main { public static void main(String[] args) { //ObjectB b = new ObjectB(new ObjectA(new ObjectB())); }}大家可以看上面这个例子,可以看出是没有办法new出ObjectA或者ObjectB的
那怎么解决上面的例子呢?如下:
public class ObjectA { private ObjectB b; public void setB(ObjectB b) { this.b = b; } public ObjectB getB() { return this.b; }}public class ObjectB { private ObjectA a; public void setA(ObjectA a) { this.a = a; } public ObjectA getA() { return this.a; }}public class Main { public static void main(String[] args) { ObjectB b = new ObjectB(); ObjectA a = new ObjectA(); b.setA(a); a.setB(b); System.out.println(a + " " + a.getB()); System.out.println(b + " " + b.getA()); }}输出如下:
cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d可以看出把构造函数去掉,然后增加set方法就可以实现循环依赖的问题了。
测试例子如下:
@Componentpublic class MyBeanOne { private MyBeanTwo myBeanTwo; @Autowired public MyBeanOne(MyBeanTwo myBeanTwo) { this.myBeanTwo = myBeanTwo; }}@Componentpublic class MyBeanTwo { private MyBeanOne myBeanOne; @Autowired public MyBeanTwo(MyBeanOne myBeanOne) { this.myBeanOne = myBeanOne; }}public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class); MyBeanOne myBeanOne = context.getBean(MyBeanOne.class); MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class); System.out.println(myBeanOne); System.out.println(myBeanTwo); }}输出如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'myBeanOne': Requested bean is currently in creation: Is there an unresolvable circular reference? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)在DefaultSingletonBeanRegistry.beforeSingletonCreation()里打断点,看一下为什么出错了:
第一次经过:
第二次经过:
第三次经过:
我们可以猜测出错的原因是这样的:
去生成myBeanOne,需要生成myBeanTwo去生成myBeanTwo,需要生成myBeanOne去生成myBeanOne,发现myBeanOne已经在创建中了@Componentpublic class MyBeanOne { @Autowired @Getter private MyBeanTwo myBeanTwo;}@Componentpublic class MyBeanTwo { @Autowired @Getter private MyBeanOne myBeanOne;}public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanOne.class, MyBeanTwo.class); MyBeanOne myBeanOne = context.getBean(MyBeanOne.class); MyBeanTwo myBeanTwo = context.getBean(MyBeanTwo.class); System.out.println(myBeanOne + " " + myBeanOne.getMyBeanTwo()); System.out.println(myBeanTwo + " " + myBeanTwo.getMyBeanOne()); }}输出:
cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114 cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114可以看出通过set注入的方式是可以解决循环依赖问题的。
我们继续在DefaultSingletonBeanRegistry.beforeSingletonCreation()里打断点,看一下set方式是怎么经过这里的:
发现只经过了两次,没有第三次,如果有第三次的话,也就抛异常了,所以只经过两次是正常的。
我们看一下DefaultSingletonBeanRegistry.beforeSingletonCreation的调用链:
AbstractBeanFactory.doGetBeanDefaultSingletonBeanRegistry.getSingletonDefaultSingletonBeanRegistry.beforeSingletonCreation那我们就在AbstractBeanFactory.doGetBean这里打断点,看一下set方式,为什么没有第三次
第一次:
第二次:
第三次:
我们可以看到第三次的myBeanOne已经有值了,它就不会执行到DefaultSingletonBeanRegistry.beforeSingletonCreation
如果换成构造方式来调试的话,在第三次,myBeanOne依旧是null的,就会继续往下执行到DefaultSingletonBeanRegistry.beforeSingletonCreation,然后就会抛错。
我们来看一下第三次的myBeanOne是怎么获取的:DefaultSingletonBeanRegistry.getSingleton 方法如下:
可以看到是通过this.singletonFactories.get(beanName)得到一个工厂,通过这个工厂可以创建出对应的bean
我们再来看一下这些个工厂是什么时候被放进去的,DefaultSingletonBeanRegistry.addSingletonFactory:
再执行AbstractAutowireCapableBeanFactory.createBeanInstance 方法时
set方式会执行以下:
调用这个方法之后,回退到 AbstractAutowireCapableBeanFactory.doCreateBean() 继续往下执行,会把工厂放进去。
构造函数的方式会执行以下:
调用这个方法,后面会继续获取myBeanTwo,如下:
然后同理再获取myBeanOne,就会抛异常了,它不会回退到 AbstractAutowireCapableBeanFactory.doCreateBean(),自然也不会把工厂放进去。
对于Set方式,当类构造好之后,会提前把生成这个类的工厂放到缓存中;而构造函数的方式,由于存在构造函数,必须在当下去获取依赖类,所以就没办法构造类,其实原理和刚开始举的Java的例子是一个道理。
Spring 解决循环依赖必须要三级缓存吗?
Spring循环依赖三级缓存是否可以去掉第三级缓存?
Spring 的循环依赖,源码详细分析 → 真的非要三级缓存吗