您的位置:首页 > 编程语言 > Java开发

Spring 是怎么处理循环依赖的?

2022-02-12 19:35 441 查看

Java语法中的循环依赖

首先看一个使用构造函数的循环依赖,如下:

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@3fa77460
cn.eagle.li.spring.dependence.demo1.ObjectB@3fa77460 cn.eagle.li.spring.dependence.demo1.ObjectA@4534b60d

可以看出把

构造函数
去掉,然后增加
set方法
就可以实现循环依赖的问题了。

Spring 的构造函数循环依赖

测试例子如下:

@Component
public class MyBeanOne {
private MyBeanTwo myBeanTwo;

@Autowired
public MyBeanOne(MyBeanTwo myBeanTwo) {
this.myBeanTwo = myBeanTwo;
}
}

@Component
public 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已经在创建中了

Spring 的Set方式循环依赖

@Component
public class MyBeanOne {
@Autowired
@Getter
private MyBeanTwo myBeanTwo;
}

@Component
public 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@35ef1869
cn.eagle.li.spring.dependence.demo3.MyBeanTwo@35ef1869 cn.eagle.li.spring.dependence.demo3.MyBeanOne@3c407114

可以看出通过set注入的方式是可以解决循环依赖问题的。

我们继续在

DefaultSingletonBeanRegistry.beforeSingletonCreation()
里打断点,看一下set方式是怎么经过这里的: 发现只经过了两次,没有第三次,如果有第三次的话,也就抛异常了,所以只经过两次是正常的。

为什么构造函数有三次,而set方式有两次?

我们看一下

DefaultSingletonBeanRegistry.beforeSingletonCreation
的调用链:

AbstractBeanFactory.doGetBean
DefaultSingletonBeanRegistry.getSingleton
DefaultSingletonBeanRegistry.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 的循环依赖,源码详细分析 → 真的非要三级缓存吗

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: